@lostgradient/weft 0.2.0 → 0.3.0

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.
Files changed (207) hide show
  1. package/README.md +47 -22
  2. package/dist/cli/generated/operation-client.generated.d.ts +28 -1
  3. package/dist/cli/generated/operation-client.generated.js +2 -0
  4. package/dist/cli-main.js +79 -79
  5. package/dist/client/handle-delegation.d.ts +4 -0
  6. package/dist/client/handle-delegation.js +6 -0
  7. package/dist/client/http-client-requests.d.ts +2 -0
  8. package/dist/client/http-client-requests.js +3 -0
  9. package/dist/client/http-client.d.ts +4 -1
  10. package/dist/client/http-client.js +9 -1
  11. package/dist/client/interface.d.ts +57 -2
  12. package/dist/client/local.d.ts +4 -1
  13. package/dist/client/local.js +7 -0
  14. package/dist/client/start-body.d.ts +7 -1
  15. package/dist/client/start-body.js +13 -4
  16. package/dist/core/codec/extension-codec.js +4 -2
  17. package/dist/core/codec/index.d.ts +1 -0
  18. package/dist/core/codec/index.js +1 -0
  19. package/dist/core/codec/serializer-registry.d.ts +122 -0
  20. package/dist/core/codec/serializer-registry.js +51 -0
  21. package/dist/core/context/index.d.ts +10 -0
  22. package/dist/core/context/index.js +3 -0
  23. package/dist/core/context/internals.d.ts +10 -0
  24. package/dist/core/context/internals.js +5 -1
  25. package/dist/core/context/run-operation.d.ts +16 -3
  26. package/dist/core/context/run-operation.js +16 -7
  27. package/dist/core/context/speculative-child.js +2 -0
  28. package/dist/core/context/types.d.ts +6 -0
  29. package/dist/core/engine/bulk-operations-purge.js +1 -0
  30. package/dist/core/engine/bulk-operations.js +1 -1
  31. package/dist/core/engine/callback-creators-bundles.js +2 -1
  32. package/dist/core/engine/callback-creators-core.js +2 -1
  33. package/dist/core/engine/construction.d.ts +1 -1
  34. package/dist/core/engine/construction.js +15 -3
  35. package/dist/core/engine/disposal.js +12 -0
  36. package/dist/core/engine/engine-create-types.d.ts +0 -14
  37. package/dist/core/engine/engine-internal-types.d.ts +17 -0
  38. package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
  39. package/dist/core/engine/engine-leak-warnings.js +4 -0
  40. package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
  41. package/dist/core/engine/engine-runtime-helpers.js +26 -5
  42. package/dist/core/engine/errors.d.ts +74 -0
  43. package/dist/core/engine/errors.js +25 -1
  44. package/dist/core/engine/handle-result.js +1 -1
  45. package/dist/core/engine/handles.d.ts +89 -40
  46. package/dist/core/engine/handles.js +25 -27
  47. package/dist/core/engine/index.d.ts +122 -4
  48. package/dist/core/engine/index.js +82 -5
  49. package/dist/core/engine/inline-launch-queue.d.ts +14 -0
  50. package/dist/core/engine/inline-launch-queue.js +32 -7
  51. package/dist/core/engine/internals.d.ts +26 -10
  52. package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
  53. package/dist/core/engine/lifecycle/persist.js +5 -20
  54. package/dist/core/engine/lifecycle/recovered-services.d.ts +45 -0
  55. package/dist/core/engine/lifecycle/recovered-services.js +34 -0
  56. package/dist/core/engine/lifecycle/resume.js +33 -5
  57. package/dist/core/engine/lifecycle/shared.d.ts +8 -0
  58. package/dist/core/engine/lifecycle/start-batch.js +23 -12
  59. package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
  60. package/dist/core/engine/lifecycle/start-commit.js +27 -0
  61. package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
  62. package/dist/core/engine/lifecycle/start-exec.js +38 -0
  63. package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
  64. package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
  65. package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
  66. package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
  67. package/dist/core/engine/lifecycle/start.d.ts +3 -3
  68. package/dist/core/engine/lifecycle/start.js +42 -37
  69. package/dist/core/engine/lifecycle.d.ts +3 -2
  70. package/dist/core/engine/lifecycle.js +9 -2
  71. package/dist/core/engine/listing.js +1 -1
  72. package/dist/core/engine/operations-data.d.ts +16 -0
  73. package/dist/core/engine/operations-data.js +6 -0
  74. package/dist/core/engine/operations-time.d.ts +3 -2
  75. package/dist/core/engine/operations-time.js +6 -1
  76. package/dist/core/engine/persisted-data-version.d.ts +5 -9
  77. package/dist/core/engine/persisted-data-version.js +4 -5
  78. package/dist/core/engine/schedule-handle.d.ts +45 -0
  79. package/dist/core/engine/schedule-handle.js +26 -0
  80. package/dist/core/engine/schedules.d.ts +1 -1
  81. package/dist/core/engine/schedules.js +7 -3
  82. package/dist/core/engine/second-instance-detector.d.ts +96 -0
  83. package/dist/core/engine/second-instance-detector.js +108 -0
  84. package/dist/core/engine/signals.d.ts +22 -0
  85. package/dist/core/engine/signals.js +15 -0
  86. package/dist/core/engine/termination/cleanup.d.ts +25 -0
  87. package/dist/core/engine/termination/cleanup.js +21 -1
  88. package/dist/core/engine/termination/complete.js +4 -3
  89. package/dist/core/engine/termination/suspend.d.ts +68 -0
  90. package/dist/core/engine/termination/suspend.js +41 -0
  91. package/dist/core/engine/termination.d.ts +4 -2
  92. package/dist/core/engine/termination.js +2 -0
  93. package/dist/core/engine/validation.js +25 -1
  94. package/dist/core/engine/workflow-feed.d.ts +5 -3
  95. package/dist/core/events/event-map.d.ts +2 -1
  96. package/dist/core/events/workflow-events.d.ts +23 -0
  97. package/dist/core/events/workflow-events.js +9 -0
  98. package/dist/core/inline-execution-strategy.d.ts +5 -0
  99. package/dist/core/inline-execution-strategy.js +2 -1
  100. package/dist/core/list-filter-validation.js +2 -1
  101. package/dist/core/start-workflow-validation.d.ts +22 -0
  102. package/dist/core/start-workflow-validation.js +11 -1
  103. package/dist/core/step-context.d.ts +10 -6
  104. package/dist/core/step-context.js +7 -15
  105. package/dist/core/types/activity.d.ts +6 -3
  106. package/dist/core/types/identity.d.ts +8 -1
  107. package/dist/core/types/launch-metadata.d.ts +33 -0
  108. package/dist/core/types/launch-metadata.js +0 -0
  109. package/dist/core/types/message-handles.d.ts +25 -0
  110. package/dist/core/types/options.d.ts +90 -7
  111. package/dist/core/types/reviews.d.ts +2 -1
  112. package/dist/core/types/services-resolution.d.ts +47 -0
  113. package/dist/core/types/services-resolution.js +0 -0
  114. package/dist/core/types/state.d.ts +11 -11
  115. package/dist/core/types/workflow-builder.d.ts +5 -4
  116. package/dist/core/types/workflow-context.d.ts +25 -0
  117. package/dist/core/types/workflow-function.d.ts +17 -0
  118. package/dist/core/types/workflow-snapshot.d.ts +29 -0
  119. package/dist/core/types/workflow-snapshot.js +0 -0
  120. package/dist/core/types.d.ts +3 -0
  121. package/dist/core/types.js +3 -0
  122. package/dist/core/weft-error.d.ts +46 -14
  123. package/dist/core/weft-error.js +12 -1
  124. package/dist/diagnostics/doctor.js +6 -3
  125. package/dist/diagnostics/format.js +2 -2
  126. package/dist/diagnostics/types.d.ts +1 -0
  127. package/dist/diagnostics/version-check.js +6 -4
  128. package/dist/index.d.ts +10 -5
  129. package/dist/index.js +11 -2
  130. package/dist/json-schema.js +3 -3
  131. package/dist/mcp/cli.js +35 -35
  132. package/dist/mcp/list-filter.js +2 -1
  133. package/dist/mcp/session.js +1 -0
  134. package/dist/observability/index.js +2 -2
  135. package/dist/server/handler.js +30 -30
  136. package/dist/server/index.js +33 -33
  137. package/dist/server/interactive-operations.js +1 -0
  138. package/dist/server/operations/resume-workflow.js +2 -2
  139. package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
  140. package/dist/server/operations/start-or-signal-workflow.js +140 -0
  141. package/dist/server/operations/start-workflow-options.d.ts +32 -0
  142. package/dist/server/operations/start-workflow-options.js +63 -0
  143. package/dist/server/operations/start-workflow.js +7 -69
  144. package/dist/server/operations/suspend-workflow.d.ts +13 -0
  145. package/dist/server/operations/suspend-workflow.js +36 -0
  146. package/dist/server/rest-binding.d.ts +18 -7
  147. package/dist/server/rest-bindings.js +12 -0
  148. package/dist/server/runtime/task-dispatch.js +5 -3
  149. package/dist/server/runtime/task-polling.d.ts +16 -2
  150. package/dist/server/runtime/task-polling.js +20 -5
  151. package/dist/server/runtime/websocket-worker.js +8 -0
  152. package/dist/server/serve-internals.d.ts +8 -0
  153. package/dist/server/serve-internals.js +4 -2
  154. package/dist/server/task-state.d.ts +8 -0
  155. package/dist/service-worker/index.js +28 -28
  156. package/dist/storage/capabilities.d.ts +10 -2
  157. package/dist/storage/capabilities.js +2 -2
  158. package/dist/storage/http.js +2 -2
  159. package/dist/storage/index.d.ts +7 -1
  160. package/dist/storage/indexeddb.js +1 -1
  161. package/dist/storage/interface.d.ts +40 -0
  162. package/dist/storage/interface.js +1 -1
  163. package/dist/storage/key-prefixes.d.ts +1 -1
  164. package/dist/storage/key-prefixes.js +3 -0
  165. package/dist/storage/lmdb.js +1 -1
  166. package/dist/storage/memory.js +1 -1
  167. package/dist/storage/neon-value-mapping.d.ts +47 -0
  168. package/dist/storage/neon-value-mapping.js +11 -0
  169. package/dist/storage/neon.d.ts +108 -0
  170. package/dist/storage/neon.js +10 -0
  171. package/dist/storage/node-sqlite-loader.d.ts +71 -0
  172. package/dist/storage/node-sqlite-loader.js +41 -0
  173. package/dist/storage/node-sqlite.d.ts +1 -19
  174. package/dist/storage/node-sqlite.js +38 -32
  175. package/dist/storage/postgres-key-value-queries.d.ts +79 -0
  176. package/dist/storage/postgres-key-value-queries.js +63 -0
  177. package/dist/storage/resolve.d.ts +2 -165
  178. package/dist/storage/resolve.js +1 -1
  179. package/dist/storage/scoped-storage.js +1 -1
  180. package/dist/storage/storage-configuration.d.ts +209 -0
  181. package/dist/storage/storage-configuration.js +0 -0
  182. package/dist/storage/text-value-store.d.ts +13 -10
  183. package/dist/storage/turso.js +2 -2
  184. package/dist/storage/typed-storage.js +1 -1
  185. package/dist/storage/web-extension.js +1 -1
  186. package/dist/testing/event-loop.d.ts +36 -2
  187. package/dist/testing/index.d.ts +31 -1
  188. package/dist/testing/index.js +33 -33
  189. package/dist/version.d.ts +1 -1
  190. package/dist/version.js +1 -1
  191. package/dist/worker/index.js +9 -5
  192. package/dist/worker/long-poll.js +4 -0
  193. package/dist/worker/protocol-messages.d.ts +20 -0
  194. package/dist/worker/protocol-schemas.d.ts +32 -0
  195. package/dist/worker/protocol-schemas.js +8 -4
  196. package/dist/worker/protocol-task-result.d.ts +28 -0
  197. package/dist/worker/protocol-task-result.js +76 -0
  198. package/dist/worker/protocol.d.ts +4 -15
  199. package/dist/worker/protocol.js +1 -1
  200. package/dist/worker/registry/fair-share.d.ts +29 -0
  201. package/dist/worker/registry/fair-share.js +30 -0
  202. package/dist/worker/registry/routing.d.ts +18 -0
  203. package/dist/worker/registry/routing.js +14 -0
  204. package/dist/worker/registry/types.d.ts +7 -0
  205. package/dist/worker/registry.d.ts +16 -1
  206. package/dist/worker/registry.js +24 -36
  207. package/package.json +17 -4
