@storyshelf/db-sqlite 0.10.0-next-d1233ae → 0.10.0-next-7fcea06
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/better-sqlite3.mjs +1 -1
- package/dist/build-reference-lifecycle.mjs +1 -1
- package/dist/build-reference-lifecycle.mjs.map +1 -1
- package/dist/bun-sqlite.mjs +1 -1
- package/dist/content-uses.mjs +24 -9
- package/dist/content-uses.mjs.map +1 -1
- package/dist/d1.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/turso.mjs +1 -1
- package/package.json +2 -2
package/dist/better-sqlite3.mjs
CHANGED
|
@@ -37,7 +37,7 @@ function createBetterSqlite3Database(options) {
|
|
|
37
37
|
const adapter = createDrizzleAdapter(db, {
|
|
38
38
|
metadata: {
|
|
39
39
|
name: "better-sqlite3",
|
|
40
|
-
version: "0.10.0-next-
|
|
40
|
+
version: "0.10.0-next-7fcea06",
|
|
41
41
|
description: "better-sqlite3 database adapter (native + Drizzle)",
|
|
42
42
|
kind: "better-sqlite3",
|
|
43
43
|
category: "database"
|
|
@@ -21,7 +21,7 @@ function createBuildReferenceLifecycle(path, onContention) {
|
|
|
21
21
|
releaseBuildContent: async (projectId, buildId, hashes) => await releaseBuildContent(path, projectId, buildId, hashes),
|
|
22
22
|
markStorageDeleted: async (projectId, buildId) => await markStorageDeleted(path, projectId, buildId),
|
|
23
23
|
completeDeletion: async (projectId, buildId, removeRows) => await completeDeletion(path, projectId, buildId, removeRows),
|
|
24
|
-
recordContentUses: async (projectId, buildId, hashes) => await recordContentUses(path, projectId, buildId, hashes),
|
|
24
|
+
recordContentUses: async (projectId, buildId, hashes, staleBefore) => await recordContentUses(path, projectId, buildId, hashes, staleBefore),
|
|
25
25
|
claimContent: async (token, idleBefore, staleBefore) => await claimContent(path, token, idleBefore, staleBefore),
|
|
26
26
|
holdsContentClaim: async (hash, token) => await holdsContentClaim(path, hash, token),
|
|
27
27
|
abandonContentClaim: async (hash, token) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-reference-lifecycle.mjs","names":[],"sources":["../src/build-reference-lifecycle.ts"],"sourcesContent":["/** SQLite's write-serialized admission and irreversible deletion claim primitive. */\nimport {\n BaselineSourceIntegrityError,\n ReferenceGenerationConflict,\n type BuildReferenceLifecycle,\n type BuildReferenceOwner,\n type FencedBuildReferenceChange,\n type TxStore,\n} from \"@storyshelf/core/adapter/database\";\nimport { resolveBaselineOrigin } from \"@storyshelf/core/models\";\nimport type { Baseline, Build, Snapshot } from \"@storyshelf/core/schema\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport {\n completeDeletion,\n listDeleting,\n markStorageDeleted,\n releaseBuildContent,\n} from \"./build-deletion-steps.ts\";\nimport {\n abandonContentClaim,\n claimContent,\n finishContentCollection,\n holdsContentClaim,\n recordContentUses,\n} from \"./content-uses.ts\";\nimport { inTransaction, open, rollbackQuietly, transactionStore } from \"./lifecycle-transaction.ts\";\nimport { schema } from \"./schema/index.ts\";\n\n/** Not yet exposed by the adapter: all domain mutations and cleanup must be wired first. */\nexport function createBuildReferenceLifecycle(\n path: string,\n onContention?: () => void,\n): BuildReferenceLifecycle {\n return {\n referenceGeneration: async (owner) => await referenceGeneration(path, owner),\n replaceManyFencedReferences: async (changes, writeDomain) =>\n await replaceManyFencedReferences(path, changes, writeDomain),\n replaceFencedReferences: async (owner, generation, ids, writeDomain) =>\n await replaceFencedReferences(path, owner, generation, ids, writeDomain),\n claimDeletion: async (projectId, buildId) =>\n await claimDeletion(path, projectId, buildId, onContention),\n listDeleting: async (projectId) => await listDeleting(path, projectId),\n releaseBuildContent: async (projectId, buildId, hashes) =>\n await releaseBuildContent(path, projectId, buildId, hashes),\n markStorageDeleted: async (projectId, buildId) =>\n await markStorageDeleted(path, projectId, buildId),\n completeDeletion: async (projectId, buildId, removeRows) =>\n await completeDeletion(path, projectId, buildId, removeRows),\n recordContentUses: async (projectId, buildId, hashes) =>\n await recordContentUses(path, projectId, buildId, hashes),\n claimContent: async (token, idleBefore, staleBefore) =>\n await claimContent(path, token, idleBefore, staleBefore),\n holdsContentClaim: async (hash, token) => await holdsContentClaim(path, hash, token),\n abandonContentClaim: async (hash, token) => {\n await abandonContentClaim(path, hash, token);\n },\n finishContentCollection: async (hash, token) => {\n await finishContentCollection(path, hash, token);\n },\n };\n}\n\n/** What preparation did with one baseline. */\ntype Prepared = \"protected\" | \"unknown\" | \"unavailable\";\n\n/** Counts of {@link Prepared} outcomes: every baseline with a source or a snapshot is in exactly one. */\nexport interface PreparationResult {\n /** Baselines whose proven source build is now pinned. */\n protected: number;\n /** Baselines whose provenance cannot be proven; they stay unpinned and unknown. */\n unknown: number;\n /** Baselines whose recorded source build row is gone; the record is kept, nothing is pinned. */\n unavailable: number;\n}\n\n/**\n * Prepare known baseline pins under the same SQLite write lock as reference admission.\n * Do not enable lifecycle-aware deletion until all writers participate in this concurrency domain.\n *\n * A recorded source whose build row is missing keeps its `source_build_id` and gets no pin\n * (`unavailable`). A recorded source that is tombstoned or belongs to another project stops the\n * whole preparation with a {@link BaselineSourceIntegrityError}; the transaction rolls back.\n */\nexport async function prepareBaselineReferences(\n path: string,\n beforeCommit?: () => Promise<void>,\n): Promise<PreparationResult> {\n return await inTransaction(path, async (db) => {\n const rows = db\n .prepare(\n \"SELECT id FROM baselines WHERE source_build_id IS NOT NULL OR snapshot_id IS NOT NULL ORDER BY id\",\n )\n .all() as unknown as [string][];\n releaseOrphanedPins(db);\n const outcomes: Prepared[] = [];\n for (const [id] of rows) {\n // oxlint-disable-next-line no-await-in-loop -- one baseline at a time: a failure must stop the rest before the rollback\n outcomes.push(await prepareLegacyBaseline(db, id));\n }\n await beforeCommit?.();\n return await Promise.resolve(tally(outcomes));\n });\n}\n\n/**\n * Drop the pins of baseline owners that cannot justify one: the baseline row is gone, or it records\n * neither a source build nor a snapshot. While retention was paused (or before it existed) the\n * unfenced path could clear a baseline's provenance or delete the baseline without releasing its\n * pin, and the loop below only visits baselines that still record a source, so such a pin would\n * keep an unrelated build protected for good. A baseline that records a source is left to the loop.\n */\nfunction releaseOrphanedPins(db: DatabaseSync): void {\n const orphaned = db\n .prepare(\n `SELECT DISTINCT r.project_id, r.owner_id FROM build_references r\n WHERE r.owner_type = 'baseline' AND NOT EXISTS (\n SELECT 1 FROM baselines b WHERE b.id = r.owner_id\n AND (b.source_build_id IS NOT NULL OR b.snapshot_id IS NOT NULL))`,\n )\n .all() as unknown as [string, string][];\n for (const [projectId, id] of orphaned) {\n db.prepare(\n \"DELETE FROM build_references WHERE project_id = ? AND owner_type = 'baseline' AND owner_id = ?\",\n ).run(projectId, id);\n advanceGeneration(db, { type: \"baseline\", id, projectId });\n }\n}\n\nfunction tally(outcomes: readonly Prepared[]): PreparationResult {\n const count = (kind: Prepared): number => outcomes.filter((outcome) => outcome === kind).length;\n return {\n protected: count(\"protected\"),\n unknown: count(\"unknown\"),\n unavailable: count(\"unavailable\"),\n };\n}\n\nasync function prepareLegacyBaseline(db: DatabaseSync, id: string): Promise<Prepared> {\n const baseline = (await transactionStore(db).get(schema.baselines, id)) as unknown as Baseline;\n if (baseline.sourceBuildId && !recordedSourcePresent(db, baseline, baseline.sourceBuildId)) {\n keepRecordUnpinned(db, baseline);\n return \"unavailable\";\n }\n const source = await provenSource(db, baseline);\n if (!source) {\n setPreparedSource(db, baseline, null);\n return \"unknown\";\n }\n protectSource(db, baseline, source.id);\n return \"protected\";\n}\n\n/** Pin the proven source (never a tombstone) and record it on the baseline. */\nfunction protectSource(db: DatabaseSync, baseline: Baseline, buildId: string): void {\n pinAvailableSource(db, baseline, buildId);\n setPreparedSource(db, baseline, buildId);\n}\n\n/** The record stays (it is verified history), but nothing may pin a build for it. */\nfunction keepRecordUnpinned(db: DatabaseSync, baseline: Baseline): void {\n if (releaseReferences(db, baseline)) advanceBaselineGeneration(db, baseline);\n}\n\n/** The build that provably rendered the baseline's image, or null when its history is incomplete. */\nasync function provenSource(db: DatabaseSync, baseline: Baseline): Promise<Build | null> {\n const tx = transactionStore(db);\n return await resolveBaselineOrigin(baseline, {\n baseline: async (key) => (await tx.get(schema.baselines, key)) as unknown as Baseline | null,\n snapshot: async (key) => (await tx.get(schema.snapshots, key)) as unknown as Snapshot | null,\n build: async (key) => (await tx.get(schema.builds, key)) as unknown as Build | null,\n });\n}\n\n/**\n * Whether the build a baseline records as its source still has a row. A row in another project is\n * an integrity problem, not a missing build: the record is neither kept nor cleared.\n */\nfunction recordedSourcePresent(db: DatabaseSync, baseline: Baseline, buildId: string): boolean {\n const row = db.prepare(\"SELECT project_id FROM builds WHERE id = ?\").get(buildId) as\n | unknown[]\n | undefined;\n if (!row) return false;\n if (row[0] !== baseline.projectId) {\n throw new BaselineSourceIntegrityError(baseline.id, buildId, \"other-project\");\n }\n return true;\n}\n\nfunction setPreparedSource(db: DatabaseSync, baseline: Baseline, source: string | null): void {\n const removed = source === null && releaseReferences(db, baseline);\n if ((baseline.sourceBuildId ?? null) === source && !removed) return;\n db.prepare(\"UPDATE baselines SET source_build_id = ? WHERE id = ?\").run(source, baseline.id);\n advanceBaselineGeneration(db, baseline);\n}\n\n/** Drop every pin the baseline owns; true when there was one. The caller advances the generation. */\nfunction releaseReferences(db: DatabaseSync, baseline: Baseline): boolean {\n const removed = db\n .prepare(\n \"DELETE FROM build_references WHERE project_id = ? AND owner_type = 'baseline' AND owner_id = ?\",\n )\n .run(baseline.projectId, baseline.id).changes;\n return removed > 0;\n}\n\nfunction advanceBaselineGeneration(db: DatabaseSync, baseline: Baseline): void {\n advanceGeneration(db, { type: \"baseline\", id: baseline.id, projectId: baseline.projectId });\n}\n\n/** Pin `buildId` for the baseline, replacing a pin on any other build; a tombstoned build is never pinned. */\nfunction pinAvailableSource(db: DatabaseSync, baseline: Baseline, buildId: string): void {\n const { id, projectId } = baseline;\n const row = db\n .prepare(\"SELECT retention_state FROM builds WHERE id = ? AND project_id = ?\")\n .get(buildId, projectId) as unknown[] | undefined;\n if (row?.[0] !== \"available\") {\n throw new BaselineSourceIntegrityError(id, buildId, \"tombstoned\");\n }\n const removed = db\n .prepare(\n \"DELETE FROM build_references WHERE project_id = ? AND owner_type = 'baseline' AND owner_id = ? AND build_id <> ?\",\n )\n .run(projectId, id, buildId).changes;\n const result = db\n .prepare(\n \"INSERT OR IGNORE INTO build_references (project_id, owner_type, owner_id, build_id) VALUES (?, 'baseline', ?, ?)\",\n )\n .run(projectId, id, buildId);\n if (result.changes || removed) advanceGeneration(db, { type: \"baseline\", id, projectId });\n}\n\nasync function referenceGeneration(path: string, owner: BuildReferenceOwner): Promise<number> {\n return await inTransaction(path, async (db) => {\n const row = db\n .prepare(\n \"SELECT generation FROM build_reference_generations WHERE project_id = ? AND owner_type = ? AND owner_id = ?\",\n )\n .get(owner.projectId, owner.type, owner.id) as unknown[] | undefined;\n return await Promise.resolve(Number(row?.[0] ?? 0));\n });\n}\n\n/** Serialize every owner check and write with deletion claims in the SQLite write transaction. */\nexport async function replaceManyFencedReferences<T>(\n path: string,\n changes: readonly FencedBuildReferenceChange[],\n writeDomain: (tx: TxStore) => Promise<T>,\n): Promise<{ result: T; generations: number[] }> {\n validateFencedChanges(changes);\n return await inTransaction(path, async (db) => {\n const normalized = changes.map((change) => ({\n ...change,\n ids: [...new Set(change.buildIds)].toSorted(),\n }));\n checkGenerations(db, normalized);\n const result = await writeDomain(transactionStore(db));\n for (const change of normalized) {\n saveReferences(db, change.owner, change.ids);\n advanceGeneration(db, change.owner);\n }\n return { result, generations: normalized.map((change) => change.expectedGeneration + 1) };\n });\n}\n\nfunction validateFencedChanges(changes: readonly FencedBuildReferenceChange[]): void {\n const keys = changes.map(({ owner }) => JSON.stringify([owner.projectId, owner.type, owner.id]));\n if (new Set(keys).size !== keys.length) throw new Error(\"Duplicate reference owner\");\n for (const { expectedGeneration } of changes)\n if (\n !Number.isSafeInteger(expectedGeneration) ||\n expectedGeneration < 0 ||\n expectedGeneration >= Number.MAX_SAFE_INTEGER\n )\n throw new Error(\"Invalid reference generation\");\n}\n\nfunction checkGenerations(\n db: DatabaseSync,\n changes: readonly (FencedBuildReferenceChange & { ids: string[] })[],\n): void {\n const select = db.prepare(\n \"SELECT generation FROM build_reference_generations WHERE project_id = ? AND owner_type = ? AND owner_id = ?\",\n );\n for (const change of changes) {\n const row = select.get(change.owner.projectId, change.owner.type, change.owner.id) as\n | unknown[]\n | undefined;\n if ((row?.[0] ?? 0) !== change.expectedGeneration)\n throw new ReferenceGenerationConflict(\"Stale reference generation\");\n checkAvailable(db, change.owner.projectId, change.ids);\n }\n}\n\n/** Generation-checked owner update; the retained generation fences stale releases and retries. */\nexport async function replaceFencedReferences<T>(\n path: string,\n owner: BuildReferenceOwner,\n expectedGeneration: number,\n buildIds: readonly string[],\n writeDomain: (tx: TxStore) => Promise<T>,\n): Promise<{ result: T; generation: number }> {\n const updated = await replaceManyFencedReferences(\n path,\n [{ owner, expectedGeneration, buildIds }],\n writeDomain,\n );\n return { result: updated.result, generation: expectedGeneration + 1 };\n}\n\n/** Every reference mutation advances the owner's fence. */\nfunction advanceGeneration(db: DatabaseSync, owner: BuildReferenceOwner): void {\n db.prepare(\n \"INSERT INTO build_reference_generations (project_id, owner_type, owner_id, generation) VALUES (?, ?, ?, 1) ON CONFLICT (project_id, owner_type, owner_id) DO UPDATE SET generation = generation + 1\",\n ).run(owner.projectId, owner.type, owner.id);\n}\n\nfunction saveReferences(db: DatabaseSync, owner: BuildReferenceOwner, ids: string[]): void {\n db.prepare(\n \"DELETE FROM build_references WHERE project_id = ? AND owner_type = ? AND owner_id = ?\",\n ).run(owner.projectId, owner.type, owner.id);\n const insert = db.prepare(\n \"INSERT INTO build_references (project_id, owner_type, owner_id, build_id) VALUES (?, ?, ?, ?)\",\n );\n for (const id of ids) insert.run(owner.projectId, owner.type, owner.id, id);\n}\n\nfunction checkAvailable(db: DatabaseSync, projectId: string, ids: string[]): void {\n const select = db.prepare(\"SELECT retention_state FROM builds WHERE project_id = ? AND id = ?\");\n for (const id of ids) {\n const row = select.get(projectId, id) as unknown[] | undefined;\n if (row?.[0] !== \"available\") throw new Error(\"Referenced build is unavailable\");\n }\n}\n\nasync function claimDeletion(\n path: string,\n projectId: string,\n buildId: string,\n onContention?: () => void,\n): Promise<\"claimed\" | \"protected\" | \"deleting\" | \"missing\"> {\n const db = await open(path, onContention);\n try {\n const build = db\n .prepare(\"SELECT retention_state FROM builds WHERE project_id = ? AND id = ?\")\n .get(projectId, buildId) as unknown[] | undefined;\n const result = claim(db, projectId, buildId, build);\n db.exec(\"COMMIT\");\n return result;\n } catch (error) {\n rollbackQuietly(db);\n throw error;\n } finally {\n db.close();\n }\n}\n\nfunction claim(\n db: DatabaseSync,\n projectId: string,\n buildId: string,\n build: unknown[] | undefined,\n): \"claimed\" | \"protected\" | \"deleting\" | \"missing\" {\n if (!build) return \"missing\";\n if (build[0] !== \"available\") return \"deleting\";\n if (db.prepare(\"SELECT 1 FROM build_references WHERE build_id = ? LIMIT 1\").get(buildId))\n return \"protected\";\n db.prepare(\"UPDATE builds SET retention_state = 'deleting' WHERE project_id = ? AND id = ?\").run(\n projectId,\n buildId,\n );\n return \"claimed\";\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAgB,8BACd,MACA,cACyB;CACzB,OAAO;EACL,qBAAqB,OAAO,UAAU,MAAM,oBAAoB,MAAM,KAAK;EAC3E,6BAA6B,OAAO,SAAS,gBAC3C,MAAM,4BAA4B,MAAM,SAAS,WAAW;EAC9D,yBAAyB,OAAO,OAAO,YAAY,KAAK,gBACtD,MAAM,wBAAwB,MAAM,OAAO,YAAY,KAAK,WAAW;EACzE,eAAe,OAAO,WAAW,YAC/B,MAAM,cAAc,MAAM,WAAW,SAAS,YAAY;EAC5D,cAAc,OAAO,cAAc,MAAM,aAAa,MAAM,SAAS;EACrE,qBAAqB,OAAO,WAAW,SAAS,WAC9C,MAAM,oBAAoB,MAAM,WAAW,SAAS,MAAM;EAC5D,oBAAoB,OAAO,WAAW,YACpC,MAAM,mBAAmB,MAAM,WAAW,OAAO;EACnD,kBAAkB,OAAO,WAAW,SAAS,eAC3C,MAAM,iBAAiB,MAAM,WAAW,SAAS,UAAU;EAC7D,mBAAmB,OAAO,WAAW,SAAS,WAC5C,MAAM,kBAAkB,MAAM,WAAW,SAAS,MAAM;EAC1D,cAAc,OAAO,OAAO,YAAY,gBACtC,MAAM,aAAa,MAAM,OAAO,YAAY,WAAW;EACzD,mBAAmB,OAAO,MAAM,UAAU,MAAM,kBAAkB,MAAM,MAAM,KAAK;EACnF,qBAAqB,OAAO,MAAM,UAAU;GAC1C,MAAM,oBAAoB,MAAM,MAAM,KAAK;EAC7C;EACA,yBAAyB,OAAO,MAAM,UAAU;GAC9C,MAAM,wBAAwB,MAAM,MAAM,KAAK;EACjD;CACF;AACF;;;;;;;;;AAuBA,eAAsB,0BACpB,MACA,cAC4B;CAC5B,OAAO,MAAM,cAAc,MAAM,OAAO,OAAO;EAC7C,MAAM,OAAO,GACV,QACC,mGACF,CAAC,CACA,IAAI;EACP,oBAAoB,EAAE;EACtB,MAAM,WAAuB,CAAC;EAC9B,KAAK,MAAM,CAAC,OAAO,MAEjB,SAAS,KAAK,MAAM,sBAAsB,IAAI,EAAE,CAAC;EAEnD,MAAM,eAAe;EACrB,OAAO,MAAM,QAAQ,QAAQ,MAAM,QAAQ,CAAC;CAC9C,CAAC;AACH;;;;;;;;AASA,SAAS,oBAAoB,IAAwB;CACnD,MAAM,WAAW,GACd,QACC;;;6EAIF,CAAC,CACA,IAAI;CACP,KAAK,MAAM,CAAC,WAAW,OAAO,UAAU;EACtC,GAAG,QACD,gGACF,CAAC,CAAC,IAAI,WAAW,EAAE;EACnB,kBAAkB,IAAI;GAAE,MAAM;GAAY;GAAI;EAAU,CAAC;CAC3D;AACF;AAEA,SAAS,MAAM,UAAkD;CAC/D,MAAM,SAAS,SAA2B,SAAS,QAAQ,YAAY,YAAY,IAAI,CAAC,CAAC;CACzF,OAAO;EACL,WAAW,MAAM,WAAW;EAC5B,SAAS,MAAM,SAAS;EACxB,aAAa,MAAM,aAAa;CAClC;AACF;AAEA,eAAe,sBAAsB,IAAkB,IAA+B;CACpF,MAAM,WAAY,MAAM,iBAAiB,EAAE,CAAC,CAAC,IAAI,OAAO,WAAW,EAAE;CACrE,IAAI,SAAS,iBAAiB,CAAC,sBAAsB,IAAI,UAAU,SAAS,aAAa,GAAG;EAC1F,mBAAmB,IAAI,QAAQ;EAC/B,OAAO;CACT;CACA,MAAM,SAAS,MAAM,aAAa,IAAI,QAAQ;CAC9C,IAAI,CAAC,QAAQ;EACX,kBAAkB,IAAI,UAAU,IAAI;EACpC,OAAO;CACT;CACA,cAAc,IAAI,UAAU,OAAO,EAAE;CACrC,OAAO;AACT;;AAGA,SAAS,cAAc,IAAkB,UAAoB,SAAuB;CAClF,mBAAmB,IAAI,UAAU,OAAO;CACxC,kBAAkB,IAAI,UAAU,OAAO;AACzC;;AAGA,SAAS,mBAAmB,IAAkB,UAA0B;CACtE,IAAI,kBAAkB,IAAI,QAAQ,GAAG,0BAA0B,IAAI,QAAQ;AAC7E;;AAGA,eAAe,aAAa,IAAkB,UAA2C;CACvF,MAAM,KAAK,iBAAiB,EAAE;CAC9B,OAAO,MAAM,sBAAsB,UAAU;EAC3C,UAAU,OAAO,QAAS,MAAM,GAAG,IAAI,OAAO,WAAW,GAAG;EAC5D,UAAU,OAAO,QAAS,MAAM,GAAG,IAAI,OAAO,WAAW,GAAG;EAC5D,OAAO,OAAO,QAAS,MAAM,GAAG,IAAI,OAAO,QAAQ,GAAG;CACxD,CAAC;AACH;;;;;AAMA,SAAS,sBAAsB,IAAkB,UAAoB,SAA0B;CAC7F,MAAM,MAAM,GAAG,QAAQ,4CAA4C,CAAC,CAAC,IAAI,OAAO;CAGhF,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,IAAI,OAAO,SAAS,WACtB,MAAM,IAAI,6BAA6B,SAAS,IAAI,SAAS,eAAe;CAE9E,OAAO;AACT;AAEA,SAAS,kBAAkB,IAAkB,UAAoB,QAA6B;CAC5F,MAAM,UAAU,WAAW,QAAQ,kBAAkB,IAAI,QAAQ;CACjE,KAAK,SAAS,iBAAiB,UAAU,UAAU,CAAC,SAAS;CAC7D,GAAG,QAAQ,uDAAuD,CAAC,CAAC,IAAI,QAAQ,SAAS,EAAE;CAC3F,0BAA0B,IAAI,QAAQ;AACxC;;AAGA,SAAS,kBAAkB,IAAkB,UAA6B;CAMxE,OALgB,GACb,QACC,gGACF,CAAC,CACA,IAAI,SAAS,WAAW,SAAS,EAAE,CAAC,CAAC,UACvB;AACnB;AAEA,SAAS,0BAA0B,IAAkB,UAA0B;CAC7E,kBAAkB,IAAI;EAAE,MAAM;EAAY,IAAI,SAAS;EAAI,WAAW,SAAS;CAAU,CAAC;AAC5F;;AAGA,SAAS,mBAAmB,IAAkB,UAAoB,SAAuB;CACvF,MAAM,EAAE,IAAI,cAAc;CAI1B,IAHY,GACT,QAAQ,oEAAoE,CAAC,CAC7E,IAAI,SAAS,SACV,CAAC,GAAG,OAAO,aACf,MAAM,IAAI,6BAA6B,IAAI,SAAS,YAAY;CAElE,MAAM,UAAU,GACb,QACC,kHACF,CAAC,CACA,IAAI,WAAW,IAAI,OAAO,CAAC,CAAC;CAM/B,IALe,GACZ,QACC,kHACF,CAAC,CACA,IAAI,WAAW,IAAI,OACb,CAAC,CAAC,WAAW,SAAS,kBAAkB,IAAI;EAAE,MAAM;EAAY;EAAI;CAAU,CAAC;AAC1F;AAEA,eAAe,oBAAoB,MAAc,OAA6C;CAC5F,OAAO,MAAM,cAAc,MAAM,OAAO,OAAO;EAC7C,MAAM,MAAM,GACT,QACC,6GACF,CAAC,CACA,IAAI,MAAM,WAAW,MAAM,MAAM,MAAM,EAAE;EAC5C,OAAO,MAAM,QAAQ,QAAQ,OAAO,MAAM,MAAM,CAAC,CAAC;CACpD,CAAC;AACH;;AAGA,eAAsB,4BACpB,MACA,SACA,aAC+C;CAC/C,sBAAsB,OAAO;CAC7B,OAAO,MAAM,cAAc,MAAM,OAAO,OAAO;EAC7C,MAAM,aAAa,QAAQ,KAAK,YAAY;GAC1C,GAAG;GACH,KAAK,CAAC,GAAG,IAAI,IAAI,OAAO,QAAQ,CAAC,CAAC,CAAC,SAAS;EAC9C,EAAE;EACF,iBAAiB,IAAI,UAAU;EAC/B,MAAM,SAAS,MAAM,YAAY,iBAAiB,EAAE,CAAC;EACrD,KAAK,MAAM,UAAU,YAAY;GAC/B,eAAe,IAAI,OAAO,OAAO,OAAO,GAAG;GAC3C,kBAAkB,IAAI,OAAO,KAAK;EACpC;EACA,OAAO;GAAE;GAAQ,aAAa,WAAW,KAAK,WAAW,OAAO,qBAAqB,CAAC;EAAE;CAC1F,CAAC;AACH;AAEA,SAAS,sBAAsB,SAAsD;CACnF,MAAM,OAAO,QAAQ,KAAK,EAAE,YAAY,KAAK,UAAU;EAAC,MAAM;EAAW,MAAM;EAAM,MAAM;CAAE,CAAC,CAAC;CAC/F,IAAI,IAAI,IAAI,IAAI,CAAC,CAAC,SAAS,KAAK,QAAQ,MAAM,IAAI,MAAM,2BAA2B;CACnF,KAAK,MAAM,EAAE,wBAAwB,SACnC,IACE,CAAC,OAAO,cAAc,kBAAkB,KACxC,qBAAqB,KACrB,sBAAsB,OAAO,kBAE7B,MAAM,IAAI,MAAM,8BAA8B;AACpD;AAEA,SAAS,iBACP,IACA,SACM;CACN,MAAM,SAAS,GAAG,QAChB,6GACF;CACA,KAAK,MAAM,UAAU,SAAS;EAI5B,KAHY,OAAO,IAAI,OAAO,MAAM,WAAW,OAAO,MAAM,MAAM,OAAO,MAAM,EAGxE,CAAC,GAAG,MAAM,OAAO,OAAO,oBAC7B,MAAM,IAAI,4BAA4B,4BAA4B;EACpE,eAAe,IAAI,OAAO,MAAM,WAAW,OAAO,GAAG;CACvD;AACF;;AAGA,eAAsB,wBACpB,MACA,OACA,oBACA,UACA,aAC4C;CAM5C,OAAO;EAAE,SAAQ,MALK,4BACpB,MACA,CAAC;GAAE;GAAO;GAAoB;EAAS,CAAC,GACxC,WACF,EAAA,CACyB;EAAQ,YAAY,qBAAqB;CAAE;AACtE;;AAGA,SAAS,kBAAkB,IAAkB,OAAkC;CAC7E,GAAG,QACD,qMACF,CAAC,CAAC,IAAI,MAAM,WAAW,MAAM,MAAM,MAAM,EAAE;AAC7C;AAEA,SAAS,eAAe,IAAkB,OAA4B,KAAqB;CACzF,GAAG,QACD,uFACF,CAAC,CAAC,IAAI,MAAM,WAAW,MAAM,MAAM,MAAM,EAAE;CAC3C,MAAM,SAAS,GAAG,QAChB,+FACF;CACA,KAAK,MAAM,MAAM,KAAK,OAAO,IAAI,MAAM,WAAW,MAAM,MAAM,MAAM,IAAI,EAAE;AAC5E;AAEA,SAAS,eAAe,IAAkB,WAAmB,KAAqB;CAChF,MAAM,SAAS,GAAG,QAAQ,oEAAoE;CAC9F,KAAK,MAAM,MAAM,KAEf,IADY,OAAO,IAAI,WAAW,EAC5B,CAAC,GAAG,OAAO,aAAa,MAAM,IAAI,MAAM,iCAAiC;AAEnF;AAEA,eAAe,cACb,MACA,WACA,SACA,cAC2D;CAC3D,MAAM,KAAK,MAAM,KAAK,MAAM,YAAY;CACxC,IAAI;EAIF,MAAM,SAAS,MAAM,IAAI,WAAW,SAHtB,GACX,QAAQ,oEAAoE,CAAC,CAC7E,IAAI,WAAW,OAC+B,CAAC;EAClD,GAAG,KAAK,QAAQ;EAChB,OAAO;CACT,SAAS,OAAO;EACd,gBAAgB,EAAE;EAClB,MAAM;CACR,UAAU;EACR,GAAG,MAAM;CACX;AACF;AAEA,SAAS,MACP,IACA,WACA,SACA,OACkD;CAClD,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI,MAAM,OAAO,aAAa,OAAO;CACrC,IAAI,GAAG,QAAQ,2DAA2D,CAAC,CAAC,IAAI,OAAO,GACrF,OAAO;CACT,GAAG,QAAQ,gFAAgF,CAAC,CAAC,IAC3F,WACA,OACF;CACA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"build-reference-lifecycle.mjs","names":[],"sources":["../src/build-reference-lifecycle.ts"],"sourcesContent":["/** SQLite's write-serialized admission and irreversible deletion claim primitive. */\nimport {\n BaselineSourceIntegrityError,\n ReferenceGenerationConflict,\n type BuildReferenceLifecycle,\n type BuildReferenceOwner,\n type FencedBuildReferenceChange,\n type TxStore,\n} from \"@storyshelf/core/adapter/database\";\nimport { resolveBaselineOrigin } from \"@storyshelf/core/models\";\nimport type { Baseline, Build, Snapshot } from \"@storyshelf/core/schema\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport {\n completeDeletion,\n listDeleting,\n markStorageDeleted,\n releaseBuildContent,\n} from \"./build-deletion-steps.ts\";\nimport {\n abandonContentClaim,\n claimContent,\n finishContentCollection,\n holdsContentClaim,\n recordContentUses,\n} from \"./content-uses.ts\";\nimport { inTransaction, open, rollbackQuietly, transactionStore } from \"./lifecycle-transaction.ts\";\nimport { schema } from \"./schema/index.ts\";\n\n/** Not yet exposed by the adapter: all domain mutations and cleanup must be wired first. */\nexport function createBuildReferenceLifecycle(\n path: string,\n onContention?: () => void,\n): BuildReferenceLifecycle {\n return {\n referenceGeneration: async (owner) => await referenceGeneration(path, owner),\n replaceManyFencedReferences: async (changes, writeDomain) =>\n await replaceManyFencedReferences(path, changes, writeDomain),\n replaceFencedReferences: async (owner, generation, ids, writeDomain) =>\n await replaceFencedReferences(path, owner, generation, ids, writeDomain),\n claimDeletion: async (projectId, buildId) =>\n await claimDeletion(path, projectId, buildId, onContention),\n listDeleting: async (projectId) => await listDeleting(path, projectId),\n releaseBuildContent: async (projectId, buildId, hashes) =>\n await releaseBuildContent(path, projectId, buildId, hashes),\n markStorageDeleted: async (projectId, buildId) =>\n await markStorageDeleted(path, projectId, buildId),\n completeDeletion: async (projectId, buildId, removeRows) =>\n await completeDeletion(path, projectId, buildId, removeRows),\n recordContentUses: async (projectId, buildId, hashes, staleBefore) =>\n await recordContentUses(path, projectId, buildId, hashes, staleBefore),\n claimContent: async (token, idleBefore, staleBefore) =>\n await claimContent(path, token, idleBefore, staleBefore),\n holdsContentClaim: async (hash, token) => await holdsContentClaim(path, hash, token),\n abandonContentClaim: async (hash, token) => {\n await abandonContentClaim(path, hash, token);\n },\n finishContentCollection: async (hash, token) => {\n await finishContentCollection(path, hash, token);\n },\n };\n}\n\n/** What preparation did with one baseline. */\ntype Prepared = \"protected\" | \"unknown\" | \"unavailable\";\n\n/** Counts of {@link Prepared} outcomes: every baseline with a source or a snapshot is in exactly one. */\nexport interface PreparationResult {\n /** Baselines whose proven source build is now pinned. */\n protected: number;\n /** Baselines whose provenance cannot be proven; they stay unpinned and unknown. */\n unknown: number;\n /** Baselines whose recorded source build row is gone; the record is kept, nothing is pinned. */\n unavailable: number;\n}\n\n/**\n * Prepare known baseline pins under the same SQLite write lock as reference admission.\n * Do not enable lifecycle-aware deletion until all writers participate in this concurrency domain.\n *\n * A recorded source whose build row is missing keeps its `source_build_id` and gets no pin\n * (`unavailable`). A recorded source that is tombstoned or belongs to another project stops the\n * whole preparation with a {@link BaselineSourceIntegrityError}; the transaction rolls back.\n */\nexport async function prepareBaselineReferences(\n path: string,\n beforeCommit?: () => Promise<void>,\n): Promise<PreparationResult> {\n return await inTransaction(path, async (db) => {\n const rows = db\n .prepare(\n \"SELECT id FROM baselines WHERE source_build_id IS NOT NULL OR snapshot_id IS NOT NULL ORDER BY id\",\n )\n .all() as unknown as [string][];\n releaseOrphanedPins(db);\n const outcomes: Prepared[] = [];\n for (const [id] of rows) {\n // oxlint-disable-next-line no-await-in-loop -- one baseline at a time: a failure must stop the rest before the rollback\n outcomes.push(await prepareLegacyBaseline(db, id));\n }\n await beforeCommit?.();\n return await Promise.resolve(tally(outcomes));\n });\n}\n\n/**\n * Drop the pins of baseline owners that cannot justify one: the baseline row is gone, or it records\n * neither a source build nor a snapshot. While retention was paused (or before it existed) the\n * unfenced path could clear a baseline's provenance or delete the baseline without releasing its\n * pin, and the loop below only visits baselines that still record a source, so such a pin would\n * keep an unrelated build protected for good. A baseline that records a source is left to the loop.\n */\nfunction releaseOrphanedPins(db: DatabaseSync): void {\n const orphaned = db\n .prepare(\n `SELECT DISTINCT r.project_id, r.owner_id FROM build_references r\n WHERE r.owner_type = 'baseline' AND NOT EXISTS (\n SELECT 1 FROM baselines b WHERE b.id = r.owner_id\n AND (b.source_build_id IS NOT NULL OR b.snapshot_id IS NOT NULL))`,\n )\n .all() as unknown as [string, string][];\n for (const [projectId, id] of orphaned) {\n db.prepare(\n \"DELETE FROM build_references WHERE project_id = ? AND owner_type = 'baseline' AND owner_id = ?\",\n ).run(projectId, id);\n advanceGeneration(db, { type: \"baseline\", id, projectId });\n }\n}\n\nfunction tally(outcomes: readonly Prepared[]): PreparationResult {\n const count = (kind: Prepared): number => outcomes.filter((outcome) => outcome === kind).length;\n return {\n protected: count(\"protected\"),\n unknown: count(\"unknown\"),\n unavailable: count(\"unavailable\"),\n };\n}\n\nasync function prepareLegacyBaseline(db: DatabaseSync, id: string): Promise<Prepared> {\n const baseline = (await transactionStore(db).get(schema.baselines, id)) as unknown as Baseline;\n if (baseline.sourceBuildId && !recordedSourcePresent(db, baseline, baseline.sourceBuildId)) {\n keepRecordUnpinned(db, baseline);\n return \"unavailable\";\n }\n const source = await provenSource(db, baseline);\n if (!source) {\n setPreparedSource(db, baseline, null);\n return \"unknown\";\n }\n protectSource(db, baseline, source.id);\n return \"protected\";\n}\n\n/** Pin the proven source (never a tombstone) and record it on the baseline. */\nfunction protectSource(db: DatabaseSync, baseline: Baseline, buildId: string): void {\n pinAvailableSource(db, baseline, buildId);\n setPreparedSource(db, baseline, buildId);\n}\n\n/** The record stays (it is verified history), but nothing may pin a build for it. */\nfunction keepRecordUnpinned(db: DatabaseSync, baseline: Baseline): void {\n if (releaseReferences(db, baseline)) advanceBaselineGeneration(db, baseline);\n}\n\n/** The build that provably rendered the baseline's image, or null when its history is incomplete. */\nasync function provenSource(db: DatabaseSync, baseline: Baseline): Promise<Build | null> {\n const tx = transactionStore(db);\n return await resolveBaselineOrigin(baseline, {\n baseline: async (key) => (await tx.get(schema.baselines, key)) as unknown as Baseline | null,\n snapshot: async (key) => (await tx.get(schema.snapshots, key)) as unknown as Snapshot | null,\n build: async (key) => (await tx.get(schema.builds, key)) as unknown as Build | null,\n });\n}\n\n/**\n * Whether the build a baseline records as its source still has a row. A row in another project is\n * an integrity problem, not a missing build: the record is neither kept nor cleared.\n */\nfunction recordedSourcePresent(db: DatabaseSync, baseline: Baseline, buildId: string): boolean {\n const row = db.prepare(\"SELECT project_id FROM builds WHERE id = ?\").get(buildId) as\n | unknown[]\n | undefined;\n if (!row) return false;\n if (row[0] !== baseline.projectId) {\n throw new BaselineSourceIntegrityError(baseline.id, buildId, \"other-project\");\n }\n return true;\n}\n\nfunction setPreparedSource(db: DatabaseSync, baseline: Baseline, source: string | null): void {\n const removed = source === null && releaseReferences(db, baseline);\n if ((baseline.sourceBuildId ?? null) === source && !removed) return;\n db.prepare(\"UPDATE baselines SET source_build_id = ? WHERE id = ?\").run(source, baseline.id);\n advanceBaselineGeneration(db, baseline);\n}\n\n/** Drop every pin the baseline owns; true when there was one. The caller advances the generation. */\nfunction releaseReferences(db: DatabaseSync, baseline: Baseline): boolean {\n const removed = db\n .prepare(\n \"DELETE FROM build_references WHERE project_id = ? AND owner_type = 'baseline' AND owner_id = ?\",\n )\n .run(baseline.projectId, baseline.id).changes;\n return removed > 0;\n}\n\nfunction advanceBaselineGeneration(db: DatabaseSync, baseline: Baseline): void {\n advanceGeneration(db, { type: \"baseline\", id: baseline.id, projectId: baseline.projectId });\n}\n\n/** Pin `buildId` for the baseline, replacing a pin on any other build; a tombstoned build is never pinned. */\nfunction pinAvailableSource(db: DatabaseSync, baseline: Baseline, buildId: string): void {\n const { id, projectId } = baseline;\n const row = db\n .prepare(\"SELECT retention_state FROM builds WHERE id = ? AND project_id = ?\")\n .get(buildId, projectId) as unknown[] | undefined;\n if (row?.[0] !== \"available\") {\n throw new BaselineSourceIntegrityError(id, buildId, \"tombstoned\");\n }\n const removed = db\n .prepare(\n \"DELETE FROM build_references WHERE project_id = ? AND owner_type = 'baseline' AND owner_id = ? AND build_id <> ?\",\n )\n .run(projectId, id, buildId).changes;\n const result = db\n .prepare(\n \"INSERT OR IGNORE INTO build_references (project_id, owner_type, owner_id, build_id) VALUES (?, 'baseline', ?, ?)\",\n )\n .run(projectId, id, buildId);\n if (result.changes || removed) advanceGeneration(db, { type: \"baseline\", id, projectId });\n}\n\nasync function referenceGeneration(path: string, owner: BuildReferenceOwner): Promise<number> {\n return await inTransaction(path, async (db) => {\n const row = db\n .prepare(\n \"SELECT generation FROM build_reference_generations WHERE project_id = ? AND owner_type = ? AND owner_id = ?\",\n )\n .get(owner.projectId, owner.type, owner.id) as unknown[] | undefined;\n return await Promise.resolve(Number(row?.[0] ?? 0));\n });\n}\n\n/** Serialize every owner check and write with deletion claims in the SQLite write transaction. */\nexport async function replaceManyFencedReferences<T>(\n path: string,\n changes: readonly FencedBuildReferenceChange[],\n writeDomain: (tx: TxStore) => Promise<T>,\n): Promise<{ result: T; generations: number[] }> {\n validateFencedChanges(changes);\n return await inTransaction(path, async (db) => {\n const normalized = changes.map((change) => ({\n ...change,\n ids: [...new Set(change.buildIds)].toSorted(),\n }));\n checkGenerations(db, normalized);\n const result = await writeDomain(transactionStore(db));\n for (const change of normalized) {\n saveReferences(db, change.owner, change.ids);\n advanceGeneration(db, change.owner);\n }\n return { result, generations: normalized.map((change) => change.expectedGeneration + 1) };\n });\n}\n\nfunction validateFencedChanges(changes: readonly FencedBuildReferenceChange[]): void {\n const keys = changes.map(({ owner }) => JSON.stringify([owner.projectId, owner.type, owner.id]));\n if (new Set(keys).size !== keys.length) throw new Error(\"Duplicate reference owner\");\n for (const { expectedGeneration } of changes)\n if (\n !Number.isSafeInteger(expectedGeneration) ||\n expectedGeneration < 0 ||\n expectedGeneration >= Number.MAX_SAFE_INTEGER\n )\n throw new Error(\"Invalid reference generation\");\n}\n\nfunction checkGenerations(\n db: DatabaseSync,\n changes: readonly (FencedBuildReferenceChange & { ids: string[] })[],\n): void {\n const select = db.prepare(\n \"SELECT generation FROM build_reference_generations WHERE project_id = ? AND owner_type = ? AND owner_id = ?\",\n );\n for (const change of changes) {\n const row = select.get(change.owner.projectId, change.owner.type, change.owner.id) as\n | unknown[]\n | undefined;\n if ((row?.[0] ?? 0) !== change.expectedGeneration)\n throw new ReferenceGenerationConflict(\"Stale reference generation\");\n checkAvailable(db, change.owner.projectId, change.ids);\n }\n}\n\n/** Generation-checked owner update; the retained generation fences stale releases and retries. */\nexport async function replaceFencedReferences<T>(\n path: string,\n owner: BuildReferenceOwner,\n expectedGeneration: number,\n buildIds: readonly string[],\n writeDomain: (tx: TxStore) => Promise<T>,\n): Promise<{ result: T; generation: number }> {\n const updated = await replaceManyFencedReferences(\n path,\n [{ owner, expectedGeneration, buildIds }],\n writeDomain,\n );\n return { result: updated.result, generation: expectedGeneration + 1 };\n}\n\n/** Every reference mutation advances the owner's fence. */\nfunction advanceGeneration(db: DatabaseSync, owner: BuildReferenceOwner): void {\n db.prepare(\n \"INSERT INTO build_reference_generations (project_id, owner_type, owner_id, generation) VALUES (?, ?, ?, 1) ON CONFLICT (project_id, owner_type, owner_id) DO UPDATE SET generation = generation + 1\",\n ).run(owner.projectId, owner.type, owner.id);\n}\n\nfunction saveReferences(db: DatabaseSync, owner: BuildReferenceOwner, ids: string[]): void {\n db.prepare(\n \"DELETE FROM build_references WHERE project_id = ? AND owner_type = ? AND owner_id = ?\",\n ).run(owner.projectId, owner.type, owner.id);\n const insert = db.prepare(\n \"INSERT INTO build_references (project_id, owner_type, owner_id, build_id) VALUES (?, ?, ?, ?)\",\n );\n for (const id of ids) insert.run(owner.projectId, owner.type, owner.id, id);\n}\n\nfunction checkAvailable(db: DatabaseSync, projectId: string, ids: string[]): void {\n const select = db.prepare(\"SELECT retention_state FROM builds WHERE project_id = ? AND id = ?\");\n for (const id of ids) {\n const row = select.get(projectId, id) as unknown[] | undefined;\n if (row?.[0] !== \"available\") throw new Error(\"Referenced build is unavailable\");\n }\n}\n\nasync function claimDeletion(\n path: string,\n projectId: string,\n buildId: string,\n onContention?: () => void,\n): Promise<\"claimed\" | \"protected\" | \"deleting\" | \"missing\"> {\n const db = await open(path, onContention);\n try {\n const build = db\n .prepare(\"SELECT retention_state FROM builds WHERE project_id = ? AND id = ?\")\n .get(projectId, buildId) as unknown[] | undefined;\n const result = claim(db, projectId, buildId, build);\n db.exec(\"COMMIT\");\n return result;\n } catch (error) {\n rollbackQuietly(db);\n throw error;\n } finally {\n db.close();\n }\n}\n\nfunction claim(\n db: DatabaseSync,\n projectId: string,\n buildId: string,\n build: unknown[] | undefined,\n): \"claimed\" | \"protected\" | \"deleting\" | \"missing\" {\n if (!build) return \"missing\";\n if (build[0] !== \"available\") return \"deleting\";\n if (db.prepare(\"SELECT 1 FROM build_references WHERE build_id = ? LIMIT 1\").get(buildId))\n return \"protected\";\n db.prepare(\"UPDATE builds SET retention_state = 'deleting' WHERE project_id = ? AND id = ?\").run(\n projectId,\n buildId,\n );\n return \"claimed\";\n}\n"],"mappings":"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"}
|
package/dist/bun-sqlite.mjs
CHANGED
|
@@ -37,7 +37,7 @@ function createBunSqliteDatabase(options) {
|
|
|
37
37
|
const adapter = createDrizzleAdapter(db, {
|
|
38
38
|
metadata: {
|
|
39
39
|
name: "Bun SQLite",
|
|
40
|
-
version: "0.10.0-next-
|
|
40
|
+
version: "0.10.0-next-7fcea06",
|
|
41
41
|
description: "Bun SQLite database adapter (bun:sqlite + Drizzle)",
|
|
42
42
|
kind: "bun-sqlite",
|
|
43
43
|
category: "database"
|
package/dist/content-uses.mjs
CHANGED
|
@@ -3,26 +3,41 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { inTransaction } from "./lifecycle-transaction.mjs";
|
|
5
5
|
// src/content-uses.ts
|
|
6
|
-
/** Build uses of shared content and its claim for collection (ADR 0039), in SQLite's write lock. */
|
|
7
6
|
/**
|
|
8
7
|
* Record the build's use of each hash. A hash a collector claimed refuses the whole call, so a use
|
|
9
8
|
* and the collection of the same blob end in exactly one outcome: the refused hashes are returned
|
|
10
|
-
* and nothing is written. A
|
|
11
|
-
*
|
|
9
|
+
* and nothing is written. A claim taken before `staleBefore` (an ISO instant) belongs to a
|
|
10
|
+
* collector that stopped: the record clears it in the same transaction and goes ahead, and the
|
|
11
|
+
* stalled collector's `holdsContentClaim` check, just before its delete, then fails, so it deletes
|
|
12
|
+
* nothing. A build that is gone or being deleted records nothing either (every hash is refused):
|
|
13
|
+
* its content was or is being released, so a late use would pin the blobs for good.
|
|
12
14
|
*/
|
|
13
|
-
async function recordContentUses(path, projectId, buildId, hashes) {
|
|
15
|
+
async function recordContentUses(path, projectId, buildId, hashes, staleBefore) {
|
|
14
16
|
return await inTransaction(path, async (db) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const refused =
|
|
17
|
+
const distinct = [...new Set(hashes)];
|
|
18
|
+
if (db.prepare("SELECT retention_state FROM builds WHERE id = ? AND project_id = ?").get(buildId, projectId)?.[0] !== "available") return await Promise.resolve(distinct);
|
|
19
|
+
const refused = claimedHashes(db, distinct, staleBefore);
|
|
18
20
|
if (refused.length > 0) return await Promise.resolve(refused);
|
|
19
|
-
|
|
21
|
+
clearAbandonedClaims(db, distinct, staleBefore);
|
|
20
22
|
const insert = db.prepare(`INSERT OR IGNORE INTO content_uses (build_id, content_hash, project_id, created_at)
|
|
21
23
|
VALUES (?, ?, ?, ?)`);
|
|
22
|
-
|
|
24
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25
|
+
for (const hash of distinct) insert.run(buildId, hash, projectId, now);
|
|
23
26
|
return await Promise.resolve([]);
|
|
24
27
|
});
|
|
25
28
|
}
|
|
29
|
+
/** The hashes a collector holds a live claim on: any claim, or only those taken since `staleBefore`. */
|
|
30
|
+
function claimedHashes(db, hashes, staleBefore) {
|
|
31
|
+
const claimed = db.prepare("SELECT 1 FROM content_uses WHERE content_hash = ? AND collecting_at IS NOT NULL AND (? IS NULL OR collecting_at >= ?) LIMIT 1");
|
|
32
|
+
const cutoff = staleBefore ?? null;
|
|
33
|
+
return hashes.filter((hash) => claimed.get(hash, cutoff, cutoff) !== void 0);
|
|
34
|
+
}
|
|
35
|
+
/** Drop the claims on `hashes` that were taken before `staleBefore`; their uses stay released. */
|
|
36
|
+
function clearAbandonedClaims(db, hashes, staleBefore) {
|
|
37
|
+
if (staleBefore === void 0) return;
|
|
38
|
+
const clear = db.prepare("UPDATE content_uses SET collecting_at = NULL, claim_token = NULL WHERE content_hash = ? AND collecting_at IS NOT NULL AND collecting_at < ?");
|
|
39
|
+
for (const hash of hashes) clear.run(hash, staleBefore);
|
|
40
|
+
}
|
|
26
41
|
/**
|
|
27
42
|
* Claim every idle hash (see the capability) under the pass's token, taking over the hashes whose
|
|
28
43
|
* earlier claim is stale. Re-stamping with the new token fences the old collector out.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-uses.mjs","names":[],"sources":["../src/content-uses.ts"],"sourcesContent":["/** Build uses of shared content and its claim for collection (ADR 0039), in SQLite's write lock. */\nimport { inTransaction } from \"./lifecycle-transaction.ts\";\n\n/**\n * Record the build's use of each hash. A hash a collector claimed refuses the whole call, so a use\n * and the collection of the same blob end in exactly one outcome: the refused hashes are returned\n * and nothing is written. A build that is gone or being deleted records nothing either (every hash
|
|
1
|
+
{"version":3,"file":"content-uses.mjs","names":[],"sources":["../src/content-uses.ts"],"sourcesContent":["/** Build uses of shared content and its claim for collection (ADR 0039), in SQLite's write lock. */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { inTransaction } from \"./lifecycle-transaction.ts\";\n\n/**\n * Record the build's use of each hash. A hash a collector claimed refuses the whole call, so a use\n * and the collection of the same blob end in exactly one outcome: the refused hashes are returned\n * and nothing is written. A claim taken before `staleBefore` (an ISO instant) belongs to a\n * collector that stopped: the record clears it in the same transaction and goes ahead, and the\n * stalled collector's `holdsContentClaim` check, just before its delete, then fails, so it deletes\n * nothing. A build that is gone or being deleted records nothing either (every hash is refused):\n * its content was or is being released, so a late use would pin the blobs for good.\n */\nexport async function recordContentUses(\n path: string,\n projectId: string,\n buildId: string,\n hashes: readonly string[],\n staleBefore?: string,\n): Promise<string[]> {\n return await inTransaction(path, async (db) => {\n const distinct = [...new Set(hashes)];\n const build = db\n .prepare(\"SELECT retention_state FROM builds WHERE id = ? AND project_id = ?\")\n .get(buildId, projectId) as unknown as [string] | undefined;\n if (build?.[0] !== \"available\") return await Promise.resolve(distinct);\n const refused = claimedHashes(db, distinct, staleBefore);\n if (refused.length > 0) return await Promise.resolve(refused);\n clearAbandonedClaims(db, distinct, staleBefore);\n const insert = db.prepare(\n `INSERT OR IGNORE INTO content_uses (build_id, content_hash, project_id, created_at)\n VALUES (?, ?, ?, ?)`,\n );\n const now = new Date().toISOString();\n for (const hash of distinct) insert.run(buildId, hash, projectId, now);\n return await Promise.resolve([]);\n });\n}\n\n/** The hashes a collector holds a live claim on: any claim, or only those taken since `staleBefore`. */\nfunction claimedHashes(\n db: DatabaseSync,\n hashes: readonly string[],\n staleBefore: string | undefined,\n): string[] {\n const claimed = db.prepare(\n \"SELECT 1 FROM content_uses WHERE content_hash = ? AND collecting_at IS NOT NULL AND (? IS NULL OR collecting_at >= ?) LIMIT 1\",\n );\n const cutoff = staleBefore ?? null;\n return hashes.filter((hash) => claimed.get(hash, cutoff, cutoff) !== undefined);\n}\n\n/** Drop the claims on `hashes` that were taken before `staleBefore`; their uses stay released. */\nfunction clearAbandonedClaims(\n db: DatabaseSync,\n hashes: readonly string[],\n staleBefore: string | undefined,\n): void {\n if (staleBefore === undefined) return;\n const clear = db.prepare(\n \"UPDATE content_uses SET collecting_at = NULL, claim_token = NULL WHERE content_hash = ? AND collecting_at IS NOT NULL AND collecting_at < ?\",\n );\n for (const hash of hashes) clear.run(hash, staleBefore);\n}\n\n/**\n * Claim every idle hash (see the capability) under the pass's token, taking over the hashes whose\n * earlier claim is stale. Re-stamping with the new token fences the old collector out.\n */\nexport async function claimContent(\n path: string,\n token: string,\n idleBefore: string,\n staleBefore: string,\n): Promise<string[]> {\n return await inTransaction(path, async (db) => {\n const now = new Date().toISOString();\n const stale = db\n .prepare(\n \"SELECT DISTINCT content_hash FROM content_uses WHERE collecting_at IS NOT NULL AND collecting_at < ?\",\n )\n .all(staleBefore) as unknown as [string][];\n const idle = db\n .prepare(\n `SELECT content_hash FROM content_uses GROUP BY content_hash\n HAVING SUM(released_at IS NULL) = 0\n AND SUM(collecting_at IS NOT NULL) = 0\n AND MAX(released_at) < ?`,\n )\n .all(idleBefore) as unknown as [string][];\n const claim = db.prepare(\n \"UPDATE content_uses SET collecting_at = ?, claim_token = ? WHERE content_hash = ?\",\n );\n const hashes = [...stale, ...idle].map(([hash]) => hash);\n for (const hash of hashes) claim.run(now, token, hash);\n return await Promise.resolve(hashes);\n });\n}\n\n/** Whether the token still holds the hash's claim and no use of it is unreleased. */\nexport async function holdsContentClaim(\n path: string,\n hash: string,\n token: string,\n): Promise<boolean> {\n return await inTransaction(path, async (db) => {\n const row = db\n .prepare(\n `SELECT SUM(claim_token = ?), SUM(released_at IS NULL)\n FROM content_uses WHERE content_hash = ?`,\n )\n .get(token, hash) as unknown as [number | null, number | null];\n const [held, open] = row;\n return await Promise.resolve((held ?? 0) > 0 && (open ?? 0) === 0);\n });\n}\n\n/** Clear the claim of a hash if the token holds it; its uses stay released. */\nexport async function abandonContentClaim(\n path: string,\n hash: string,\n token: string,\n): Promise<void> {\n await inTransaction(path, async (db) => {\n db.prepare(\n \"UPDATE content_uses SET collecting_at = NULL, claim_token = NULL WHERE content_hash = ? AND claim_token = ?\",\n ).run(hash, token);\n await Promise.resolve();\n });\n}\n\n/** Drop the rows of a collected hash, if the token still holds its claim. */\nexport async function finishContentCollection(\n path: string,\n hash: string,\n token: string,\n): Promise<void> {\n await inTransaction(path, async (db) => {\n db.prepare(\"DELETE FROM content_uses WHERE content_hash = ? AND claim_token = ?\").run(\n hash,\n token,\n );\n await Promise.resolve();\n });\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,eAAsB,kBACpB,MACA,WACA,SACA,QACA,aACmB;CACnB,OAAO,MAAM,cAAc,MAAM,OAAO,OAAO;EAC7C,MAAM,WAAW,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;EAIpC,IAHc,GACX,QAAQ,oEAAoE,CAAC,CAC7E,IAAI,SAAS,SACR,CAAC,GAAG,OAAO,aAAa,OAAO,MAAM,QAAQ,QAAQ,QAAQ;EACrE,MAAM,UAAU,cAAc,IAAI,UAAU,WAAW;EACvD,IAAI,QAAQ,SAAS,GAAG,OAAO,MAAM,QAAQ,QAAQ,OAAO;EAC5D,qBAAqB,IAAI,UAAU,WAAW;EAC9C,MAAM,SAAS,GAAG,QAChB;2BAEF;EACA,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,KAAK,MAAM,QAAQ,UAAU,OAAO,IAAI,SAAS,MAAM,WAAW,GAAG;EACrE,OAAO,MAAM,QAAQ,QAAQ,CAAC,CAAC;CACjC,CAAC;AACH;;AAGA,SAAS,cACP,IACA,QACA,aACU;CACV,MAAM,UAAU,GAAG,QACjB,+HACF;CACA,MAAM,SAAS,eAAe;CAC9B,OAAO,OAAO,QAAQ,SAAS,QAAQ,IAAI,MAAM,QAAQ,MAAM,MAAM,KAAA,CAAS;AAChF;;AAGA,SAAS,qBACP,IACA,QACA,aACM;CACN,IAAI,gBAAgB,KAAA,GAAW;CAC/B,MAAM,QAAQ,GAAG,QACf,6IACF;CACA,KAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI,MAAM,WAAW;AACxD;;;;;AAMA,eAAsB,aACpB,MACA,OACA,YACA,aACmB;CACnB,OAAO,MAAM,cAAc,MAAM,OAAO,OAAO;EAC7C,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,QAAQ,GACX,QACC,sGACF,CAAC,CACA,IAAI,WAAW;EAClB,MAAM,OAAO,GACV,QACC;;;oCAIF,CAAC,CACA,IAAI,UAAU;EACjB,MAAM,QAAQ,GAAG,QACf,mFACF;EACA,MAAM,SAAS,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,UAAU,IAAI;EACvD,KAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,OAAO,IAAI;EACrD,OAAO,MAAM,QAAQ,QAAQ,MAAM;CACrC,CAAC;AACH;;AAGA,eAAsB,kBACpB,MACA,MACA,OACkB;CAClB,OAAO,MAAM,cAAc,MAAM,OAAO,OAAO;EAO7C,MAAM,CAAC,MAAM,QAND,GACT,QACC;kDAEF,CAAC,CACA,IAAI,OAAO,IACS;EACvB,OAAO,MAAM,QAAQ,SAAS,QAAQ,KAAK,MAAM,QAAQ,OAAO,CAAC;CACnE,CAAC;AACH;;AAGA,eAAsB,oBACpB,MACA,MACA,OACe;CACf,MAAM,cAAc,MAAM,OAAO,OAAO;EACtC,GAAG,QACD,6GACF,CAAC,CAAC,IAAI,MAAM,KAAK;EACjB,MAAM,QAAQ,QAAQ;CACxB,CAAC;AACH;;AAGA,eAAsB,wBACpB,MACA,MACA,OACe;CACf,MAAM,cAAc,MAAM,OAAO,OAAO;EACtC,GAAG,QAAQ,qEAAqE,CAAC,CAAC,IAChF,MACA,KACF;EACA,MAAM,QAAQ,QAAQ;CACxB,CAAC;AACH"}
|
package/dist/d1.mjs
CHANGED
|
@@ -22,7 +22,7 @@ function createD1Database(options) {
|
|
|
22
22
|
return createDrizzleAdapter(db, {
|
|
23
23
|
metadata: {
|
|
24
24
|
name: "Cloudflare D1",
|
|
25
|
-
version: "0.10.0-next-
|
|
25
|
+
version: "0.10.0-next-7fcea06",
|
|
26
26
|
description: "Cloudflare D1 database adapter (D1 binding + Drizzle)",
|
|
27
27
|
kind: "d1",
|
|
28
28
|
category: "database",
|
package/dist/index.mjs
CHANGED
|
@@ -29,7 +29,7 @@ function createSqliteDatabase(path) {
|
|
|
29
29
|
const adapter = createDrizzleAdapter(db, {
|
|
30
30
|
metadata: {
|
|
31
31
|
name: "SQLite",
|
|
32
|
-
version: "0.10.0-next-
|
|
32
|
+
version: "0.10.0-next-7fcea06",
|
|
33
33
|
description: "SQLite database adapter (node:sqlite + Drizzle)",
|
|
34
34
|
kind: "sqlite",
|
|
35
35
|
category: "database"
|
package/dist/turso.mjs
CHANGED
|
@@ -32,7 +32,7 @@ function createTursoDatabase(options) {
|
|
|
32
32
|
return createDrizzleAdapter(db, {
|
|
33
33
|
metadata: {
|
|
34
34
|
name: "Turso",
|
|
35
|
-
version: "0.10.0-next-
|
|
35
|
+
version: "0.10.0-next-7fcea06",
|
|
36
36
|
description: "Turso/libSQL database adapter",
|
|
37
37
|
kind: "turso",
|
|
38
38
|
category: "database"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storyshelf/db-sqlite",
|
|
3
|
-
"version": "0.10.0-next-
|
|
3
|
+
"version": "0.10.0-next-7fcea06",
|
|
4
4
|
"description": "SQLite database adapter for StoryShelf (node:sqlite + Drizzle).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"database",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"prepublishOnly": "nub run build"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@storyshelf/core": "0.10.0-next-
|
|
90
|
+
"@storyshelf/core": "0.10.0-next-7fcea06",
|
|
91
91
|
"drizzle-orm": "^0.45.4"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|