{"_id":"@8x/kysely-d1","_rev":"2-c81e694834242163f00f2bebadc28e7f","name":"@8x/kysely-d1","dist-tags":{"beta":"1.0.1","latest":"1.1.0"},"versions":{"1.0.1":{"name":"@8x/kysely-d1","version":"1.0.1","license":"MIT","_id":"@8x/kysely-d1@1.0.1","maintainers":[{"name":"ken0x0a","email":"ken0x0a+npm@gmail.com"}],"homepage":"https://github.com/ken0x0a/kysely-d1-impl","dist":{"shasum":"70df23a84ee7e41cd7e04f3557ec206b52854320","tarball":"https://registry.npmjs.org/@8x/kysely-d1/-/kysely-d1-1.0.1.tgz","fileCount":5,"integrity":"sha512-92O4Swb23A4MTeYaH+HxhiDCBf3S2nDRtfJEgoOU6seSRvPTvwi/5WenUFb3AgcqJHBJsR/xILSG/6tCTQcAAA==","signatures":[{"sig":"MEUCIQDBawRNWCu8vgcFaHKsUveTYBDTsGSUcdDs6Cav0t65OgIgM54N+oOBgjoyyyyYXGXO6gsjHcqZD+2jHBujYZ+mslU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":9919},"main":"src/index.ts","type":"module","types":"src/index.ts","module":"src/index.ts","shasum":"70df23a84ee7e41cd7e04f3557ec206b52854320","scripts":{"test":"vitest run","prepare":"bash scripts/setup-git-hooks.sh","typecheck":"wrangler types && tsc","cf-typegen":"wrangler types","setup-git-hooks":"bash scripts/setup-git-hooks.sh"},"_npmUser":{"name":"ken0x0a","email":"ken0x0a+npm@gmail.com"},"_integrity":"sha512-92O4Swb23A4MTeYaH+HxhiDCBf3S2nDRtfJEgoOU6seSRvPTvwi/5WenUFb3AgcqJHBJsR/xILSG/6tCTQcAAA==","_npmVersion":"10.8.3","directories":{},"_nodeVersion":"24.3.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"kysely":"^0.29.2","vitest":"~3.2.0","@types/bun":"latest","typescript":"6.0.3","@biomejs/biome":"2.3.11","@cloudflare/workers-types":"^4.20260118.0","@cloudflare/vitest-pool-workers":"^0.12.4"},"peerDependencies":{"kysely":"^0.28.10 || ^0.29.0"},"_npmOperationalInternal":{"tmp":"tmp/kysely-d1_1.0.1_1782891394918_0.6158477826471098","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@8x/kysely-d1","version":"1.1.0","description":"A Kysely dialect for Cloudflare D1, with insertId support and typed atomic batches.","keywords":["kysely","cloudflare","d1","workers","sqlite","dialect"],"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"module":"./dist/index.js","main":"./dist/index.js","types":"./dist/index.d.ts","sideEffects":false,"license":"MIT","author":"ken0x0a","homepage":"https://github.com/ken0x0a/kysely-d1-impl","repository":{"type":"git","url":"git+https://github.com/ken0x0a/kysely-d1-impl.git"},"bugs":{"url":"https://github.com/ken0x0a/kysely-d1-impl/issues"},"publishConfig":{"access":"public"},"peerDependencies":{"@cloudflare/workers-types":"^4","kysely":"^0.28.10 || ^0.29.0"},"peerDependenciesMeta":{"@cloudflare/workers-types":{"optional":true}},"scripts":{"cf-typegen":"wrangler types","build":"bash scripts/build.sh","typecheck":"wrangler types && tsc","test":"vitest run","setup-git-hooks":"bash scripts/setup-git-hooks.sh","prepare":"bash scripts/setup-git-hooks.sh && bash scripts/build.sh"},"devDependencies":{"@biomejs/biome":"2.3.11","@cloudflare/vitest-pool-workers":"^0.12.4","@cloudflare/workers-types":"^4.20260118.0","@types/bun":"latest","kysely":"^0.29.2","typescript":"6.0.3","vitest":"~3.2.0"},"_id":"@8x/kysely-d1@1.1.0","_integrity":"sha512-tpKAz6gTUJrQX9ot7koN/UMZr02cmgNNIqcJJdeIXY6uIRn2htobiZSKfZNsipHxMl65ZAqsdRv1MpX9+7OGpA==","_nodeVersion":"26.3.0","_npmVersion":"10.8.3","shasum":"984a6e2486f58af0058fc36beeb354922f15318b","dist":{"integrity":"sha512-tpKAz6gTUJrQX9ot7koN/UMZr02cmgNNIqcJJdeIXY6uIRn2htobiZSKfZNsipHxMl65ZAqsdRv1MpX9+7OGpA==","shasum":"984a6e2486f58af0058fc36beeb354922f15318b","tarball":"https://registry.npmjs.org/@8x/kysely-d1/-/kysely-d1-1.1.0.tgz","fileCount":18,"unpackedSize":44441,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDRkJ08gVDNbJJWNWmdzG+vKHp4rCAEZxcC84ag0qw5tgIgLXvBBjhEADs9OoRNrQXSS1H80gMaB7LFhCPIUgElvII="}]},"_npmUser":{"name":"ken0x0a","email":"ken0x0a+npm@gmail.com"},"directories":{},"maintainers":[{"name":"ken0x0a","email":"ken0x0a+npm@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/kysely-d1_1.1.0_1787340233285_0.17625905959617505"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-01T07:36:34.719Z","modified":"2026-08-21T19:23:53.682Z","1.0.1":"2026-07-01T07:36:35.088Z","1.1.0":"2026-08-21T19:23:53.470Z"},"license":"MIT","homepage":"https://github.com/ken0x0a/kysely-d1-impl","maintainers":[{"name":"ken0x0a","email":"ken0x0a+npm@gmail.com"}],"readme":"# @8x/kysely-d1\n\n## Install\n\n```sh\nbun add @8x/kysely-d1\n```\n\n## Usage\n\n### Create a Kysely instance (recommended)\n\n`initD1Kysely` takes a `D1Database` and returns a `Kysely` instance configured with the D1 dialect.\n\n```ts\nimport { initD1Kysely } from \"@8x/kysely-d1\";\n\n// Example database type\ntype Database = {\n\tComment: {\n\t\tid: number;\n\t\tcontent: string;\n\t\tisHidden: 0 | 1;\n\t};\n};\n\nexport default {\n\tasync fetch(_req: Request, env: { DB: D1Database }) {\n\t\tconst db = initD1Kysely<Database>(env.DB);\n\n\t\tconst comments = await db.selectFrom(\"Comment\").selectAll().execute();\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"Comment\")\n\t\t\t.select([\"Comment.content\", \"id\", \"isHidden as is\"])\n\t\t\t.execute();\n\n\t\treturn Response.json({ comments, rows });\n\t},\n};\n```\n\n### Create manually with `D1SQLiteDialect`\n\nIf you prefer to construct `Kysely` yourself, you can use `D1SQLiteDialect` directly.\n\n```ts\nimport { Kysely } from \"kysely\";\nimport { D1SQLiteDialect } from \"@8x/kysely-d1\";\n\nconst db = new Kysely<Database>({\n\tdialect: new D1SQLiteDialect(env.DB),\n});\n```\n\n### Run batch (atomic on D1)\n\n`runBatch(db, queries)` executes an array of Kysely queries (`Compilable`) using D1's `db.batch()`.\nPer D1 behavior, a batch is executed atomically; if any statement fails, the batch is rolled back.\n\n```ts\nimport { runBatch } from \"@8x/kysely-d1\";\n\nconst result = await runBatch(env.DB, [\n\tdb.insertInto(\"Person\").values({ first_name: \"John\" }),\n\tdb.insertInto(\"Pet\").values({ name: \"Fido\", owner_id: 1 }),\n\tdb.selectFrom(\"Person\").selectAll(),\n]);\n\nconsole.log(result[0].meta.last_row_id); // insert id of the first query\nconsole.log(result[1].meta.last_row_id); // insert id of the second query\nconsole.log(result[2].results); // rows returned by the third (select) query\n```\n\nFor `insert` / `update` / `delete` queries without `returning()`, D1 returns an empty\n`results` array (typed as `never[]`); the affected-row info lives in `meta`\n(`last_row_id`, `changes`). Add `returning()` / `returningAll()` to get typed rows back.\n\n#### Chaining a generated id into a later statement in the same batch\n\nAll queries passed to `runBatch` are compiled up front, before any of them run, so a later\nstatement can't reference the JS value of an id generated earlier in the same call (it doesn't\nexist yet at compile time — unlike calling `runBatch` / `.execute()` separately per statement and\nthreading the returned id through your own code).\n\nSQLite's `last_insert_rowid()`, via Kysely's `sql` tag, is evaluated while the batch runs instead:\n\n```ts\nimport { sql } from \"kysely\";\nimport { runBatch } from \"@8x/kysely-d1\";\n\nconst result = await runBatch(env.DB, [\n\tdb.insertInto(\"Person\").values({ first_name: \"John\" }),\n\t// No need to know Person's new id ahead of time — `last_insert_rowid()` resolves\n\t// to it once the batch actually runs.\n\tdb.insertInto(\"Pet\").values({\n\t\tname: \"Fido\",\n\t\towner_id: sql<number>`last_insert_rowid()`,\n\t}),\n]);\n\nconsole.log(result[0].meta.last_row_id); // Person's generated id — but see the caveats\n```\n\n**Read the caveats before using this.** `last_insert_rowid()` does not mean \"the id from the\nprevious statement\". It returns *the rowid of the most recent successful INSERT into a rowid table\non the database connection*, so it is only safe for the narrow shape above: a parent INSERT that\nactually inserts exactly one row into a table whose primary key is `INTEGER PRIMARY KEY`, followed\nby a single child statement inserting a single row, with no other insert in between. Each of the\nfollowing yields a wrong\nid — usually silently, because a wrong rowid is often still a valid parent id, though it surfaces\nas a foreign key error when it happens not to be:\n\n- **More than one child statement.** The second child reads back the *first child's* rowid, not the\n  parent's, because that insert became the most recent one. If that rowid happens to be a valid\n  parent id, the foreign key still holds and the row commits against the wrong parent.\n- **A multi-row parent insert.** `values([a, b, c])` — or `INSERT ... SELECT` — leaves\n  `last_insert_rowid()` pointing at `c`, not at `a`.\n- **A multi-row child insert.** The value is re-evaluated per row, so rows after the first read\n  back the child's own rowids.\n- **A parent that inserts nothing** (`on conflict do nothing`, or an ignored `insert or ignore`).\n  The value stays at whatever it was before.\n- **A parent `on conflict do update` that takes the UPDATE path.** `meta.changes` is `1`, but no\n  row was inserted, so the value is stale. Checking `meta.changes >= 1` does **not** make this\n  safe — the same known limitation that `insertId` carries for upserts (see `docs/ROADMAP.md`).\n- **A parent whose id column is not `INTEGER PRIMARY KEY`.** For a `TEXT PRIMARY KEY` (uuid) or a\n  plain `INT PRIMARY KEY`, you get the table's hidden rowid, which is unrelated to the key you\n  wanted; for a `WITHOUT ROWID` table the value is not updated at all.\n- **Referencing it before any insert in the batch.** The value comes from the connection's history,\n  not from the batch, and is `0` only on a fresh connection. An insert counts towards it even if\n  its batch later rolled back.\n\nNon-INSERT statements in between (`SELECT` / `UPDATE` / `DELETE`) do not disturb the value, but\nthey do not refresh it either.\n\nIf your case doesn't fit the narrow safe shape, use `returning()` and split the work into separate\nround trips, or make the relationship expressible without the generated id (e.g. a natural key).\n\nOne more caveat: this relies on all statements in a batch running on the same connection. D1\ndocuments that a batch's statements execute sequentially, non-concurrently, as a single\ntransaction, but it does not explicitly document connection identity — and the test for this is\nrun against local (Miniflare) D1.\n\n### Notes / Limitations\n\n- Kysely `transaction()` is not supported because D1 does not support SQL transaction statements like `BEGIN`, `COMMIT`, and `ROLLBACK`.\n- `streamQuery` is not supported.\n- The package is published as compiled ESM: `dist/*.js` plus `dist/*.d.ts`, with `.` as the only\n  entry point. `src/` ships alongside it so that source maps and \"go to definition\" land on the\n  original TypeScript, but it is not importable — resolve through the package name.\n- ESM only. There is no CommonJS build — Workers, wrangler, Vite and Bun are all ESM. Node\n  versions that support `require(esm)` (20.19+ / 22.12+) can still `require()` it; older ones\n  cannot.\n- `D1Database` / `D1Result` are referenced as ambient globals, not imported. You get them from\n  `wrangler types` (`worker-configuration.d.ts`), which is the usual Workers setup, or from\n  `@cloudflare/workers-types` — but that package is not under `@types/`, so installing it is not\n  enough: add `\"types\": [\"@cloudflare/workers-types\"]` to your `tsconfig.json`. It is declared as\n  an *optional* peer dependency to record that requirement; you do not need to install it if\n  `wrangler types` already gives you the globals. (Do not use both — their declarations collide.)\n- D1 reports failures by rejecting, so a failed statement rejects out of `execute()` rather than\n  coming back as an empty result set. A rejection is passed through untouched. If a D1-compatible\n  stand-in *resolves* with a failure instead, that is converted to a rejection shaped like the\n  binding's own — `D1_ERROR: <message>`, with the bare message as `cause`.\n\n## Installing from a git URL\n\nPrefer the registry. A git dependency ships no prebuilt `dist/`, so it relies on\nthe `prepare` lifecycle script to build one — and **bun blocks lifecycle scripts\nby default**, which leaves the package installed but without its entry point.\nThe install reports success; the failure surfaces later as\n`Cannot find package '@8x/kysely-d1'`.\n\n```sh\nbun pm untrusted            # shows the blocked script\nbun pm trust @8x/kysely-d1  # then reinstall\n```\n\nnpm, pnpm and yarn install devDependencies before running `prepare`, so they\nbuild it without extra steps.\n\n## Development\n\n```sh\nbun install\nbun run typecheck   # wrangler types && tsc\nbun run build       # tsc -p tsconfig.build.json -> dist/\nbun run test        # vitest run\n```\n","readmeFilename":"README.md","description":"A Kysely dialect for Cloudflare D1, with insertId support and typed atomic batches.","keywords":["kysely","cloudflare","d1","workers","sqlite","dialect"],"repository":{"type":"git","url":"git+https://github.com/ken0x0a/kysely-d1-impl.git"},"author":"ken0x0a","bugs":{"url":"https://github.com/ken0x0a/kysely-d1-impl/issues"}}