@@ -0,0 +1,108 @@
1
+ import { type DeleteRangeOptions } from './delete-range.ts';
2
+ import { type BatchOperation, type ConditionalBatchCondition, type ScanOptions, type Storage, type StorageCapabilities } from './interface.ts';
3
+ import { type NeonQueryResult } from './neon-value-mapping.ts';
4
+ /**
5
+ * A connection that can run a single interactive transaction. Obtained from
6
+ * {@link NeonPool.connect}; `release()` returns it to the pool. Both
7
+ * `batch()` and `conditionalBatch()` drive `BEGIN`/`COMMIT`/`ROLLBACK` over one
8
+ * of these so every statement in a transaction lands on the same connection —
9
+ * `pool.query()` alone may scatter statements across pooled connections, which
10
+ * would make a multi-statement batch non-atomic.
11
+ */
12
+ export type NeonPoolClient = {
13
+ query(sql: string, parameters?: unknown[]): Promise<NeonQueryResult>;
14
+ release(): void;
15
+ };
16
+ /**
17
+ * Minimal structural view of a node-postgres `Pool`. The real Neon serverless
18
+ * `Pool` satisfies this; the PGlite test backend is wrapped to satisfy it too.
19
+ * `query()` runs a single statement on a pooled connection (used for the
20
+ * single-statement hot paths); `connect()` pins a connection for an interactive
21
+ * transaction; `end()` tears the pool down.
22
+ */
23
+ export type NeonPool = {
24
+ query(sql: string, parameters?: unknown[]): Promise<NeonQueryResult>;
25
+ connect(): Promise<NeonPoolClient>;
26
+ end(): Promise<void>;
27
+ };
28
+ /**
29
+ * Configuration for connecting to a Neon (or any Postgres) database.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { NeonStorage, type NeonStorageOptions } from '@lostgradient/weft/storage/neon';
34
+ *
35
+ * const options: NeonStorageOptions = {
36
+ * url: 'postgresql://user:password@ep-cool-name.us-east-2.aws.neon.tech/weft?sslmode=require',
37
+ * };
38
+ * await using storage = new NeonStorage(options);
39
+ * ```
40
+ */
41
+ export type NeonStorageOptions = {
42
+ /** Postgres connection string for the primary endpoint. */
43
+ url: string;
44
+ /**
45
+ * Optional pre-built pool. Pass this to reuse a pool you manage (for example a
46
+ * test backend such as PGlite, or a shared application pool), instead of having
47
+ * the adapter construct its own from `url`. When supplied, `url` is ignored and
48
+ * **ownership stays with the caller**: disposing the `NeonStorage` does NOT
49
+ * close an injected pool, so it can be shared across adapters and the caller
50
+ * remains responsible for ending it. A pool the adapter constructs itself (from
51
+ * `url`) IS closed on disposal.
52
+ */
53
+ pool?: NeonPool;
54
+ };
55
+ /**
56
+ * Storage adapter backed by Neon serverless Postgres for durable, remote
57
+ * deployments. Implements the same `Storage` interface as the SQLite adapters
58
+ * over a single `kv(key TEXT COLLATE "C", value BYTEA)` table, so switching from
59
+ * a local SQLite store to Neon is a configuration change, not a code change.
60
+ *
61
+ * **Endpoint assumption.** `capabilities()` reports `readAfterWrite:
62
+ * 'linearizable'`, which holds for the **primary** Neon endpoint. A read-replica
63
+ * connection string would violate that guarantee — point this adapter at the
64
+ * primary.
65
+ *
66
+ * **WebSocket runtime.** The Neon serverless driver connects over WebSocket. Bun
67
+ * and Node 22+ provide a global `WebSocket`, so no extra wiring is needed there.
68
+ * On Node ≤21 the driver needs `neonConfig.webSocketConstructor` set to the `ws`
69
+ * package before first use; install `ws` and configure it in that runtime.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { NeonStorage } from '@lostgradient/weft/storage/neon';
74
+ * import { Engine } from '@lostgradient/weft';
75
+ *
76
+ * await using storage = new NeonStorage({
77
+ * url: process.env['NEON_DATABASE_URL']!,
78
+ * });
79
+ * await using engine = new Engine({ storage });
80
+ * ```
81
+ */
82
+ export declare class NeonStorage implements Storage {
83
+ #private;
84
+ /**
85
+ * @param options Connection configuration ({@link NeonStorageOptions}).
86
+ * @param poolFactory Internal seam for constructing the owned pool from `url`.
87
+ * Defaults to the real driver `Pool`; tests inject a fake (for example one
88
+ * whose `end()` rejects) to exercise owned-pool teardown without a network.
89
+ * Used only when no `pool` is supplied; an injected `pool` stays caller-owned.
90
+ */
91
+ constructor(options: NeonStorageOptions, poolFactory?: (url: string) => NeonPool);
92
+ capabilities(): StorageCapabilities;
93
+ get(key: string): Promise<Uint8Array | null>;
94
+ put(key: string, value: Uint8Array): Promise<void>;
95
+ delete(key: string): Promise<void>;
96
+ has(key: string): Promise<boolean>;
97
+ deletePrefix(prefix: string): Promise<number>;
98
+ deleteRange(prefix: string, options: DeleteRangeOptions): Promise<number>;
99
+ scan(prefix: string, options?: ScanOptions): AsyncIterable<[string, Uint8Array]>;
100
+ keys(prefix: string, options?: ScanOptions): AsyncIterable<string>;
101
+ count(prefix: string): Promise<number>;
102
+ scoped(prefix: string): Storage;
103
+ batch(operations: BatchOperation[]): Promise<void>;
104
+ conditionalBatch(conditions: ConditionalBatchCondition[], operations: BatchOperation[]): Promise<boolean>;
105
+ query<T>(sql: string, parameters?: unknown[]): Promise<T[]>;
106
+ [Symbol.dispose](): void;
107
+ [Symbol.asyncDispose](): Promise<void>;
108
+ }
@@ -0,0 +1,10 @@
1
+ // @bun
2
+ var $W=Object.defineProperty;var YW=(W)=>W;function jW(W,E){this[W]=YW.bind(null,E)}var zW=(W,E)=>{for(var F in E)$W(W,F,{get:E[F],enumerable:!0,configurable:!0,set:jW.bind(E,F)})};var j=(W,E)=>()=>(W&&(E=W(W=0)),E);function N(W,E,F){if(!W.capabilities()[E])throw Error(`Feature "${F}" requires storage capability "${E}", but this storage backend does not provide it.`)}function MW(W){let E=W.capabilities(),F=[];if(E.persistence!=="local"&&E.persistence!=="remote")F.push(`persistence must be "local" or "remote" (got "${E.persistence}")`);if(E.readAfterWrite!=="linearizable")F.push(`readAfterWrite must be "linearizable" (got "${E.readAfterWrite}")`);if(E.scanConsistency!=="snapshot")F.push(`scanConsistency must be "snapshot" (got "${E.scanConsistency}")`);if(!E.atomicBatch)F.push("atomicBatch must be true");if(!E.conditionalBatch)F.push("conditionalBatch must be true");if(F.length>0)throw Error(`Storage is not durable enough for recovery: ${F.join("; ")}.`)}var L="default";async function h(W,E){return await W.get(E)!==null}async function*U(W,E,F){for await(let[X]of W.scan(E,F))yield X}async function b(W,E){let F=0;for await(let X of U(W,E))F++;return F}async function C(W,E){let F=[];for await(let X of U(W,E))F.push({type:"delete",key:X});if(F.length===0)return 0;return await W.batch(F),F.length}async function K(W,E,F){let X=[];for await(let J of U(W,E,F))X.push({type:"delete",key:J});if(X.length===0)return 0;return await W.batch(X),X.length}var UW;var S=j(()=>{UW=["actrec:","archive:","async-act:","attr:","audit:bulk:","blob:","budget:","budget-charged:","ev:","idx:","liveness:","offload:","op:","review:","schedule:","schedule-due:","schedule-run:","sig:","sigres:","sigseq:","start-idem:","state:","tag:","tool-effect:","upd:","upk:","upr:","wf:","wf-cleanup:","wf-cleanup-needed:","wf-deadline:","wf-delayed:","wf-has-services:","wf-headers:","wf-idx-","wf-terminal:"]});function I(W){return W.length>0?W.slice(0,-1)+String.fromCharCode(W.charCodeAt(W.length-1)+1):"\xFF"}function IW(W,E={}){if(E.gt!==void 0&&W<=E.gt)return!1;if(E.gte!==void 0&&W<E.gte)return!1;if(E.lt!==void 0&&W>=E.lt)return!1;if(E.lte!==void 0&&W>E.lte)return!1;return!0}function x(W,E){if(W===null||E===null)return W===E;if(W.byteLength!==E.byteLength)return!1;for(let F=0;F<W.byteLength;F++)if(W[F]!==E[F])return!1;return!0}async function u(W,E){if(W.has)return W.has(E);return h(W,E)}function k(W,E,F){if(W.keys)return W.keys(E,F);return U(W,E,F)}async function c(W,E){if(W.count)return W.count(E);return b(W,E)}async function w(W,E){if(W.deletePrefix)return W.deletePrefix(E);return C(W,E)}async function y(W,E,F){if(N(W,"conditionalBatch","storageConditionalBatch"),!W.conditionalBatch)throw Error("This storage backend reports conditionalBatch capability but does not implement the conditionalBatch() method.");return W.conditionalBatch(E,F)}function _(W){return encodeURIComponent(W)}function xW(W){return decodeURIComponent(W)}function uW(W){try{return decodeURIComponent(W)}catch{return null}}var Y=(W)=>String(W).padStart(16,"0"),kW;var H=j(()=>{S();kW={workflow:(W)=>`wf:${_(W)}`,checkpoint:(W)=>`wf:${_(W)}:ckpt`,checkpointHistory:(W,E)=>`wf:${_(W)}:ckpt:${String(E).padStart(10,"0")}`,timelinePrefix:(W)=>`wf:${_(W)}:timeline:`,timeline:(W,E)=>`wf:${_(W)}:timeline:${String(E).padStart(10,"0")}`,schedule:(W)=>`schedule:${_(W)}`,scheduleTick:(W,E)=>`schedule-due:${String(W).padStart(16,"0")}:${_(E)}`,scheduleRun:(W)=>`schedule-run:${_(W)}`,operation:(W,E,F)=>`op:${W}:${Y(E)}:${F}`,operationInflight:(W)=>`op:inflight:${W}`,operationQueued:(W)=>`op:queued:${W}`,operationResolved:(W)=>`op:resolved:${W}`,bulkOperationAuditPrefix:()=>"audit:bulk:",bulkOperationAudit:(W,E,F)=>`audit:bulk:${Y(W)}:${_(E)}:${_(F)}`,operationResolvedByTimePrefix:()=>"op:resolved-by-time:",operationResolvedByTime:(W,E)=>`op:resolved-by-time:${Y(W)}:${_(E)}`,asyncActivity:(W,E)=>`async-act:v1:${_(W)}:${_(E)}`,activityReconciliationPrefix:(W)=>`actrec:v1:${_(W)}:`,activityReconciliation:(W,E,F)=>`actrec:v1:${_(W)}:${_(E)}:${F}`,eventPrefix:(W)=>`ev:${_(W)}:`,event:(W,E)=>`ev:${_(W)}:${String(E).padStart(10,"0")}`,eventHead:(W)=>`ev:${_(W)}:head`,eventWatermark:(W)=>`ev:${_(W)}:watermark`,signal:(W,E,F)=>`sig:${_(W)}:${E}:${_(F)}`,signalSequence:(W)=>`sigseq:v1:${_(W)}`,signalAcceptedResponsePrefix:(W)=>`sigres:v1:${_(W)}:`,signalAcceptedResponse:(W,E,F)=>`sigres:v1:${_(W)}:${_(E)}:${_(F)}`,deadline:(W,E)=>`wf-deadline:${Y(W)}:${_(E)}`,terminalCleanup:(W,E)=>`wf-cleanup:${Y(W)}:${_(E)}`,delayedStart:(W,E)=>`wf-delayed:${Y(W)}:${_(E)}`,terminalWorkflowPrefix:()=>"wf-terminal:",terminalWorkflow:(W,E)=>`wf-terminal:${Y(W)}:${_(E)}`,attribute:(W)=>`attr:${_(W)}`,attributeIndex:(W,E,F)=>`idx:${W}:${E}:${_(F)}`,tagIndex:(W,E)=>`tag:${_(W)}:${_(E)}`,updatePrefix:(W)=>`upd:${_(W)}:`,update:(W,E)=>`upd:${_(W)}:${E}`,updateResponse:(W)=>`upr:${W}`,updateIdempotency:(W,E)=>`upk:${_(W)}:${E}`,startIdempotency:(W)=>`start-idem:${_(W)}`,startIdempotencySignalId:(W)=>`start-idem:${W}`,livenessPrefix:()=>"liveness:",liveness:(W)=>`liveness:${_(W)}`,budget:(W,E,F)=>`budget:${W}:${E}:${F}`,review:(W,E)=>`review:${_(W)}:${E}`,workflowHeaders:(W)=>`wf-headers:${_(W)}`,terminalCleanupNeeded:(W)=>`wf-cleanup-needed:${_(W)}`,workflowHasServices:(W)=>`wf-has-services:${_(W)}`,offload:(W,E)=>`offload:${_(W)}:${E}`,archive:(W,E)=>`archive:${_(W)}:${E}`,stateExecution:(W,E)=>`state:execution:${_(W)}:${_(E)}`,stateWorkflow:(W,E)=>`state:workflow-scope:${L}:${_(W)}:${_(E)}`,streamChunkPrefix:(W,E)=>`blob:${_(W)}:${E}:chunk:`,streamChunk:(W,E,F)=>`blob:${_(W)}:${E}:chunk:${String(F).padStart(10,"0")}`,streamMetadata:(W,E)=>`blob:${_(W)}:${E}:meta`,budgetCharged:(W)=>`budget-charged:${W}`,toolEffect:(W,E,F)=>`tool-effect:${_(W)}:${E}:${F}`,workflowVisibilityStatus:(W,E)=>`wf-idx-status:${_(W)}:${_(E)}`,workflowVisibilityType:(W,E)=>`wf-idx-type:${_(W)}:${_(E)}`,workflowVisibilityCreated:(W,E)=>`wf-idx-created:${Y(W)}:${_(E)}`,workflowVisibilityUpdated:(W,E)=>`wf-idx-updated:${Y(W)}:${_(E)}`,workflowVisibilityDeadline:(W,E)=>`wf-idx-deadline:${Y(W)}:${_(E)}`,workflowVisibilityManifest:(W)=>`wf-idx-manifest:${_(W)}`,workflowVisibilityMetaVersion:()=>"wf-idx-meta:version",workflowVisibilityMetaBuiltAt:()=>"wf-idx-meta:built-at",workflowVisibilityMetaCursor:()=>"wf-idx-meta:cursor"}});function VW(W){if(W===void 0)return;if(typeof W!=="number"||!Number.isInteger(W)||W<0)throw Error("deleteRange limit must be a finite non-negative integer");return W===0?0:W}function V(W){let E={},F=!1;for(let J of["gt","gte","lt","lte"]){let Q=W[J];if(Q===void 0)continue;if(typeof Q!=="string")throw Error("deleteRange bounds must be strings");E[J]=Q,F=!0}if(!F)throw Error("deleteRange requires at least one of gt/gte/lt/lte; use deletePrefix to delete a whole prefix");let X=VW(W.limit);if(X!==void 0)E.limit=X;return E}async function f(W,E,F){let X=V(F);if(W.deleteRange)return W.deleteRange(E,X);return K(W,E,X)}var B=()=>{};function R(W){return W.replaceAll(/:+$/g,"")}function HW(W,E){let F=R(W),X=R(E);if(F.length===0)return X;if(X.length===0)return F;return`${F}:${X}`}function m(W,E){return new A(W,E)}var A;var g=j(()=>{B();H();A=class A{#W;#_;constructor(W,E){this.#W=W,this.#_=R(E)}#E(W){if(this.#_.length===0)return W;return W.length===0?`${this.#_}:`:`${this.#_}:${W}`}#X(W){if(this.#_.length===0)return W;return W.slice(this.#_.length+1)}#F(W={}){let E={};if(W.limit!==void 0)E.limit=W.limit;if(W.reverse!==void 0)E.reverse=W.reverse;if(W.gt!==void 0)E.gt=this.#E(W.gt);if(W.gte!==void 0)E.gte=this.#E(W.gte);if(W.lt!==void 0)E.lt=this.#E(W.lt);if(W.lte!==void 0)E.lte=this.#E(W.lte);return E}#J(W){let E={};if(W.limit!==void 0)E.limit=W.limit;if(W.gt!==void 0)E.gt=this.#E(W.gt);if(W.gte!==void 0)E.gte=this.#E(W.gte);if(W.lt!==void 0)E.lt=this.#E(W.lt);if(W.lte!==void 0)E.lte=this.#E(W.lte);return E}capabilities(){return this.#W.capabilities()}scoped(W){return new A(this.#W,HW(this.#_,W))}async get(W){return this.#W.get(this.#E(W))}async put(W,E){await this.#W.put(this.#E(W),E)}async delete(W){await this.#W.delete(this.#E(W))}async*scan(W,E){for await(let[F,X]of this.#W.scan(this.#E(W),this.#F(E)))yield[this.#X(F),X]}async batch(W){await this.#W.batch(W.map((E)=>{if(E.type==="put")return{type:"put",key:this.#E(E.key),value:E.value};return{type:"delete",key:this.#E(E.key)}}))}async conditionalBatch(W,E){return y(this.#W,W.map((F)=>({key:this.#E(F.key),expectedValue:F.expectedValue})),E.map((F)=>{if(F.type==="put")return{type:"put",key:this.#E(F.key),value:F.value};return{type:"delete",key:this.#E(F.key)}}))}async has(W){return u(this.#W,this.#E(W))}async deletePrefix(W){return w(this.#W,this.#E(W))}async deleteRange(W,E){let F=this.#J(V(E));return f(this.#W,this.#E(W),F)}async*keys(W,E){for await(let F of k(this.#W,this.#E(W),this.#F(E)))yield this.#X(F)}async count(W){return c(this.#W,this.#E(W))}[Symbol.dispose](){this.#W[Symbol.dispose]()}}});function LW(W){return W.trim().replace(/;+\s*$/u,"").trim()}function BW(W){return DW.test(W)}function _W(W){let E=LW(W);if(E.length===0)throw Error("Storage query must not be empty.");if(E.includes(";"))throw Error("Storage query must contain exactly one read-only statement.");if(NW.test(E))return;if(GW.test(E)&&BW(E))return;throw Error("Storage query only supports read-only SELECT and PRAGMA statements.")}var GW,DW,NW;var XW=j(()=>{GW=/^PRAGMA\b/iu,DW=/^PRAGMA\s+(?:[A-Z_][A-Z0-9_]*\.)?[A-Z_][A-Z0-9_]*\s*$/iu,NW=/^SELECT\b/iu});B();H();import{Pool as RW}from"@neondatabase/serverless";function O(W){return W.rowCount??W.affectedRows??W.rows.length}function G(W){if(W instanceof Uint8Array)return new Uint8Array(W);return new Uint8Array(W)}function q(W){return Buffer.from(W)}H();var d=`CREATE TABLE IF NOT EXISTS kv (
3
+ key TEXT COLLATE "C" PRIMARY KEY,
4
+ value BYTEA NOT NULL
5
+ )`,l=`
6
+ SELECT COALESCE(co.collname, 'default') AS collation
7
+ FROM pg_attribute a
8
+ LEFT JOIN pg_collation co ON co.oid = a.attcollation
9
+ WHERE a.attrelid = to_regclass('kv') AND a.attname = 'key' AND a.attnum > 0
10
+ `,p="BEGIN ISOLATION LEVEL SERIALIZABLE",s="BEGIN ISOLATION LEVEL READ COMMITTED",a="BEGIN READ ONLY",i="COMMIT",z="ROLLBACK",T="SELECT value FROM kv WHERE key = $1",v="INSERT INTO kv (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",P="DELETE FROM kv WHERE key = $1",n="SELECT 1 AS present FROM kv WHERE key = $1 LIMIT 1",r="SELECT COUNT(*) AS count FROM kv WHERE key >= $1 AND key < $2",t="DELETE FROM kv WHERE key >= $1 AND key < $2";function D(W){return[W,I(W)]}function o(W,E){let{gt:F,gte:X,lt:J,lte:Q}=E,Z=D(W),$=["key >= $1 AND key < $2"];if(F!==void 0)Z.push(F),$.push(`key > $${Z.length}`);if(X!==void 0)Z.push(X),$.push(`key >= $${Z.length}`);if(J!==void 0)Z.push(J),$.push(`key < $${Z.length}`);if(Q!==void 0)Z.push(Q),$.push(`key <= $${Z.length}`);return{conditions:$,parameters:Z}}function e(W,E={}){let{limit:F,reverse:X}=E,{conditions:J,parameters:Q}=o(W,E),Z=X?"DESC":"ASC",$="";if(F!==void 0)Q.push(F),$=` LIMIT $${Q.length}`;return{parameters:Q,whereOrderLimit:`WHERE ${J.join(" AND ")} ORDER BY key ${Z}${$}`}}function WW(W,E={}){let{parameters:F,whereOrderLimit:X}=e(W,E);return{parameters:F,sql:`SELECT key, value FROM kv ${X}`}}function EW(W,E={}){let{parameters:F,whereOrderLimit:X}=e(W,E);return{parameters:F,sql:`SELECT key FROM kv ${X}`}}function FW(W,E){let{conditions:F,parameters:X}=o(W,E),J=F.join(" AND ");if(E.limit===void 0)return{parameters:X,sql:`DELETE FROM kv WHERE ${J}`};return X.push(E.limit),{parameters:X,sql:`DELETE FROM kv WHERE key IN (SELECT key FROM kv WHERE ${J} ORDER BY key ASC LIMIT $${X.length})`}}XW();g();var AW=new Set(["40001","40P01"]),JW=5;function OW(W){if(typeof W!=="object"||W===null||!("code"in W))return!1;let E=W.code;return typeof E==="string"&&AW.has(E)}class qW{#W;#_;#E;#X;constructor(W,E=(F)=>new RW({connectionString:F})){this.#E=W.pool===void 0,this.#W=W.pool??E(W.url)}capabilities(){return{persistence:"remote",readAfterWrite:"linearizable",scanConsistency:"snapshot",atomicBatch:!0,conditionalBatch:!0,boundedRangeDelete:!0}}#F(){return this.#_??=this.#J().catch((W)=>{throw this.#_=void 0,W}),this.#_}async#J(){await this.#W.query(d),await this.#$()}async#$(){let E=(await this.#W.query(l)).rows[0]?.collation;if(E==="C")return;let F=E===void 0?"no kv table":typeof E==="string"?`"${E}"`:"an unexpected collation value";throw Error(`NeonStorage requires the "kv" table's "key" column to use COLLATE "C" (found ${F}). A "kv" table created without COLLATE "C" sorts keys by the database locale, which breaks Weft's lexicographic prefix scans. Use a fresh database, or recreate the table as: CREATE TABLE kv (key TEXT COLLATE "C" PRIMARY KEY, value BYTEA NOT NULL).`)}async get(W){await this.#F();let F=(await this.#W.query(T,[W])).rows[0];if(F===void 0)return null;let X=F.value;if(X===null||X===void 0)return null;return G(X)}async put(W,E){await this.#F(),await this.#W.query(v,[W,q(E)])}async delete(W){await this.#F(),await this.#W.query(P,[W])}async has(W){return await this.#F(),(await this.#W.query(n,[W])).rows.length>0}async deletePrefix(W){await this.#F();let[E,F]=D(W),X=await this.#W.query(t,[E,F]);return O(X)}async deleteRange(W,E){await this.#F();let F=V(E),{parameters:X,sql:J}=FW(W,F),Q=await this.#W.query(J,X);return O(Q)}async*scan(W,E={}){await this.#F();let{parameters:F,sql:X}=WW(W,E),J=await this.#W.query(X,F);for(let Q of J.rows)yield[Q.key,G(Q.value)]}async*keys(W,E={}){await this.#F();let{parameters:F,sql:X}=EW(W,E),J=await this.#W.query(X,F);for(let Q of J.rows)yield Q.key}async count(W){await this.#F();let[E,F]=D(W),X=await this.#W.query(r,[E,F]);return Number(X.rows[0]?.count??0)}scoped(W){return m(this,W)}async#Q(W,E,F=()=>!0){let X=await this.#W.connect();try{await X.query(W);try{let J=await E(X);return await X.query(F(J)?i:z),J}catch(J){throw await X.query(z).catch(()=>{}),J}}finally{X.release()}}async batch(W){if(W.length===0)return;await this.#F(),await this.#Q(s,async(E)=>{await QW(E,W)})}async conditionalBatch(W,E){await this.#F();let F;for(let X=0;X<JW;X+=1)try{return await this.#Q(p,async(J)=>{for(let Q of W){let $=(await J.query(T,[Q.key])).rows[0]?.value,ZW=$===null||$===void 0?null:G($);if(!x(ZW,Q.expectedValue))return!1}return await QW(J,E),!0},(J)=>J)}catch(J){if(!OW(J))throw J;F=J}throw Error(`conditionalBatch exhausted ${JW} retries after retryable transaction failures`,{cause:F})}async query(W,E){return await this.#F(),_W(W),this.#Q(a,async(F)=>{return(await F.query(W,E??[])).rows})}#Z(){if(!this.#E)return Promise.resolve();return this.#X??=this.#W.end(),this.#X}[Symbol.dispose](){this.#Z().catch(()=>{})}async[Symbol.asyncDispose](){await this.#Z()}}async function QW(W,E){for(let F of E)if(F.type==="put")await W.query(v,[F.key,q(F.value)]);else await W.query(P,[F.key])}export{qW as NeonStorage};
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Lazy loader for the optional `better-sqlite3` peer dependency used by
3
+ * {@link NodeSQLiteStorage}. Kept in its own module — and deliberately NOT in the
4
+ * package `exports` map — so the test-only injection seam
5
+ * ({@link loadBetterSqlite3ForTest}) never becomes part of the documented public
6
+ * surface. `node-sqlite.ts` imports `loadBetterSqlite3` (production path);
7
+ * `node-sqlite.test.ts` imports the test entry directly from here.
8
+ *
9
+ * @module storage/node-sqlite-loader
10
+ */
11
+ /**
12
+ * Minimal `better-sqlite3` `Database` surface this adapter uses. Defined here so
13
+ * both the loader and the storage module compile without the package installed.
14
+ */
15
+ export type BetterSqliteStatement = {
16
+ run(...parameters: unknown[]): unknown;
17
+ get(...parameters: unknown[]): Record<string, unknown> | undefined;
18
+ all(...parameters: unknown[]): Record<string, unknown>[];
19
+ };
20
+ export type BetterSqliteTransaction = (...args: unknown[]) => unknown;
21
+ export type BetterSqliteDatabase = {
22
+ pragma(source: string): unknown;
23
+ exec(source: string): void;
24
+ prepare(source: string): BetterSqliteStatement;
25
+ transaction(fn: (...args: unknown[]) => unknown): BetterSqliteTransaction;
26
+ close(): void;
27
+ };
28
+ export type BetterSqliteConstructor = new (path: string) => BetterSqliteDatabase;
29
+ /**
30
+ * Build the actionable error thrown when `better-sqlite3` cannot be loaded —
31
+ * either because the optional dependency is absent or its native binding fails
32
+ * to dlopen.
33
+ */
34
+ export declare function createMissingBetterSqlite3Error(cause: unknown): Error;
35
+ /**
36
+ * Whether `error` is a recognizable `better-sqlite3` load failure (missing
37
+ * package or native-binding dlopen failure) worth reshaping into the actionable
38
+ * peer-dependency error rather than re-throwing raw.
39
+ */
40
+ export declare function isBetterSqlite3LoadFailure(error: unknown): boolean;
41
+ /**
42
+ * Resolve the `better-sqlite3` module via a CommonJS require. This package is
43
+ * ESM (`type: module`), so the global `require` is not defined — `createRequire`
44
+ * from `node:module` builds a CommonJS require for loading the native binding.
45
+ *
46
+ * Extracted as a standalone function so tests can inject a throwing resolver
47
+ * (see {@link loadBetterSqlite3ForTest}) to exercise the missing/failed-dependency
48
+ * paths WITHOUT `mock.module('node:module', ...)`. That mock is irreversible in
49
+ * Bun: it patches the CJS loader process-wide and `mock.restore()` does not undo
50
+ * it, which poisons `require()` for every later test in the same process
51
+ * (notably any WASM module that requires a core module during boot).
52
+ */
53
+ declare function resolveBetterSqlite3Module(): {
54
+ default?: BetterSqliteConstructor;
55
+ } & BetterSqliteConstructor;
56
+ /** Resolver for the `better-sqlite3` module. Injectable for tests. */
57
+ type BetterSqlite3ModuleResolver = typeof resolveBetterSqlite3Module;
58
+ /**
59
+ * Load (and cache) the `better-sqlite3` constructor. Reshapes a recognized load
60
+ * failure into the actionable peer-dependency error.
61
+ */
62
+ export declare function loadBetterSqlite3(resolveModule?: BetterSqlite3ModuleResolver): BetterSqliteConstructor;
63
+ /**
64
+ * Test-only entry that exercises {@link loadBetterSqlite3}'s module-resolution
65
+ * and error-shaping with an injected resolver, bypassing the cached constructor
66
+ * so the missing/failed-dependency paths run every call. This lets those paths be
67
+ * tested without mocking the global `node:module` loader. Lives in this
68
+ * non-exported module so it never reaches the public package surface.
69
+ */
70
+ export declare function loadBetterSqlite3ForTest(resolveModule: BetterSqlite3ModuleResolver): BetterSqliteConstructor;
71
+ export {};
@@ -0,0 +1,41 @@
1
+ import { createRequire } from "node:module";
2
+ let DatabaseConstructor;
3
+ export function createMissingBetterSqlite3Error(cause) {
4
+ return Error('NodeSQLiteStorage requires the optional peer dependency "better-sqlite3". Install it in your application with: bun add better-sqlite3 (or npm install better-sqlite3).', { cause });
5
+ }
6
+ export function isBetterSqlite3LoadFailure(error) {
7
+ if (!(error instanceof Error))
8
+ return !1;
9
+ const errorCode = error.code;
10
+ if (errorCode === "MODULE_NOT_FOUND")
11
+ return error.message.includes("'better-sqlite3'") || error.message.includes('"better-sqlite3"') || error.message.includes("'bindings'") || error.message.includes('"bindings"');
12
+ if (errorCode === "ERR_DLOPEN_FAILED")
13
+ return error.message.includes("better-sqlite3");
14
+ return !1;
15
+ }
16
+ function resolveBetterSqlite3Module() {
17
+ return createRequire(import.meta.url)("better-sqlite3");
18
+ }
19
+ export function loadBetterSqlite3(resolveModule = resolveBetterSqlite3Module) {
20
+ if (DatabaseConstructor)
21
+ return DatabaseConstructor;
22
+ let mod;
23
+ try {
24
+ mod = resolveModule();
25
+ } catch (error) {
26
+ if (isBetterSqlite3LoadFailure(error))
27
+ throw createMissingBetterSqlite3Error(error);
28
+ throw error;
29
+ }
30
+ DatabaseConstructor = typeof mod.default === "function" ? mod.default : mod;
31
+ return DatabaseConstructor;
32
+ }
33
+ export function loadBetterSqlite3ForTest(resolveModule) {
34
+ const previous = DatabaseConstructor;
35
+ DatabaseConstructor = void 0;
36
+ try {
37
+ return loadBetterSqlite3(resolveModule);
38
+ } finally {
39
+ DatabaseConstructor = previous;
40
+ }
41
+ }
@@ -11,25 +11,7 @@
11
11
  * @module storage/node-sqlite
12
12
  */
13
13
  import type { BatchOperation, ConditionalBatchCondition, ScanOptions, Storage, StorageCapabilities } from './interface.ts';
14
- /**
15
- * Minimal subset of the `better-sqlite3` API surface that this adapter uses.
16
- * Defined here so the module compiles without the package installed — the
17
- * actual dependency is resolved lazily at construction time.
18
- */
19
- type BetterSqliteStatement = {
20
- run(...parameters: unknown[]): unknown;
21
- get(...parameters: unknown[]): Record<string, unknown> | undefined;
22
- all(...parameters: unknown[]): Record<string, unknown>[];
23
- };
24
- type BetterSqliteTransaction = (...args: unknown[]) => unknown;
25
- type BetterSqliteDatabase = {
26
- pragma(source: string): unknown;
27
- exec(source: string): void;
28
- prepare(source: string): BetterSqliteStatement;
29
- transaction(fn: (...args: unknown[]) => unknown): BetterSqliteTransaction;
30
- close(): void;
31
- };
32
- type BetterSqliteConstructor = new (path: string) => BetterSqliteDatabase;
14
+ import { type BetterSqliteConstructor } from './node-sqlite-loader.ts';
33
15
  /**
34
16
  * Runtime-neutral alias for the Node SQLite adapter. Consumers that import
35
17
  * from `@lostgradient/weft/storage/sqlite` get this class under Node.
@@ -1,7 +1,4 @@
1
1
  // @bun
2
- // src/storage/node-sqlite.ts
3
- import { createRequire } from "module";
4
-
5
2
  // src/storage/capabilities.ts
6
3
  function requireStorageCapability(storage, capability, featureName) {
7
4
  if (!storage.capabilities()[capability]) {
@@ -98,6 +95,44 @@ async function storageConditionalBatch(storage, conditions, operations) {
98
95
  return storage.conditionalBatch(conditions, operations);
99
96
  }
100
97
 
98
+ // src/storage/node-sqlite-loader.ts
99
+ import { createRequire } from "module";
100
+ var DatabaseConstructor;
101
+ function createMissingBetterSqlite3Error(cause) {
102
+ return new Error('NodeSQLiteStorage requires the optional peer dependency "better-sqlite3". ' + "Install it in your application with: bun add better-sqlite3 (or npm install better-sqlite3).", { cause });
103
+ }
104
+ function isBetterSqlite3LoadFailure(error) {
105
+ if (!(error instanceof Error))
106
+ return false;
107
+ const errorCode = error.code;
108
+ if (errorCode === "MODULE_NOT_FOUND") {
109
+ return error.message.includes("'better-sqlite3'") || error.message.includes('"better-sqlite3"') || error.message.includes("'bindings'") || error.message.includes('"bindings"');
110
+ }
111
+ if (errorCode === "ERR_DLOPEN_FAILED") {
112
+ return error.message.includes("better-sqlite3");
113
+ }
114
+ return false;
115
+ }
116
+ function resolveBetterSqlite3Module() {
117
+ const requireFromHere = createRequire(import.meta.url);
118
+ return requireFromHere("better-sqlite3");
119
+ }
120
+ function loadBetterSqlite3(resolveModule = resolveBetterSqlite3Module) {
121
+ if (DatabaseConstructor)
122
+ return DatabaseConstructor;
123
+ let mod;
124
+ try {
125
+ mod = resolveModule();
126
+ } catch (error) {
127
+ if (isBetterSqlite3LoadFailure(error)) {
128
+ throw createMissingBetterSqlite3Error(error);
129
+ }
130
+ throw error;
131
+ }
132
+ DatabaseConstructor = typeof mod.default === "function" ? mod.default : mod;
133
+ return DatabaseConstructor;
134
+ }
135
+
101
136
  // src/storage/sqlite-key-value-queries.ts
102
137
  var SQLITE_CREATE_KEY_VALUE_TABLE = `CREATE TABLE IF NOT EXISTS kv (
103
138
  key TEXT PRIMARY KEY,
@@ -178,35 +213,6 @@ function buildSqliteKeyRangeSelect(prefix, options = {}) {
178
213
  }
179
214
 
180
215
  // src/storage/node-sqlite.ts
181
- var DatabaseConstructor;
182
- function createMissingBetterSqlite3Error(cause) {
183
- return new Error('NodeSQLiteStorage requires the optional peer dependency "better-sqlite3". ' + "Install it in your application with: bun add better-sqlite3 (or npm install better-sqlite3).", { cause });
184
- }
185
- function isBetterSqlite3LoadFailure(error) {
186
- if (!(error instanceof Error))
187
- return false;
188
- const errorCode = error.code;
189
- if (errorCode === "MODULE_NOT_FOUND") {
190
- return error.message.includes("'better-sqlite3'") || error.message.includes('"better-sqlite3"') || error.message.includes("'bindings'") || error.message.includes('"bindings"');
191
- }
192
- if (errorCode === "ERR_DLOPEN_FAILED") {
193
- return error.message.includes("better-sqlite3");
194
- }
195
- return false;
196
- }
197
- function loadBetterSqlite3() {
198
- if (DatabaseConstructor)
199
- return DatabaseConstructor;
200
- const requireFromHere = createRequire(import.meta.url);
201
- let mod;
202
- try {
203
- mod = requireFromHere("better-sqlite3");
204
- } catch (error) {
205
- throw createMissingBetterSqlite3Error(error);
206
- }
207
- DatabaseConstructor = typeof mod.default === "function" ? mod.default : mod;
208
- return DatabaseConstructor;
209
- }
210
216
  class NodeSQLiteStorage {
211
217
  #database;
212
218
  #persistence;
@@ -0,0 +1,79 @@
1
+ import type { NormalizedDeleteRangeOptions } from './delete-range.ts';
2
+ import { type ScanOptions } from './interface.ts';
3
+ /**
4
+ * The Postgres counterpart of `sqlite-key-value-queries.ts`. The schema is the
5
+ * same single `kv(key, value)` table, but two Postgres specifics differ from the
6
+ * SQLite builders and force a separate module:
7
+ *
8
+ * - **Numbered placeholders.** Postgres binds parameters as `$1`, `$2`, … in
9
+ * statement order, not the positional `?` SQLite uses. A builder that appends
10
+ * bounds dynamically must therefore track the next placeholder index.
11
+ * - **`COLLATE "C"` on the key.** Postgres `TEXT` sorts by the database locale,
12
+ * which reorders punctuation and would silently corrupt every prefix-range
13
+ * scan and `ORDER BY key` the engine relies on. Pinning the primary-key column
14
+ * to the `C` collation restores byte-wise (codepoint) ordering, matching
15
+ * SQLite's default `BINARY` collation and the engine's key-layout assumptions.
16
+ *
17
+ * @module storage/postgres-key-value-queries
18
+ */
19
+ export declare const PG_CREATE_KEY_VALUE_TABLE = "CREATE TABLE IF NOT EXISTS kv (\n key TEXT COLLATE \"C\" PRIMARY KEY,\n value BYTEA NOT NULL\n)";
20
+ /**
21
+ * Introspect the collation of the `key` column on the `kv` table the adapter's
22
+ * own unqualified queries resolve to. Returns one row with a `collation` field:
23
+ * the named collation (`C` for a correctly-created table) or `default` when the
24
+ * column inherits the database default collation. Returns zero rows when no `kv`
25
+ * table is visible on the search path.
26
+ *
27
+ * `to_regclass('kv')` resolves the same search-path-visible relation that
28
+ * unqualified DDL/DML hits, so a `kv` table in another schema cannot be inspected
29
+ * by mistake (which would falsely pass a mis-collated active table or falsely
30
+ * reject a valid one). Detects a pre-existing `kv` whose key collation would break
31
+ * lexicographic prefix scans — a `CREATE TABLE IF NOT EXISTS` would otherwise
32
+ * silently adopt it.
33
+ */
34
+ export declare const PG_SELECT_KEY_COLLATION = "\n SELECT COALESCE(co.collname, 'default') AS collation\n FROM pg_attribute a\n LEFT JOIN pg_collation co ON co.oid = a.attcollation\n WHERE a.attrelid = to_regclass('kv') AND a.attname = 'key' AND a.attnum > 0\n";
35
+ /** Begin a transaction at SERIALIZABLE isolation (conditionalBatch's CAS path). */
36
+ export declare const PG_BEGIN_SERIALIZABLE = "BEGIN ISOLATION LEVEL SERIALIZABLE";
37
+ /** Begin a transaction at READ COMMITTED isolation (the atomic batch() path). */
38
+ export declare const PG_BEGIN_READ_COMMITTED = "BEGIN ISOLATION LEVEL READ COMMITTED";
39
+ /**
40
+ * Begin a READ ONLY transaction for the `query()` passthrough. Postgres enforces
41
+ * this at the database level — a writing statement (including a data-modifying
42
+ * CTE that a textual SELECT check would miss) errors instead of mutating.
43
+ */
44
+ export declare const PG_BEGIN_READ_ONLY = "BEGIN READ ONLY";
45
+ /** Commit the current transaction. */
46
+ export declare const PG_COMMIT = "COMMIT";
47
+ /** Roll back the current transaction. */
48
+ export declare const PG_ROLLBACK = "ROLLBACK";
49
+ export declare const PG_SELECT_VALUE_BY_KEY = "SELECT value FROM kv WHERE key = $1";
50
+ export declare const PG_UPSERT_VALUE_BY_KEY = "INSERT INTO kv (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value";
51
+ export declare const PG_DELETE_VALUE_BY_KEY = "DELETE FROM kv WHERE key = $1";
52
+ export declare const PG_SELECT_KEY_PRESENCE = "SELECT 1 AS present FROM kv WHERE key = $1 LIMIT 1";
53
+ export declare const PG_COUNT_KEYS_BY_PREFIX = "SELECT COUNT(*) AS count FROM kv WHERE key >= $1 AND key < $2";
54
+ export declare const PG_DELETE_KEYS_BY_PREFIX = "DELETE FROM kv WHERE key >= $1 AND key < $2";
55
+ export type PostgresKeyRangeQueryParameter = string | number;
56
+ /** A fully-built SQL statement and its bound parameters (SELECT or DELETE). */
57
+ export type PostgresBuiltQuery = {
58
+ parameters: PostgresKeyRangeQueryParameter[];
59
+ sql: string;
60
+ };
61
+ export declare function buildPostgresPrefixRangeParameters(prefix: string): [string, string];
62
+ export declare function buildPostgresKeyValueRangeSelect(prefix: string, options?: ScanOptions): PostgresBuiltQuery;
63
+ export declare function buildPostgresKeyRangeSelect(prefix: string, options?: ScanOptions): PostgresBuiltQuery;
64
+ /**
65
+ * Build a bounded-range `DELETE` for the `kv` table from already-validated
66
+ * delete options.
67
+ *
68
+ * Without `limit`, emits `DELETE FROM kv WHERE <conditions>` — no ordering, since
69
+ * order is meaningless when deleting the whole matched range. With `limit`, emits
70
+ * a subquery form so the lowest (ascending) keys are deleted first:
71
+ * `DELETE FROM kv WHERE key IN (SELECT key FROM kv WHERE <conditions> ORDER BY key
72
+ * ASC LIMIT $n)`. Postgres does not support `DELETE ... ORDER BY ... LIMIT`
73
+ * directly, so the subquery is the portable form.
74
+ *
75
+ * Requires {@link NormalizedDeleteRangeOptions}: the type guarantees at least one
76
+ * bound is present and the prefix range is always the leading condition, so this
77
+ * builder can never produce a whole-table wipe.
78
+ */
79
+ export declare function buildPostgresKeyRangeDelete(prefix: string, options: NormalizedDeleteRangeOptions): PostgresBuiltQuery;
@@ -0,0 +1,63 @@
1
+ import { resolvePrefixRangeEnd } from "./interface.js";
2
+ export const PG_CREATE_KEY_VALUE_TABLE = `CREATE TABLE IF NOT EXISTS kv (
3
+ key TEXT COLLATE "C" PRIMARY KEY,
4
+ value BYTEA NOT NULL
5
+ )`, PG_SELECT_KEY_COLLATION = `
6
+ SELECT COALESCE(co.collname, 'default') AS collation
7
+ FROM pg_attribute a
8
+ LEFT JOIN pg_collation co ON co.oid = a.attcollation
9
+ WHERE a.attrelid = to_regclass('kv') AND a.attname = 'key' AND a.attnum > 0
10
+ `, PG_BEGIN_SERIALIZABLE = "BEGIN ISOLATION LEVEL SERIALIZABLE", PG_BEGIN_READ_COMMITTED = "BEGIN ISOLATION LEVEL READ COMMITTED", PG_BEGIN_READ_ONLY = "BEGIN READ ONLY", PG_COMMIT = "COMMIT", PG_ROLLBACK = "ROLLBACK", PG_SELECT_VALUE_BY_KEY = "SELECT value FROM kv WHERE key = $1", PG_UPSERT_VALUE_BY_KEY = "INSERT INTO kv (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value", PG_DELETE_VALUE_BY_KEY = "DELETE FROM kv WHERE key = $1", PG_SELECT_KEY_PRESENCE = "SELECT 1 AS present FROM kv WHERE key = $1 LIMIT 1", PG_COUNT_KEYS_BY_PREFIX = "SELECT COUNT(*) AS count FROM kv WHERE key >= $1 AND key < $2", PG_DELETE_KEYS_BY_PREFIX = "DELETE FROM kv WHERE key >= $1 AND key < $2";
11
+ export function buildPostgresPrefixRangeParameters(prefix) {
12
+ return [prefix, resolvePrefixRangeEnd(prefix)];
13
+ }
14
+ function buildPostgresKeyRangeConditions(prefix, bounds) {
15
+ const { gt, gte, lt, lte } = bounds, parameters = buildPostgresPrefixRangeParameters(prefix), conditions = ["key >= $1 AND key < $2"];
16
+ if (gt !== void 0) {
17
+ parameters.push(gt);
18
+ conditions.push(`key > $${parameters.length}`);
19
+ }
20
+ if (gte !== void 0) {
21
+ parameters.push(gte);
22
+ conditions.push(`key >= $${parameters.length}`);
23
+ }
24
+ if (lt !== void 0) {
25
+ parameters.push(lt);
26
+ conditions.push(`key < $${parameters.length}`);
27
+ }
28
+ if (lte !== void 0) {
29
+ parameters.push(lte);
30
+ conditions.push(`key <= $${parameters.length}`);
31
+ }
32
+ return { conditions, parameters };
33
+ }
34
+ function buildPostgresKeyRangeQuery(prefix, options = {}) {
35
+ const { limit, reverse } = options, { conditions, parameters } = buildPostgresKeyRangeConditions(prefix, options), direction = reverse ? "DESC" : "ASC";
36
+ let limitClause = "";
37
+ if (limit !== void 0) {
38
+ parameters.push(limit);
39
+ limitClause = ` LIMIT $${parameters.length}`;
40
+ }
41
+ return {
42
+ parameters,
43
+ whereOrderLimit: `WHERE ${conditions.join(" AND ")} ORDER BY key ${direction}${limitClause}`
44
+ };
45
+ }
46
+ export function buildPostgresKeyValueRangeSelect(prefix, options = {}) {
47
+ const { parameters, whereOrderLimit } = buildPostgresKeyRangeQuery(prefix, options);
48
+ return { parameters, sql: `SELECT key, value FROM kv ${whereOrderLimit}` };
49
+ }
50
+ export function buildPostgresKeyRangeSelect(prefix, options = {}) {
51
+ const { parameters, whereOrderLimit } = buildPostgresKeyRangeQuery(prefix, options);
52
+ return { parameters, sql: `SELECT key FROM kv ${whereOrderLimit}` };
53
+ }
54
+ export function buildPostgresKeyRangeDelete(prefix, options) {
55
+ const { conditions, parameters } = buildPostgresKeyRangeConditions(prefix, options), whereClause = conditions.join(" AND ");
56
+ if (options.limit === void 0)
57
+ return { parameters, sql: `DELETE FROM kv WHERE ${whereClause}` };
58
+ parameters.push(options.limit);
59
+ return {
60
+ parameters,
61
+ sql: `DELETE FROM kv WHERE key IN (SELECT key FROM kv WHERE ${whereClause} ORDER BY key ASC LIMIT $${parameters.length})`
62
+ };
63
+ }