{"_id":"@apisurf/ngn","_rev":"11-ff99f60496412ce079a8fdada8c182a3","name":"@apisurf/ngn","dist-tags":{"latest":"0.2.4"},"versions":{"0.1.17":{"name":"@apisurf/ngn","version":"0.1.17","keywords":["cli","scheduler","cron","task","automation","typescript"],"author":{"name":"Luka Vidakovic"},"license":"ISC","_id":"@apisurf/ngn@0.1.17","maintainers":[{"name":"apisurfer","email":"luka@apisurf.dev"}],"homepage":"https://github.com/apisurf/ngn#readme","bugs":{"url":"https://github.com/apisurf/ngn/issues"},"bin":{"ngn":"dist/cli.js"},"dist":{"shasum":"2c01b374641994e38d382744fb9c65493e3a6ed8","tarball":"https://registry.npmjs.org/@apisurf/ngn/-/ngn-0.1.17.tgz","fileCount":6,"integrity":"sha512-2TV89UmLoecfDLbfHSnbvIfv1A3K0oQOQwm8MY/xPsV8ghK6LfhtlzzaHiwOG/KdMWRgmDNDr0O3KgycNt3lGg==","signatures":[{"sig":"MEUCIC9fKxohtSr9foC2uWnBaZYMnlwoAcBf3oKYxuLACZW6AiEAu2toNeypOLF9P5sj8TDnD8Usk6yKms6uJU4GFAVdvTU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":130814},"_from":"file:apisurf-ngn-0.1.17.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","inject-deps":"node scripts/inject-dependencies.js"},"_npmUser":{"name":"apisurfer","email":"luka@apisurf.dev"},"_resolved":"/private/var/folders/y8/pvw0v9kd5kd88_hpw2ntyjk00000gn/T/a1dcf5f952ea27c7bca230eef3da5afd/apisurf-ngn-0.1.17.tgz","_integrity":"sha512-2TV89UmLoecfDLbfHSnbvIfv1A3K0oQOQwm8MY/xPsV8ghK6LfhtlzzaHiwOG/KdMWRgmDNDr0O3KgycNt3lGg==","repository":{"url":"git+https://github.com/apisurf/ngn.git","type":"git","directory":"packages/ngn"},"_npmVersion":"10.9.7","description":"CLI scheduler for Node tasks. Automate and monitor Javascript and Typescript tasks.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"zod":"^3.24.2","glob":"^11.0.3","hono":"^4.8.12","tsup":"^8.3.5","debug":"^4.4.1","dotenv":"^17.2.1","esbuild":"^0.27.1","chokidar":"^4.0.1","outmatch":"^1.0.0","commander":"^12.1.0","node-cron":"^3.0.3","typescript":"^5.7.2","@libsql/client":"^0.14.0","tiny-invariant":"^1.3.3","@hono/node-server":"^1.18.1"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"ngn-os":"0.1.17","ngn-api":"0.1.17","ngn-core":"0.1.17","typescript":"^5.7.2","@types/node":"^22.8.1","ngn-persistence":"0.1.17","@types/node-cron":"^3.0.11"},"_npmOperationalInternal":{"tmp":"tmp/ngn_0.1.17_1777926034654_0.6777284224264475","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@apisurf/ngn","version":"0.2.0","keywords":["cli","scheduler","cron","task","automation","typescript"],"author":{"name":"Luka Vidakovic"},"license":"ISC","_id":"@apisurf/ngn@0.2.0","maintainers":[{"name":"apisurfer","email":"luka@apisurf.dev"}],"homepage":"https://github.com/apisurf/ngn#readme","bugs":{"url":"https://github.com/apisurf/ngn/issues"},"bin":{"ngn":"dist/cli.js"},"dist":{"shasum":"08a53395ea6014633651d8d7cebeafa2227aacfc","tarball":"https://registry.npmjs.org/@apisurf/ngn/-/ngn-0.2.0.tgz","fileCount":6,"integrity":"sha512-NJFbunJoITLtfI/617VRPVPSg0LcNtplZ5WOcr1GNV0rvbB1p8FFudgG91R7PXrCLOMSg7uYoNDOCJvieZ0jHw==","signatures":[{"sig":"MEQCIHE5NSAdy5wwAUi0Z++uQduu8D9rN7R9+p8i3tSNE9uSAiB/2zyss9XUE6H3HvROJ34bTv3VIIB15HncJ7q0FpND7g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":129969},"_from":"file:apisurf-ngn-0.2.0.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","inject-deps":"node scripts/inject-dependencies.js"},"_npmUser":{"name":"apisurfer","email":"luka@apisurf.dev"},"_resolved":"/private/var/folders/y8/pvw0v9kd5kd88_hpw2ntyjk00000gn/T/ce079395e04797766034f4f16bd73d18/apisurf-ngn-0.2.0.tgz","_integrity":"sha512-NJFbunJoITLtfI/617VRPVPSg0LcNtplZ5WOcr1GNV0rvbB1p8FFudgG91R7PXrCLOMSg7uYoNDOCJvieZ0jHw==","repository":{"url":"git+https://github.com/apisurf/ngn.git","type":"git","directory":"packages/ngn"},"_npmVersion":"10.9.7","description":"CLI scheduler for Node tasks. Automate and monitor Javascript and Typescript tasks.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"zod":"^3.24.2","glob":"^11.0.3","hono":"^4.8.12","tsup":"^8.3.5","debug":"^4.4.1","dotenv":"^17.2.1","esbuild":"^0.27.1","chokidar":"^4.0.1","outmatch":"^1.0.0","commander":"^12.1.0","node-cron":"^3.0.3","typescript":"^5.7.2","@libsql/client":"^0.14.0","tiny-invariant":"^1.3.3","@hono/node-server":"^1.18.1"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"ngn-os":"0.2.0","ngn-core":"0.2.0","typescript":"^5.7.2","@types/node":"^22.8.1","ngn-persistence":"0.2.0","@types/node-cron":"^3.0.11"},"_npmOperationalInternal":{"tmp":"tmp/ngn_0.2.0_1787046641522_0.590005377507643","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@apisurf/ngn","version":"0.2.1","keywords":["cli","scheduler","cron","task","automation","typescript"],"author":{"name":"Luka Vidakovic"},"license":"ISC","_id":"@apisurf/ngn@0.2.1","maintainers":[{"name":"apisurfer","email":"luka@apisurf.dev"}],"homepage":"https://github.com/apisurf/ngn#readme","bugs":{"url":"https://github.com/apisurf/ngn/issues"},"bin":{"ngn":"dist/cli.js"},"dist":{"shasum":"d6a7936798ac2866e3739667ba1b224225189d0d","tarball":"https://registry.npmjs.org/@apisurf/ngn/-/ngn-0.2.1.tgz","fileCount":6,"integrity":"sha512-jq/9qRP6ZFRmAUPRRXRGmNpdpIbf5qBrsf9MtPnYVGd9Nw7Zdc9ebZgL6bDi0Fu8K2Qr0YlizPohv6WeGYqX4A==","signatures":[{"sig":"MEUCIGlbhYZRdowo2ghN4cyJly48ncgmE/5s0D3vpGKCfncQAiEA0B4l4b+QQIa+/DgKj57R3zzBdZHPmd52WxbJHvrYblU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":130349},"_from":"file:apisurf-ngn-0.2.1.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","inject-deps":"node scripts/inject-dependencies.js"},"_npmUser":{"name":"apisurfer","email":"luka@apisurf.dev"},"_resolved":"/private/var/folders/y8/pvw0v9kd5kd88_hpw2ntyjk00000gn/T/f58674de09b32165861d87b6e484e642/apisurf-ngn-0.2.1.tgz","_integrity":"sha512-jq/9qRP6ZFRmAUPRRXRGmNpdpIbf5qBrsf9MtPnYVGd9Nw7Zdc9ebZgL6bDi0Fu8K2Qr0YlizPohv6WeGYqX4A==","repository":{"url":"git+https://github.com/apisurf/ngn.git","type":"git","directory":"packages/ngn"},"_npmVersion":"10.9.7","description":"CLI scheduler for Node tasks. Automate and monitor Javascript and Typescript tasks.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"zod":"^3.24.2","glob":"^11.0.3","hono":"^4.8.12","tsup":"^8.3.5","debug":"^4.4.1","dotenv":"^17.2.1","esbuild":"^0.27.1","chokidar":"^4.0.1","outmatch":"^1.0.0","commander":"^12.1.0","node-cron":"^3.0.3","typescript":"^5.7.2","@libsql/client":"^0.14.0","tiny-invariant":"^1.3.3","@hono/node-server":"^1.18.1"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"ngn-os":"0.2.1","ngn-core":"0.2.1","typescript":"^5.7.2","@types/node":"^22.8.1","ngn-persistence":"0.2.1","@types/node-cron":"^3.0.11"},"_npmOperationalInternal":{"tmp":"tmp/ngn_0.2.1_1787048615232_0.8684776635442137","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@apisurf/ngn","version":"0.2.2","keywords":["cli","scheduler","cron","task","automation","typescript"],"author":{"name":"Luka Vidakovic"},"license":"ISC","_id":"@apisurf/ngn@0.2.2","maintainers":[{"name":"apisurfer","email":"luka@apisurf.dev"}],"homepage":"https://github.com/apisurf/ngn#readme","bugs":{"url":"https://github.com/apisurf/ngn/issues"},"bin":{"ngn":"dist/cli.js"},"dist":{"shasum":"bb32b640b84e13f7520feefc99ed18f0d3cecd78","tarball":"https://registry.npmjs.org/@apisurf/ngn/-/ngn-0.2.2.tgz","fileCount":6,"integrity":"sha512-GefiumxsvSx4DVFFnRZwm7JwhEBYQMMZkj6XM7IetJgKiWFqlhE3n2r4tl6ya0WFAqaUeN4DFQ6sgQIUsNqE3Q==","signatures":[{"sig":"MEUCIQDtNc2L4Ne012s7yYyyjSg8tiIJLPG3K0S+LBzyN5iTcQIgJB23qBbEEmFVex+XayWfEcKgJsLEaQ0II4caPrqIOuo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":76699},"type":"module","_from":"file:apisurf-ngn-0.2.2.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsup --watch","build":"tsup"},"_npmUser":{"name":"apisurfer","email":"luka@apisurf.dev"},"_resolved":"/private/var/folders/y8/pvw0v9kd5kd88_hpw2ntyjk00000gn/T/47fbce7333cc1f25a5bf2e582efec268/apisurf-ngn-0.2.2.tgz","_integrity":"sha512-GefiumxsvSx4DVFFnRZwm7JwhEBYQMMZkj6XM7IetJgKiWFqlhE3n2r4tl6ya0WFAqaUeN4DFQ6sgQIUsNqE3Q==","repository":{"url":"git+https://github.com/apisurf/ngn.git","type":"git","directory":"packages/ngn"},"_npmVersion":"10.9.7","description":"CLI scheduler for Node tasks. Automate and monitor Javascript and Typescript tasks.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"zod":"^3.24.2","glob":"^11.0.3","hono":"^4.8.12","tsup":"^8.3.5","outmatch":"^1.0.0","commander":"^12.1.0","node-cron":"^3.0.3","typescript":"^5.7.2","tiny-invariant":"^1.3.3","@apisurf/ngn-os":"0.2.2","@apisurf/ngn-core":"0.2.2","@hono/node-server":"^1.18.1","@apisurf/ngn-persistence":"0.2.2"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"@types/node":"^22.8.1","@types/node-cron":"^3.0.11"},"_npmOperationalInternal":{"tmp":"tmp/ngn_0.2.2_1787054638607_0.4825882037982234","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@apisurf/ngn","version":"0.2.3","keywords":["cli","scheduler","cron","task","automation","typescript"],"author":{"name":"Luka Vidakovic"},"license":"ISC","_id":"@apisurf/ngn@0.2.3","maintainers":[{"name":"apisurfer","email":"luka@apisurf.dev"}],"homepage":"https://github.com/apisurf/ngn#readme","bugs":{"url":"https://github.com/apisurf/ngn/issues"},"bin":{"ngn":"dist/cli.js"},"dist":{"shasum":"59b59b61401964f271e47b3a772cf57ae380b3f5","tarball":"https://registry.npmjs.org/@apisurf/ngn/-/ngn-0.2.3.tgz","fileCount":6,"integrity":"sha512-+aSqIs8v13HUBT64Yu4NIF4VAyFEau3umBP1HiZjrC4KgFpEnZ3m78DA8z4hKVjjnS1I/56r/Yo0wStCGBPNGg==","signatures":[{"sig":"MEUCIHmvnc7sFVrwH5hVaoApzXTHwwGZBPiCpbdoekTevNtEAiEAmdVQkJ2yADC+LkIR4bCU/pJwYXrfcWaj5BRVrx3YiyQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":76699},"type":"module","_from":"file:apisurf-ngn-0.2.3.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsup --watch","build":"tsup"},"_npmUser":{"name":"apisurfer","email":"luka@apisurf.dev"},"_resolved":"/private/var/folders/y8/pvw0v9kd5kd88_hpw2ntyjk00000gn/T/c493f4b2e408053bb21b6d8b4703cd54/apisurf-ngn-0.2.3.tgz","_integrity":"sha512-+aSqIs8v13HUBT64Yu4NIF4VAyFEau3umBP1HiZjrC4KgFpEnZ3m78DA8z4hKVjjnS1I/56r/Yo0wStCGBPNGg==","repository":{"url":"git+https://github.com/apisurf/ngn.git","type":"git","directory":"packages/ngn"},"_npmVersion":"10.9.7","description":"CLI scheduler for Node tasks. Automate and monitor Javascript and Typescript tasks.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"zod":"^3.24.2","glob":"^11.0.3","hono":"^4.8.12","tsup":"^8.3.5","outmatch":"^1.0.0","commander":"^12.1.0","node-cron":"^3.0.3","typescript":"^5.7.2","tiny-invariant":"^1.3.3","@apisurf/ngn-os":"0.2.3","@apisurf/ngn-core":"0.2.3","@hono/node-server":"^1.18.1","@apisurf/ngn-persistence":"0.2.3"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"@types/node":"^22.8.1","@types/node-cron":"^3.0.11"},"_npmOperationalInternal":{"tmp":"tmp/ngn_0.2.3_1787054999252_0.3964397737624612","host":"s3://npm-registry-packages-npm-production"}},"0.2.4":{"name":"@apisurf/ngn","version":"0.2.4","type":"module","bin":{"ngn":"dist/cli.js"},"author":{"name":"Luka Vidakovic"},"description":"CLI scheduler for Node tasks. Automate and monitor Javascript and Typescript tasks.","keywords":["cli","scheduler","cron","task","automation","typescript"],"license":"ISC","repository":{"type":"git","url":"git+https://github.com/apisurf/ngn.git","directory":"packages/ngn"},"homepage":"https://github.com/apisurf/ngn#readme","bugs":{"url":"https://github.com/apisurf/ngn/issues"},"dependencies":{"@hono/node-server":"^1.18.1","commander":"^12.1.0","glob":"^11.0.3","hono":"^4.8.12","node-cron":"^3.0.3","outmatch":"^1.0.0","tiny-invariant":"^1.3.3","tsup":"^8.3.5","typescript":"^5.7.2","zod":"^3.24.2","@apisurf/ngn-core":"0.2.4","@apisurf/ngn-persistence":"0.2.4","@apisurf/ngn-os":"0.2.4"},"devDependencies":{"@types/node":"^22.8.1","@types/node-cron":"^3.0.11"},"exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"}},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"scripts":{"dev":"tsup --watch","build":"tsup"},"_id":"@apisurf/ngn@0.2.4","_integrity":"sha512-M9CDvVbMye1lFVnQifsNz3JVU3UD/on/GMOqz9W/Vuprw4fovAIDEY1ijPty0AksJ8hJ5jdMbzcl5wZtssug7A==","_resolved":"/private/var/folders/y8/pvw0v9kd5kd88_hpw2ntyjk00000gn/T/3372f2da5307948c2d1d970a4db6ee0f/apisurf-ngn-0.2.4.tgz","_from":"file:apisurf-ngn-0.2.4.tgz","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-M9CDvVbMye1lFVnQifsNz3JVU3UD/on/GMOqz9W/Vuprw4fovAIDEY1ijPty0AksJ8hJ5jdMbzcl5wZtssug7A==","shasum":"0fce28305a1721ab712f45baba87f06cbff58fb1","tarball":"https://registry.npmjs.org/@apisurf/ngn/-/ngn-0.2.4.tgz","fileCount":6,"unpackedSize":76699,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEMCICNxH3ktllGagbxJijl/XgEHwW4HlydHlyKMT5b2wRy9Ah9H408gCDIPJ+1zEzWCsqUnAQUL4DEtl4uogyy8gD8E"}]},"_npmUser":{"name":"apisurfer","email":"luka@apisurf.dev"},"directories":{},"maintainers":[{"name":"apisurfer","email":"luka@apisurf.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ngn_0.2.4_1787055111620_0.3714101905896383"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-04T20:20:34.578Z","modified":"2026-08-18T12:11:51.955Z","1.0.0":"2025-11-08T18:25:18.018Z","1.0.1":"2025-11-13T16:10:57.763Z","1.0.2":"2025-11-13T16:12:54.582Z","1.0.3":"2025-11-26T21:11:26.284Z","0.1.17":"2026-05-04T20:20:34.812Z","0.2.0":"2026-08-18T09:50:41.662Z","0.2.1":"2026-08-18T10:23:35.421Z","0.2.2":"2026-08-18T12:03:58.803Z","0.2.3":"2026-08-18T12:09:59.409Z","0.2.4":"2026-08-18T12:11:51.768Z"},"bugs":{"url":"https://github.com/apisurf/ngn/issues"},"author":{"name":"Luka Vidakovic"},"license":"ISC","homepage":"https://github.com/apisurf/ngn#readme","keywords":["cli","scheduler","cron","task","automation","typescript"],"repository":{"type":"git","url":"git+https://github.com/apisurf/ngn.git","directory":"packages/ngn"},"description":"CLI scheduler for Node tasks. Automate and monitor Javascript and Typescript tasks.","maintainers":[{"name":"apisurfer","email":"luka@apisurf.dev"}],"readme":"# ngn task scheduler\n\nCLI scheduler for Node tasks.\nAutomate, monitor and orchestrate Javascript and Typescript tasks by single CLI command.\n\n## Installation\n\nGlobal installation\n\n```bash\nnpm install -g @apisurf/ngn\nngn <command>\n```\n\nOr use without installing\n\n```bash\nnpx @apisurf/ngn <command>\n```\n\nCheat sheet:\n\n```bash\nngn --version # check current version\nngn init # initialize ngn inside current folder\nngn add test.ts # add a task file\nngn run # start running all tasks (blocks until Ctrl-C)\nngn run --match \"tasks/test.ts\" # run a subset — match the path, and quote the glob\nngn run:once tasks/test.ts # run one task now, then exit\nngn run:single tasks/test.ts -t \"*/2 * * * * *\" # schedule one task on your own pattern\nngn sql \"SELECT * FROM task_runs ORDER BY id DESC LIMIT 20\" # query a run history\n```\n\n## Writing a task\n\nA task file exports `timing` — a cron pattern, seconds first — and `task`.\nEverything else is optional.\n\n```ts\n// tasks/scrape.ts\nimport { TaskContext } from \"@apisurf/ngn\";\n\n// Run every 5 seconds\nexport const timing = \"*/5 * * * * *\";\n\nexport const task = async (ctx: TaskContext) => {\n  await ctx.log.info(\"fetching\");\n\n  const html = await fetch(\"https://example.com\").then((res) => res.text());\n  await ctx.kv.set(\"last-length\", String(html.length));\n\n  return { length: html.length };\n};\n```\n\n`ctx` carries `log.info/error/warning`, `kv.get/set/delete`, `timing.start(label)`,\n`sqlite`, `env` and `meta`. Whatever `task` returns is recorded with the run.\n\nAnything else a task needs, it imports itself:\n\n```ts\nimport { S3Client } from \"@aws-sdk/client-s3\"; // npm install it in your project\n```\n\nOnly your task code is bundled. Imports are left alone and resolved from your\nown `node_modules` at run time, so the version you installed is the version\nthat runs.\n\n### Storing data with `ctx.sqlite`\n\nEvery task gets its own SQLite database, with nothing to configure. It lives\nnext to the task file — `tasks/scrape.ts` writes to `tasks/scrape.db` — and the\nfile is only created once the task actually uses it.\n\nThis is your data, and it is a different file from the one `dbPath` points at:\nthat one is ngn's own, holding the run history described\n[below](#reading-what-your-tasks-did).\n\n```ts\nexport const task = async (ctx: TaskContext) => {\n  await ctx.sqlite.execute(\"CREATE TABLE IF NOT EXISTS pages (url TEXT, seen TEXT)\");\n  await ctx.sqlite.execute(\"INSERT INTO pages (url, seen) VALUES (?, ?)\", [\n    \"https://example.com\",\n    new Date().toISOString(),\n  ]);\n\n  const { rows } = await ctx.sqlite.execute(\"SELECT COUNT(*) AS n FROM pages\");\n  await ctx.log.info(`${rows[0].n} pages`);\n};\n```\n\n| Member | Does |\n| --- | --- |\n| `execute(sql, args?)` | One statement against the task's own database. |\n| `batch(statements)` | Several statements in one round trip. |\n| `client` | The raw libsql client, for anything the above does not cover. |\n| `path` | Absolute path of the task's own database file. |\n| `initDB({ file, migrations })` | Open another database in the task's folder. |\n| `destroyDB(file)` | Delete a database file in the task's folder. |\n\n`initDB` takes a path relative to the task's folder and runs any migrations you\ngive it, once each — they are tracked in a `_ngn_migrations` table inside that\ndatabase:\n\n```ts\nconst cache = await ctx.sqlite.initDB({\n  file: \"cache.db\",\n  migrations: [\n    { id: \"2026-01-13-001\", up: \"CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)\" },\n    { id: \"2026-01-14-001\", up: \"ALTER TABLE items ADD COLUMN qty INTEGER\" },\n  ],\n});\n\nawait cache.execute(\"INSERT INTO items (name, qty) VALUES (?, ?)\", [\"widget\", 3]);\n```\n\nA task can only reach databases inside its own folder. Absolute paths and paths\nthat climb out with `..` are rejected, so one task's data cannot be opened or\ndeleted by a task sitting in a different folder. Subfolders of the task's own\nfolder are fine.\n\nConnections are held open between runs and closed when `ngn` shuts down.\n\n### Hooks\n\nFour more optional exports run around `task`. Each one gets the same `ctx`:\n\n| Export | Runs |\n| --- | --- |\n| `shouldSkip` | Before `task`. Returning `true` skips the run — no other hook fires. |\n| `onSuccess` | After `task` resolves. |\n| `onError` | After `task` throws, with the error. The error is still rethrown. |\n| `onComplete` | After success or failure. Not called on a skipped run. |\n\n```ts\nimport { TaskContext } from \"@apisurf/ngn\";\n\nexport const timing = \"*/5 * * * * *\";\n\nexport const task = async (ctx: TaskContext) => {\n  const end = ctx.timing.start(\"fetch\");\n  const html = await fetch(\"https://example.com\").then((res) => res.text());\n  await end();\n\n  return { length: html.length };\n};\n\n// Skip the run entirely — nothing below this fires\nexport const shouldSkip = async (ctx: TaskContext) => {\n  return (await ctx.kv.get(\"paused\")) === \"1\";\n};\n\nexport const onSuccess = async (ctx: TaskContext) => {\n  await ctx.log.info(\"scrape ok\");\n};\n\n// The error is still rethrown and recorded after this runs\nexport const onError = async (error: Error, ctx: TaskContext) => {\n  await ctx.log.error(`scrape failed: ${error.message}`);\n};\n\n// Cleanup — runs after both success and failure\nexport const onComplete = async (ctx: TaskContext) => {\n  await ctx.kv.set(\"last-run\", new Date().toISOString());\n};\n```\n\n## Reading what your tasks did\n\nRuns, logs and timings go into one SQLite file — whatever `dbPath` in\n`ngn.config.ts` points at. Set it to a `file:` URL; the default (`:memory:`)\nkeeps nothing once the process exits.\n\n```ts\n// ngn.config.ts\nimport { defineConfig } from \"@apisurf/ngn\";\n\nexport default defineConfig({\n  dbPath: \"file:./ngn.sqlite\",\n  port: 4545,\n  match: [\"tasks/**/*.ts\"],\n  envFile: \".env\",\n});\n```\n\nThere are two ways to read that file, and neither needs the scheduler running.\n\n**From the terminal** — `ngn sql` takes any query SQLite accepts and prints a\ntable, or `--json` / `--csv`. Rows go to stdout and counts to stderr, so piping\ninto `jq` works. It opens the file without migrating it, so a query can never\nchange the schema. `ngn sql --help` lists the tables and their columns.\n\n```bash\nngn sql \"SELECT status, COUNT(*) FROM task_runs GROUP BY status\"\nngn sql \"SELECT * FROM logs WHERE status = 'error' ORDER BY id DESC\" --json\n```\n\n**In a browser** — [`@apisurf/ngnui`](https://www.npmjs.com/package/@apisurf/ngnui)\nis a separate CLI that serves a prebuilt dashboard over the same file:\n\n```bash\nnpx @apisurf/ngnui --db ./ngn.sqlite\n```\n\n`ngn run` prints the exact command for your project when it starts. The live\ntask editor in that UI needs a runtime to execute against, which only a running\n`ngn run` has — pass `--live http://127.0.0.1:4545` (or whatever `port` is set\nto in your config) to connect the two.\n","readmeFilename":"README.md"}