ai-database 2.1.3 → 2.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 (260) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/README.md +880 -669
  3. package/dist/actions.d.ts +2 -2
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +1 -1
  6. package/dist/actions.js.map +1 -1
  7. package/dist/ai-promise-db.d.ts +49 -23
  8. package/dist/ai-promise-db.d.ts.map +1 -1
  9. package/dist/ai-promise-db.js +91 -63
  10. package/dist/ai-promise-db.js.map +1 -1
  11. package/dist/authorization.d.ts.map +1 -1
  12. package/dist/authorization.js +38 -30
  13. package/dist/authorization.js.map +1 -1
  14. package/dist/cascade-orchestrator.d.ts +404 -0
  15. package/dist/cascade-orchestrator.d.ts.map +1 -0
  16. package/dist/cascade-orchestrator.js +828 -0
  17. package/dist/cascade-orchestrator.js.map +1 -0
  18. package/dist/cascade-write-strategy.d.ts +584 -0
  19. package/dist/cascade-write-strategy.d.ts.map +1 -0
  20. package/dist/cascade-write-strategy.js +590 -0
  21. package/dist/cascade-write-strategy.js.map +1 -0
  22. package/dist/ch-adapter.d.ts +358 -0
  23. package/dist/ch-adapter.d.ts.map +1 -0
  24. package/dist/ch-adapter.js +929 -0
  25. package/dist/ch-adapter.js.map +1 -0
  26. package/dist/client/index.d.ts +42 -0
  27. package/dist/client/index.d.ts.map +1 -0
  28. package/dist/client/index.js +43 -0
  29. package/dist/client/index.js.map +1 -0
  30. package/dist/client.d.ts +266 -0
  31. package/dist/client.d.ts.map +1 -0
  32. package/dist/client.js +81 -0
  33. package/dist/client.js.map +1 -0
  34. package/dist/constants.d.ts +64 -1
  35. package/dist/constants.d.ts.map +1 -1
  36. package/dist/constants.js +52 -2
  37. package/dist/constants.js.map +1 -1
  38. package/dist/dataloader.d.ts +99 -0
  39. package/dist/dataloader.d.ts.map +1 -0
  40. package/dist/dataloader.js +225 -0
  41. package/dist/dataloader.js.map +1 -0
  42. package/dist/db-provider-port.d.ts +501 -0
  43. package/dist/db-provider-port.d.ts.map +1 -0
  44. package/dist/db-provider-port.js +113 -0
  45. package/dist/db-provider-port.js.map +1 -0
  46. package/dist/digital-objects-provider.d.ts +49 -0
  47. package/dist/digital-objects-provider.d.ts.map +1 -0
  48. package/dist/digital-objects-provider.js +55 -0
  49. package/dist/digital-objects-provider.js.map +1 -0
  50. package/dist/do-sqlite-adapter.d.ts +402 -0
  51. package/dist/do-sqlite-adapter.d.ts.map +1 -0
  52. package/dist/do-sqlite-adapter.js +745 -0
  53. package/dist/do-sqlite-adapter.js.map +1 -0
  54. package/dist/docs-rels/custom-types.d.ts +134 -0
  55. package/dist/docs-rels/custom-types.d.ts.map +1 -0
  56. package/dist/docs-rels/custom-types.js +70 -0
  57. package/dist/docs-rels/custom-types.js.map +1 -0
  58. package/dist/docs-rels/index.d.ts +16 -0
  59. package/dist/docs-rels/index.d.ts.map +1 -0
  60. package/dist/docs-rels/index.js +16 -0
  61. package/dist/docs-rels/index.js.map +1 -0
  62. package/dist/docs-rels/migrations/index.d.ts +30 -0
  63. package/dist/docs-rels/migrations/index.d.ts.map +1 -0
  64. package/dist/docs-rels/migrations/index.js +128 -0
  65. package/dist/docs-rels/migrations/index.js.map +1 -0
  66. package/dist/docs-rels/schema.d.ts +2961 -0
  67. package/dist/docs-rels/schema.d.ts.map +1 -0
  68. package/dist/docs-rels/schema.js +244 -0
  69. package/dist/docs-rels/schema.js.map +1 -0
  70. package/dist/durable-clickhouse.d.ts.map +1 -1
  71. package/dist/durable-clickhouse.js +16 -13
  72. package/dist/durable-clickhouse.js.map +1 -1
  73. package/dist/durable-promise.d.ts.map +1 -1
  74. package/dist/durable-promise.js +34 -15
  75. package/dist/durable-promise.js.map +1 -1
  76. package/dist/errors.d.ts +127 -0
  77. package/dist/errors.d.ts.map +1 -0
  78. package/dist/errors.js +210 -0
  79. package/dist/errors.js.map +1 -0
  80. package/dist/eventbridge.d.ts +117 -0
  81. package/dist/eventbridge.d.ts.map +1 -0
  82. package/dist/eventbridge.js +238 -0
  83. package/dist/eventbridge.js.map +1 -0
  84. package/dist/events.d.ts +2 -2
  85. package/dist/events.d.ts.map +1 -1
  86. package/dist/events.js +1 -1
  87. package/dist/events.js.map +1 -1
  88. package/dist/execution-queue.d.ts.map +1 -1
  89. package/dist/execution-queue.js +4 -5
  90. package/dist/execution-queue.js.map +1 -1
  91. package/dist/index.d.ts +35 -8
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +106 -6
  94. package/dist/index.js.map +1 -1
  95. package/dist/linguistic.d.ts +3 -108
  96. package/dist/linguistic.d.ts.map +1 -1
  97. package/dist/linguistic.js +3 -372
  98. package/dist/linguistic.js.map +1 -1
  99. package/dist/logger.d.ts +132 -0
  100. package/dist/logger.d.ts.map +1 -0
  101. package/dist/logger.js +137 -0
  102. package/dist/logger.js.map +1 -0
  103. package/dist/memory-provider.d.ts +128 -0
  104. package/dist/memory-provider.d.ts.map +1 -1
  105. package/dist/memory-provider.js +592 -257
  106. package/dist/memory-provider.js.map +1 -1
  107. package/dist/pg-adapter.d.ts +424 -0
  108. package/dist/pg-adapter.d.ts.map +1 -0
  109. package/dist/pg-adapter.js +921 -0
  110. package/dist/pg-adapter.js.map +1 -0
  111. package/dist/pipelines-iceberg-emitter.d.ts +327 -0
  112. package/dist/pipelines-iceberg-emitter.d.ts.map +1 -0
  113. package/dist/pipelines-iceberg-emitter.js +351 -0
  114. package/dist/pipelines-iceberg-emitter.js.map +1 -0
  115. package/dist/provider-capabilities.d.ts +146 -0
  116. package/dist/provider-capabilities.d.ts.map +1 -0
  117. package/dist/provider-capabilities.js +214 -0
  118. package/dist/provider-capabilities.js.map +1 -0
  119. package/dist/rdb-provider-adapter.d.ts +195 -0
  120. package/dist/rdb-provider-adapter.d.ts.map +1 -0
  121. package/dist/rdb-provider-adapter.js +291 -0
  122. package/dist/rdb-provider-adapter.js.map +1 -0
  123. package/dist/schema/cascade.d.ts +48 -17
  124. package/dist/schema/cascade.d.ts.map +1 -1
  125. package/dist/schema/cascade.js +477 -278
  126. package/dist/schema/cascade.js.map +1 -1
  127. package/dist/schema/definition-caches.d.ts +24 -0
  128. package/dist/schema/definition-caches.d.ts.map +1 -0
  129. package/dist/schema/definition-caches.js +26 -0
  130. package/dist/schema/definition-caches.js.map +1 -0
  131. package/dist/schema/dependency-graph.d.ts +21 -109
  132. package/dist/schema/dependency-graph.d.ts.map +1 -1
  133. package/dist/schema/dependency-graph.js +25 -333
  134. package/dist/schema/dependency-graph.js.map +1 -1
  135. package/dist/schema/diff.d.ts +103 -0
  136. package/dist/schema/diff.d.ts.map +1 -0
  137. package/dist/schema/diff.js +329 -0
  138. package/dist/schema/diff.js.map +1 -0
  139. package/dist/schema/entity-operations.d.ts +99 -0
  140. package/dist/schema/entity-operations.d.ts.map +1 -0
  141. package/dist/schema/entity-operations.js +818 -0
  142. package/dist/schema/entity-operations.js.map +1 -0
  143. package/dist/schema/index.d.ts +28 -34
  144. package/dist/schema/index.d.ts.map +1 -1
  145. package/dist/schema/index.js +454 -521
  146. package/dist/schema/index.js.map +1 -1
  147. package/dist/schema/migration.d.ts +205 -0
  148. package/dist/schema/migration.d.ts.map +1 -0
  149. package/dist/schema/migration.js +327 -0
  150. package/dist/schema/migration.js.map +1 -0
  151. package/dist/schema/nl-query-generator.d.ts +68 -0
  152. package/dist/schema/nl-query-generator.d.ts.map +1 -0
  153. package/dist/schema/nl-query-generator.js +362 -0
  154. package/dist/schema/nl-query-generator.js.map +1 -0
  155. package/dist/schema/nl-query.d.ts +65 -0
  156. package/dist/schema/nl-query.d.ts.map +1 -0
  157. package/dist/schema/nl-query.js +178 -0
  158. package/dist/schema/nl-query.js.map +1 -0
  159. package/dist/schema/parse.d.ts.map +1 -1
  160. package/dist/schema/parse.js +144 -89
  161. package/dist/schema/parse.js.map +1 -1
  162. package/dist/schema/provider.d.ts +37 -0
  163. package/dist/schema/provider.d.ts.map +1 -1
  164. package/dist/schema/provider.js +15 -7
  165. package/dist/schema/provider.js.map +1 -1
  166. package/dist/schema/resolve.d.ts +46 -5
  167. package/dist/schema/resolve.d.ts.map +1 -1
  168. package/dist/schema/resolve.js +237 -95
  169. package/dist/schema/resolve.js.map +1 -1
  170. package/dist/schema/search-utils.d.ts +76 -0
  171. package/dist/schema/search-utils.d.ts.map +1 -0
  172. package/dist/schema/search-utils.js +86 -0
  173. package/dist/schema/search-utils.js.map +1 -0
  174. package/dist/schema/seed.d.ts +53 -0
  175. package/dist/schema/seed.d.ts.map +1 -0
  176. package/dist/schema/seed.js +94 -0
  177. package/dist/schema/seed.js.map +1 -0
  178. package/dist/schema/semantic.d.ts +10 -0
  179. package/dist/schema/semantic.d.ts.map +1 -1
  180. package/dist/schema/semantic.js +192 -86
  181. package/dist/schema/semantic.js.map +1 -1
  182. package/dist/schema/sub-apis.d.ts +52 -0
  183. package/dist/schema/sub-apis.d.ts.map +1 -0
  184. package/dist/schema/sub-apis.js +216 -0
  185. package/dist/schema/sub-apis.js.map +1 -0
  186. package/dist/schema/system-entities.d.ts +42 -0
  187. package/dist/schema/system-entities.d.ts.map +1 -0
  188. package/dist/schema/system-entities.js +101 -0
  189. package/dist/schema/system-entities.js.map +1 -0
  190. package/dist/schema/types.d.ts +91 -9
  191. package/dist/schema/types.d.ts.map +1 -1
  192. package/dist/schema/union-fallback.d.ts.map +1 -1
  193. package/dist/schema/union-fallback.js +21 -15
  194. package/dist/schema/union-fallback.js.map +1 -1
  195. package/dist/schema/value-generators/ai.d.ts +54 -0
  196. package/dist/schema/value-generators/ai.d.ts.map +1 -0
  197. package/dist/schema/value-generators/ai.js +136 -0
  198. package/dist/schema/value-generators/ai.js.map +1 -0
  199. package/dist/schema/value-generators/index.d.ts +126 -0
  200. package/dist/schema/value-generators/index.d.ts.map +1 -0
  201. package/dist/schema/value-generators/index.js +219 -0
  202. package/dist/schema/value-generators/index.js.map +1 -0
  203. package/dist/schema/value-generators/placeholder.d.ts +52 -0
  204. package/dist/schema/value-generators/placeholder.d.ts.map +1 -0
  205. package/dist/schema/value-generators/placeholder.js +328 -0
  206. package/dist/schema/value-generators/placeholder.js.map +1 -0
  207. package/dist/schema/value-generators/types.d.ts +116 -0
  208. package/dist/schema/value-generators/types.d.ts.map +1 -0
  209. package/dist/schema/value-generators/types.js +11 -0
  210. package/dist/schema/value-generators/types.js.map +1 -0
  211. package/dist/schema/version.d.ts +111 -0
  212. package/dist/schema/version.d.ts.map +1 -0
  213. package/dist/schema/version.js +190 -0
  214. package/dist/schema/version.js.map +1 -0
  215. package/dist/schema.d.ts +1095 -24
  216. package/dist/schema.d.ts.map +1 -1
  217. package/dist/schema.js +2852 -40
  218. package/dist/schema.js.map +1 -1
  219. package/dist/semantic-vectors.d.ts +39 -0
  220. package/dist/semantic-vectors.d.ts.map +1 -0
  221. package/dist/semantic-vectors.js +334 -0
  222. package/dist/semantic-vectors.js.map +1 -0
  223. package/dist/semantic.d.ts +29 -1
  224. package/dist/semantic.d.ts.map +1 -1
  225. package/dist/semantic.js +26 -16
  226. package/dist/semantic.js.map +1 -1
  227. package/dist/telemetry.d.ts +128 -0
  228. package/dist/telemetry.d.ts.map +1 -0
  229. package/dist/telemetry.js +305 -0
  230. package/dist/telemetry.js.map +1 -0
  231. package/dist/tests.d.ts.map +1 -1
  232. package/dist/tests.js +30 -22
  233. package/dist/tests.js.map +1 -1
  234. package/dist/type-guards.d.ts +50 -5
  235. package/dist/type-guards.d.ts.map +1 -1
  236. package/dist/type-guards.js +87 -16
  237. package/dist/type-guards.js.map +1 -1
  238. package/dist/types.d.ts +33 -245
  239. package/dist/types.d.ts.map +1 -1
  240. package/dist/types.js +62 -72
  241. package/dist/types.js.map +1 -1
  242. package/dist/validation.d.ts +2 -5
  243. package/dist/validation.d.ts.map +1 -1
  244. package/dist/validation.js +65 -93
  245. package/dist/validation.js.map +1 -1
  246. package/dist/worker/db-provider.d.ts +168 -0
  247. package/dist/worker/db-provider.d.ts.map +1 -0
  248. package/dist/worker/db-provider.js +277 -0
  249. package/dist/worker/db-provider.js.map +1 -0
  250. package/dist/worker/index.d.ts +35 -0
  251. package/dist/worker/index.d.ts.map +1 -0
  252. package/dist/worker/index.js +37 -0
  253. package/dist/worker/index.js.map +1 -0
  254. package/dist/worker.d.ts +779 -0
  255. package/dist/worker.d.ts.map +1 -0
  256. package/dist/worker.js +2786 -0
  257. package/dist/worker.js.map +1 -0
  258. package/package.json +46 -16
  259. package/src/docs-rels/migrations/0001-init.sql +125 -0
  260. package/LICENSE +0 -21
@@ -0,0 +1,745 @@
1
+ /**
2
+ * DO SQLite Adapter — Stack B transactional DBProvider
3
+ *
4
+ * First-class adapter for **Cloudflare Durable Objects with SQLite storage**.
5
+ * Stack B's transactional layer per ADR-0003: per-cascade DO isolation gives
6
+ * parallel write paths each at full single-DO throughput, which is what makes
7
+ * the cascade-heavy moat workload viable.
8
+ *
9
+ * ## Sharding model
10
+ *
11
+ * The adapter accepts a {@link DurableObjectNamespaceLike} binding plus a
12
+ * {@link ShardingStrategy} that selects which DO id receives a given
13
+ * operation. Two canonical strategies ship in this module:
14
+ *
15
+ * - **`'per-cascade'`** (default; the enabling pattern per ADR-0003) —
16
+ * each cascade gets its own DO; reads back during traversal stay inside
17
+ * the same DO (no cross-DO hot-path reads). Caller passes the cascade id
18
+ * via the per-operation `context.cascadeId` (or sets a default cascade
19
+ * on the adapter via {@link DOSqliteAdapter.withCascade}).
20
+ *
21
+ * - **`'per-tenant'`** — alternative for multi-tenant deployments where one
22
+ * tenant runs multiple cascades. Caller passes `context.tenantId`.
23
+ *
24
+ * Custom strategies are accepted as a `(ctx) => string` callback for
25
+ * deployments with their own routing logic (e.g., per-day shards, per-type
26
+ * shards, hash-based shards).
27
+ *
28
+ * ## Constraints (from ADR-0003)
29
+ *
30
+ * - **Per-DO 10GB SQLite limit** — declared via
31
+ * {@link DOSqliteAdapter.maxStorageBytes}. Cascade write strategy
32
+ * (`aip-g1i9`) consults this when fanning out.
33
+ * - **No native vector support** — Tier 4 is sidecared via Cloudflare
34
+ * Vectorize. Pass a `vectorize: VectorizeIndexLike` binding to the
35
+ * constructor and the adapter declares the capability with
36
+ * `implementation: 'sidecar'`. Without it, the capability is
37
+ * `undefined` and `vectorSearch()` throws
38
+ * {@link VectorSearchUnavailableError}.
39
+ * - **Hard to query across DOs** — Tier 3 analytics declared as `false`
40
+ * for all sub-fields. Cross-cascade analytics is the dual-write path
41
+ * (DO → Pipelines → Iceberg → ClickHouse), which is `aip-0ypt`'s concern.
42
+ *
43
+ * ## Wire protocol
44
+ *
45
+ * The adapter speaks to {@link DatabaseDO} via its existing fetch routes
46
+ * (`/data`, `/rels`, `/query/*`, `/traverse`). This keeps the DO surface
47
+ * untouched and matches the existing test infrastructure
48
+ * (`@cloudflare/vitest-pool-workers`).
49
+ *
50
+ * @see {@link ../docs/adr/0003-storage-strategy-pg-clickhouse-default.md}
51
+ * @see {@link ../docs/plans/2026-05-05-cascade-storage-execution-implementation.md} Phase 1
52
+ * @packageDocumentation
53
+ */
54
+ import { VectorSearchUnavailableError } from './errors.js';
55
+ /**
56
+ * Built-in sharding strategies. Callers can also pass any
57
+ * `(ctx) => string` callback.
58
+ *
59
+ * - {@link perCascade} — `ctx.cascadeId`, falling back to a default if
60
+ * provided to the adapter constructor. Throws if neither is set.
61
+ * - {@link perTenant} — `ctx.tenantId`, falling back to default. Throws if
62
+ * neither is set.
63
+ * - {@link perType} — `ctx.type` (every type gets its own DO). Useful for
64
+ * small/dev workloads where cascade isolation isn't yet wired.
65
+ * - {@link unsharded} — always `'__shared__'`. Single DO for everything.
66
+ * Defeats the per-cascade isolation pattern; intended for tests and
67
+ * tiny workloads only.
68
+ */
69
+ export const ShardingStrategies = {
70
+ perCascade: (defaultCascadeId) => {
71
+ return (ctx) => {
72
+ const id = ctx.cascadeId ?? defaultCascadeId;
73
+ if (!id) {
74
+ throw new Error('DOSqliteAdapter (per-cascade strategy): no cascadeId in context. ' +
75
+ 'Set a default via the adapter constructor, or call ' +
76
+ 'adapter.withCascade(cascadeId) before issuing operations.');
77
+ }
78
+ return `cascade:${id}`;
79
+ };
80
+ },
81
+ perTenant: (defaultTenantId) => {
82
+ return (ctx) => {
83
+ const id = ctx.tenantId ?? defaultTenantId;
84
+ if (!id) {
85
+ throw new Error('DOSqliteAdapter (per-tenant strategy): no tenantId in context. ' +
86
+ 'Set a default via the adapter constructor, or call ' +
87
+ 'adapter.withTenant(tenantId) before issuing operations.');
88
+ }
89
+ return `tenant:${id}`;
90
+ };
91
+ },
92
+ perType: () => {
93
+ return (ctx) => {
94
+ if (!ctx.type) {
95
+ throw new Error('DOSqliteAdapter (per-type strategy): no type in context.');
96
+ }
97
+ return `type:${ctx.type}`;
98
+ };
99
+ },
100
+ unsharded: () => {
101
+ return () => '__shared__';
102
+ },
103
+ };
104
+ const ACTION_TYPE = '__svo_action';
105
+ const VERB_TYPE = '__svo_verb';
106
+ // =============================================================================
107
+ // Adapter
108
+ // =============================================================================
109
+ /**
110
+ * DO SQLite first-class adapter.
111
+ *
112
+ * Implements {@link DBProvider} (Tier 1+2), {@link DBProviderSVO} (SVO
113
+ * Action recording + Verb registry), and exposes
114
+ * {@link ProviderTierCapabilities} via the `capabilities` getter.
115
+ *
116
+ * @example Basic per-cascade usage
117
+ * ```ts
118
+ * import { DOSqliteAdapter } from 'ai-database'
119
+ *
120
+ * const adapter = new DOSqliteAdapter({
121
+ * namespace: env.DATABASE,
122
+ * sharding: 'per-cascade',
123
+ * defaultCascadeId: cascade.id,
124
+ * })
125
+ *
126
+ * // Per-op context overrides
127
+ * const customer = await adapter
128
+ * .withCascade(cascade.id)
129
+ * .create('Customer', undefined, { name: 'Acme' })
130
+ * ```
131
+ *
132
+ * @example Per-tenant strategy
133
+ * ```ts
134
+ * const adapter = new DOSqliteAdapter({
135
+ * namespace: env.DATABASE,
136
+ * sharding: 'per-tenant',
137
+ * defaultTenantId: 'acme',
138
+ * })
139
+ * ```
140
+ *
141
+ * @example Custom strategy
142
+ * ```ts
143
+ * const adapter = new DOSqliteAdapter({
144
+ * namespace: env.DATABASE,
145
+ * sharding: (ctx) => `${ctx.tenantId}:${ctx.cascadeId}`,
146
+ * })
147
+ * ```
148
+ */
149
+ export class DOSqliteAdapter {
150
+ namespace;
151
+ strategy;
152
+ shardingModel;
153
+ baseContext;
154
+ vectorize;
155
+ vectorizeDimensions;
156
+ vectorizeNamespace;
157
+ /**
158
+ * The per-DO SQLite hard cap from Cloudflare. Declared as `getter` so
159
+ * future Cloudflare changes to the cap can be reflected in one place.
160
+ * Cascade write strategy (`aip-g1i9`) consults this when sizing fan-out.
161
+ */
162
+ maxStorageBytes = 10 * 1024 * 1024 * 1024; // 10 GB
163
+ constructor(options) {
164
+ if (!options.namespace) {
165
+ throw new Error('DOSqliteAdapter: `namespace` (a DurableObjectNamespace binding) is required.');
166
+ }
167
+ this.namespace = options.namespace;
168
+ // Resolve sharding strategy
169
+ const sharding = options.sharding ?? 'per-cascade';
170
+ if (typeof sharding === 'function') {
171
+ this.strategy = sharding;
172
+ this.shardingModel = 'per-cascade'; // unknown; default declaration
173
+ }
174
+ else {
175
+ switch (sharding) {
176
+ case 'per-cascade':
177
+ this.strategy = ShardingStrategies.perCascade(options.defaultCascadeId);
178
+ this.shardingModel = 'per-cascade';
179
+ break;
180
+ case 'per-tenant':
181
+ this.strategy = ShardingStrategies.perTenant(options.defaultTenantId);
182
+ this.shardingModel = 'partitioned-by-tenant';
183
+ break;
184
+ case 'per-type':
185
+ this.strategy = ShardingStrategies.perType();
186
+ this.shardingModel = 'per-type';
187
+ break;
188
+ case 'unsharded':
189
+ this.strategy = ShardingStrategies.unsharded();
190
+ this.shardingModel = 'unsharded';
191
+ break;
192
+ default:
193
+ throw new Error(`DOSqliteAdapter: unknown sharding strategy "${String(sharding)}"`);
194
+ }
195
+ }
196
+ this.baseContext = { ...(options.defaultContext ?? {}) };
197
+ if (options.defaultCascadeId !== undefined) {
198
+ this.baseContext.cascadeId = options.defaultCascadeId;
199
+ }
200
+ if (options.defaultTenantId !== undefined) {
201
+ this.baseContext.tenantId = options.defaultTenantId;
202
+ }
203
+ this.vectorize = options.vectorize;
204
+ this.vectorizeDimensions = options.vectorizeDimensions ?? 1536;
205
+ this.vectorizeNamespace = options.vectorizeNamespace;
206
+ }
207
+ // ===========================================================================
208
+ // Capability declaration (Tier model from ADR-0003)
209
+ // ===========================================================================
210
+ /**
211
+ * Tier capability declaration for this adapter.
212
+ *
213
+ * Per ADR-0003:
214
+ * - **Sharding**: `per-cascade` (default; the enabling pattern). Switches
215
+ * to `partitioned-by-tenant` when constructed with `'per-tenant'` or
216
+ * `unsharded` when constructed with `'unsharded'`.
217
+ * - **Tier 3 analytics**: declared `false` across the board. Cross-DO
218
+ * queries are hard; aggregations are the dual-write/Iceberg path.
219
+ * - **Tier 4 vector search**: `undefined`. DO SQLite has no native
220
+ * vectors; callers wire a Vectorize sidecar (per `aip-kh9l`).
221
+ * - **SVO Action recording / Verb registry**: both `true`. Stored as
222
+ * reserved entity types (`__svo_action`, `__svo_verb`) inside each DO,
223
+ * matching the per-cascade isolation pattern (Action records live in
224
+ * the same DO as the entities they reference).
225
+ */
226
+ get capabilities() {
227
+ const caps = {
228
+ adapter: 'do-sqlite',
229
+ shardingModel: this.shardingModel,
230
+ analytics: {
231
+ hasAggregations: false,
232
+ hasTimeSeries: false,
233
+ hasLargeScans: false,
234
+ },
235
+ hasActionRecording: true,
236
+ hasVerbRegistry: true,
237
+ };
238
+ // Tier 4 is opt-in via a Vectorize sidecar binding. When present we
239
+ // declare the capability with `implementation: 'sidecar'` so callers
240
+ // can distinguish from native (PG/CH).
241
+ if (this.vectorize) {
242
+ caps.vectorSearch = {
243
+ maxDimensions: this.vectorizeDimensions,
244
+ metrics: ['cosine'],
245
+ implementation: 'sidecar',
246
+ };
247
+ }
248
+ return caps;
249
+ }
250
+ // ===========================================================================
251
+ // Context derivation — `withCascade`/`withTenant`/`withContext` produce a
252
+ // bound view of the adapter that fills in shard context per op. They
253
+ // return DBProvider+SVO without re-instantiating the underlying namespace.
254
+ // ===========================================================================
255
+ /**
256
+ * Return an adapter bound to `cascadeId` for subsequent operations.
257
+ * Useful when the surrounding code has a stable cascade scope but the
258
+ * adapter was constructed without one.
259
+ *
260
+ * Returns a new bound adapter; the original is unchanged.
261
+ */
262
+ withCascade(cascadeId) {
263
+ return this.cloneWithContext({ cascadeId });
264
+ }
265
+ /**
266
+ * Return an adapter bound to `tenantId` for subsequent operations.
267
+ */
268
+ withTenant(tenantId) {
269
+ return this.cloneWithContext({ tenantId });
270
+ }
271
+ /**
272
+ * Return an adapter bound to a free-form context. Lets callers route to
273
+ * a custom shard strategy without modifying the strategy itself.
274
+ */
275
+ withContext(context) {
276
+ return this.cloneWithContext(context);
277
+ }
278
+ cloneWithContext(extra) {
279
+ const clone = Object.create(DOSqliteAdapter.prototype);
280
+ Object.assign(clone, {
281
+ namespace: this.namespace,
282
+ strategy: this.strategy,
283
+ shardingModel: this.shardingModel,
284
+ baseContext: { ...this.baseContext, ...extra },
285
+ maxStorageBytes: this.maxStorageBytes,
286
+ vectorize: this.vectorize,
287
+ vectorizeDimensions: this.vectorizeDimensions,
288
+ vectorizeNamespace: this.vectorizeNamespace,
289
+ });
290
+ return clone;
291
+ }
292
+ // ===========================================================================
293
+ // Internal: shard resolution + fetch
294
+ // ===========================================================================
295
+ resolveStub(ctx) {
296
+ const merged = { ...this.baseContext, ...ctx };
297
+ const idName = this.strategy(merged);
298
+ const id = this.namespace.idFromName(idName);
299
+ return this.namespace.get(id);
300
+ }
301
+ async doFetch(ctx, path, init) {
302
+ const stub = this.resolveStub(ctx);
303
+ const url = `https://do.test${path}`;
304
+ const response = await stub.fetch(url, init);
305
+ return this.parseResponse(response, path);
306
+ }
307
+ async parseResponse(response, path) {
308
+ const text = await response.text();
309
+ let parsed;
310
+ try {
311
+ parsed = text.length > 0 ? JSON.parse(text) : null;
312
+ }
313
+ catch {
314
+ throw new Error(`DOSqliteAdapter: ${path} returned non-JSON (status ${response.status}): ${text.slice(0, 200)}`);
315
+ }
316
+ if (!response.ok) {
317
+ const err = parsed?.error ?? `status ${response.status}`;
318
+ throw new Error(`DOSqliteAdapter: ${path} failed: ${err}`);
319
+ }
320
+ return parsed;
321
+ }
322
+ static normalizeEntity(row) {
323
+ const data = row.data ?? {};
324
+ return {
325
+ ...data,
326
+ $id: row.id,
327
+ $type: row.type,
328
+ createdAt: row.created_at,
329
+ updatedAt: row.updated_at,
330
+ };
331
+ }
332
+ static jsonInit(method, body) {
333
+ return {
334
+ method,
335
+ headers: { 'Content-Type': 'application/json' },
336
+ body: JSON.stringify(body),
337
+ };
338
+ }
339
+ // ===========================================================================
340
+ // Tier 1 — entity CRUD
341
+ // ===========================================================================
342
+ async get(type, id) {
343
+ const stub = this.resolveStub({ type });
344
+ const response = await stub.fetch(`https://do.test/data/${encodeURIComponent(id)}`);
345
+ if (response.status === 404)
346
+ return null;
347
+ const row = await this.parseResponse(response, `/data/${id}`);
348
+ if (row.type !== type)
349
+ return null;
350
+ return DOSqliteAdapter.normalizeEntity(row);
351
+ }
352
+ async list(type, options) {
353
+ const body = { type };
354
+ if (options?.where !== undefined)
355
+ body['where'] = options.where;
356
+ if (options?.orderBy !== undefined)
357
+ body['orderBy'] = options.orderBy;
358
+ if (options?.order !== undefined)
359
+ body['order'] = options.order;
360
+ if (options?.limit !== undefined)
361
+ body['limit'] = options.limit;
362
+ if (options?.offset !== undefined)
363
+ body['offset'] = options.offset;
364
+ const rows = await this.doFetch({ type }, '/query/list', DOSqliteAdapter.jsonInit('POST', body));
365
+ return rows.map(DOSqliteAdapter.normalizeEntity);
366
+ }
367
+ async search(type, query, options) {
368
+ const body = { type, query };
369
+ if (options?.fields !== undefined)
370
+ body['fields'] = options.fields;
371
+ if (options?.limit !== undefined)
372
+ body['limit'] = options.limit;
373
+ if (options?.minScore !== undefined)
374
+ body['minScore'] = options.minScore;
375
+ const rows = await this.doFetch({ type }, '/query/search', DOSqliteAdapter.jsonInit('POST', body));
376
+ let results = rows.map(DOSqliteAdapter.normalizeEntity);
377
+ // Apply where filter client-side (DO /query/search doesn't support where).
378
+ if (options?.where && Object.keys(options.where).length > 0) {
379
+ const where = options.where;
380
+ results = results.filter((entity) => {
381
+ for (const [key, value] of Object.entries(where)) {
382
+ if (entity[key] !== value)
383
+ return false;
384
+ }
385
+ return true;
386
+ });
387
+ }
388
+ if (options?.offset)
389
+ results = results.slice(options.offset);
390
+ if (options?.limit !== undefined)
391
+ results = results.slice(0, options.limit);
392
+ return results;
393
+ }
394
+ async create(type, id, data) {
395
+ const body = { type, data };
396
+ if (id !== undefined)
397
+ body['id'] = id;
398
+ const row = await this.doFetch({ type }, '/data', DOSqliteAdapter.jsonInit('POST', body));
399
+ return DOSqliteAdapter.normalizeEntity(row);
400
+ }
401
+ async update(type, id, data) {
402
+ const row = await this.doFetch({ type }, `/data/${encodeURIComponent(id)}`, DOSqliteAdapter.jsonInit('PATCH', { data }));
403
+ return DOSqliteAdapter.normalizeEntity(row);
404
+ }
405
+ async delete(type, id) {
406
+ const result = await this.doFetch({ type }, `/data/${encodeURIComponent(id)}`, { method: 'DELETE' });
407
+ return result.deleted === true;
408
+ }
409
+ // ===========================================================================
410
+ // Tier 2 — relationships / graph traversal
411
+ // ===========================================================================
412
+ async related(type, id, relation) {
413
+ const params = new URLSearchParams({ from_id: id, relation });
414
+ const rows = await this.doFetch({ type }, `/traverse?${params.toString()}`);
415
+ return rows.map(DOSqliteAdapter.normalizeEntity);
416
+ }
417
+ async relate(fromType, fromId, relation, toType, toId, metadata) {
418
+ const body = { from_id: fromId, relation, to_id: toId };
419
+ if (metadata !== undefined)
420
+ body['metadata'] = { ...metadata, fromType, toType };
421
+ await this.doFetch({ type: fromType }, '/rels', DOSqliteAdapter.jsonInit('POST', body));
422
+ }
423
+ async unrelate(fromType, fromId, relation, _toType, toId) {
424
+ await this.doFetch({ type: fromType }, '/rels/delete', DOSqliteAdapter.jsonInit('DELETE', { from_id: fromId, relation, to_id: toId }));
425
+ }
426
+ // ===========================================================================
427
+ // SVO surface — Action recording
428
+ // ===========================================================================
429
+ //
430
+ // Actions are stored as reserved-type entities (`__svo_action`) inside the
431
+ // shard the action's subject lives in. This honours per-cascade isolation:
432
+ // an Action recorded as part of a cascade lives in that cascade's DO,
433
+ // alongside the entities it references.
434
+ //
435
+ // Verbs are stored similarly under `__svo_verb`. Because each shard has
436
+ // its own SQLite DB, the Verb registry is **per-shard** — different shards
437
+ // can hold independent Verb sets. For most cascade workloads that's the
438
+ // correct shape (cascades are short-lived). Long-lived workloads that
439
+ // need a global Verb registry should layer it via `digital-objects`
440
+ // upstream of the adapter.
441
+ actionRouteContext(input) {
442
+ // Route Actions to the shard of the subject if available, else object,
443
+ // else the default context. We don't carry a `type` here — the Action
444
+ // type itself is reserved.
445
+ return { type: ACTION_TYPE, subjectId: input.subject, objectId: input.object };
446
+ }
447
+ async recordAction(input) {
448
+ const id = crypto.randomUUID();
449
+ const createdAt = new Date();
450
+ const status = input.status ?? 'completed';
451
+ const data = {
452
+ verb: input.verb,
453
+ status,
454
+ };
455
+ if (input.subject !== undefined)
456
+ data['subject'] = input.subject;
457
+ if (input.object !== undefined)
458
+ data['object'] = input.object;
459
+ if (input.roles !== undefined)
460
+ data['roles'] = input.roles;
461
+ if (input.data !== undefined)
462
+ data['data'] = input.data;
463
+ if (status === 'completed' || status === 'failed' || status === 'cancelled') {
464
+ data['completedAt'] = createdAt.toISOString();
465
+ }
466
+ const ctx = this.actionRouteContext({
467
+ ...(input.subject !== undefined && { subject: input.subject }),
468
+ ...(input.object !== undefined && { object: input.object }),
469
+ });
470
+ const row = await this.doFetch(ctx, '/data', DOSqliteAdapter.jsonInit('POST', { id, type: ACTION_TYPE, data }));
471
+ const persistedData = (row.data ?? {});
472
+ const action = {
473
+ id: row.id,
474
+ verb: persistedData['verb'],
475
+ status: persistedData['status'] ?? 'completed',
476
+ createdAt: new Date(row.created_at),
477
+ ...(persistedData['subject'] !== undefined && {
478
+ subject: persistedData['subject'],
479
+ }),
480
+ ...(persistedData['object'] !== undefined && {
481
+ object: persistedData['object'],
482
+ }),
483
+ ...(persistedData['roles'] !== undefined && {
484
+ roles: persistedData['roles'],
485
+ }),
486
+ ...(persistedData['data'] !== undefined && { data: persistedData['data'] }),
487
+ ...(persistedData['completedAt'] !== undefined && {
488
+ completedAt: new Date(persistedData['completedAt']),
489
+ }),
490
+ };
491
+ return action;
492
+ }
493
+ async queryActions(query) {
494
+ // List all action records on the resolved shard. Filtering is applied
495
+ // client-side for simplicity (per-DO SVO records remain small relative
496
+ // to entity volume).
497
+ const where = {};
498
+ if (query?.verb !== undefined)
499
+ where['data.verb'] = query.verb;
500
+ const ctx = this.actionRouteContext({
501
+ ...(query?.subject !== undefined && { subject: query.subject }),
502
+ ...(query?.object !== undefined && { object: query.object }),
503
+ });
504
+ const rows = await this.doFetch(ctx, '/query/list', DOSqliteAdapter.jsonInit('POST', { type: ACTION_TYPE }));
505
+ let actions = rows.map((row) => {
506
+ const data = (row.data ?? {});
507
+ const action = {
508
+ id: row.id,
509
+ verb: data['verb'],
510
+ status: data['status'] ?? 'completed',
511
+ createdAt: new Date(row.created_at),
512
+ ...(data['subject'] !== undefined && { subject: data['subject'] }),
513
+ ...(data['object'] !== undefined && { object: data['object'] }),
514
+ ...(data['roles'] !== undefined && {
515
+ roles: data['roles'],
516
+ }),
517
+ ...(data['data'] !== undefined && { data: data['data'] }),
518
+ ...(data['completedAt'] !== undefined && {
519
+ completedAt: new Date(data['completedAt']),
520
+ }),
521
+ };
522
+ return action;
523
+ });
524
+ if (query?.verb !== undefined) {
525
+ actions = actions.filter((a) => a.verb === query.verb);
526
+ }
527
+ if (query?.subject !== undefined) {
528
+ actions = actions.filter((a) => a.subject === query.subject);
529
+ }
530
+ if (query?.object !== undefined) {
531
+ actions = actions.filter((a) => a.object === query.object);
532
+ }
533
+ if (query?.role) {
534
+ const role = query.role;
535
+ actions = actions.filter((a) => {
536
+ for (const [key, value] of Object.entries(role)) {
537
+ if (key === 'subject') {
538
+ if (a.subject !== value)
539
+ return false;
540
+ }
541
+ else if (key === 'object') {
542
+ if (a.object !== value)
543
+ return false;
544
+ }
545
+ else {
546
+ const slot = (a.roles ?? {});
547
+ if (slot[key] !== value)
548
+ return false;
549
+ }
550
+ }
551
+ return true;
552
+ });
553
+ }
554
+ if (query?.status !== undefined) {
555
+ const want = Array.isArray(query.status) ? new Set(query.status) : new Set([query.status]);
556
+ actions = actions.filter((a) => want.has(a.status));
557
+ }
558
+ if (query?.since !== undefined) {
559
+ actions = actions.filter((a) => a.createdAt >= query.since);
560
+ }
561
+ if (query?.until !== undefined) {
562
+ actions = actions.filter((a) => a.createdAt <= query.until);
563
+ }
564
+ actions.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
565
+ if (query?.offset !== undefined)
566
+ actions = actions.slice(query.offset);
567
+ if (query?.limit !== undefined)
568
+ actions = actions.slice(0, query.limit);
569
+ return actions;
570
+ }
571
+ // ===========================================================================
572
+ // SVO surface — Verb registry
573
+ // ===========================================================================
574
+ async defineVerb(def) {
575
+ const existing = await this.getVerb(def.name);
576
+ if (existing) {
577
+ // Idempotent re-registration is allowed when conjugations match;
578
+ // reject when they conflict.
579
+ const conflict = (def.action !== undefined && def.action !== existing.action) ||
580
+ (def.act !== undefined && def.act !== existing.act) ||
581
+ (def.activity !== undefined && def.activity !== existing.activity);
582
+ if (conflict) {
583
+ throw new Error(`DOSqliteAdapter.defineVerb: verb "${def.name}" already registered with conflicting conjugations`);
584
+ }
585
+ return existing;
586
+ }
587
+ const action = def.action ?? def.name;
588
+ const act = def.act ?? `${action}s`;
589
+ const activity = def.activity ?? `${action}ing`;
590
+ const event = def.event ?? action;
591
+ const data = {
592
+ name: def.name,
593
+ action,
594
+ act,
595
+ activity,
596
+ event,
597
+ ...(def.reverseBy !== undefined && { reverseBy: def.reverseBy }),
598
+ ...(def.reverseAt !== undefined && { reverseAt: def.reverseAt }),
599
+ ...(def.reverseIn !== undefined && { reverseIn: def.reverseIn }),
600
+ ...(def.inverse !== undefined && { inverse: def.inverse }),
601
+ ...(def.description !== undefined && { description: def.description }),
602
+ ...(def.frame !== undefined && { frame: def.frame }),
603
+ ...(def.source !== undefined && { source: def.source }),
604
+ ...(def.canonical !== undefined && { canonical: def.canonical }),
605
+ };
606
+ const ctx = { type: VERB_TYPE };
607
+ const row = await this.doFetch(ctx, '/data', DOSqliteAdapter.jsonInit('POST', { id: def.name, type: VERB_TYPE, data }));
608
+ return DOSqliteAdapter.toVerbRecord(row);
609
+ }
610
+ async getVerb(name) {
611
+ const ctx = { type: VERB_TYPE };
612
+ const stub = this.resolveStub(ctx);
613
+ const response = await stub.fetch(`https://do.test/data/${encodeURIComponent(name)}`);
614
+ if (response.status === 404)
615
+ return null;
616
+ const row = await this.parseResponse(response, `/data/${name}`);
617
+ if (row.type !== VERB_TYPE)
618
+ return null;
619
+ return DOSqliteAdapter.toVerbRecord(row);
620
+ }
621
+ async listVerbs() {
622
+ const ctx = { type: VERB_TYPE };
623
+ const rows = await this.doFetch(ctx, '/query/list', DOSqliteAdapter.jsonInit('POST', { type: VERB_TYPE }));
624
+ return rows.map(DOSqliteAdapter.toVerbRecord);
625
+ }
626
+ // ===========================================================================
627
+ // Tier 4 — vector search (Vectorize sidecar)
628
+ // ===========================================================================
629
+ /**
630
+ * Vector search via the Cloudflare Vectorize sidecar binding.
631
+ *
632
+ * - When the adapter was constructed **without** a `vectorize` binding,
633
+ * this throws {@link VectorSearchUnavailableError}.
634
+ * - When **with** a binding, the binding's `query()` is invoked with
635
+ * `topK = options.limit ?? 10` and `returnMetadata: true` so each hit
636
+ * carries enough context to reconstruct an entity. The vector id
637
+ * returned by Vectorize is treated as the Thing id; if the index
638
+ * stores the Thing's `data` in `metadata`, the result entity is
639
+ * composed from that — otherwise a fallback `get()` against the
640
+ * resolved DO shard fills in the data. Dimensions and metrics are
641
+ * determined by how the index was provisioned upstream; we pass the
642
+ * caller's `metric` through unchanged for documentation purposes
643
+ * (Vectorize doesn't accept a per-query metric).
644
+ *
645
+ * Frame-aware role filtering is **deferred** to a refinement bead;
646
+ * Vectorize's `filter` parameter could carry role-shaped metadata
647
+ * filters, but the wire shape needs design with how callers seed the
648
+ * index, which is out of scope here.
649
+ */
650
+ async vectorSearch(type, queryEmbedding, options) {
651
+ if (!this.vectorize) {
652
+ throw new VectorSearchUnavailableError('do-sqlite', 'no Vectorize binding configured (pass `vectorize` to DOSqliteAdapter constructor)');
653
+ }
654
+ if (!Array.isArray(queryEmbedding) || queryEmbedding.length === 0) {
655
+ throw new Error('vectorSearch: queryEmbedding must be a non-empty array of numbers');
656
+ }
657
+ for (const v of queryEmbedding) {
658
+ if (typeof v !== 'number' || !Number.isFinite(v)) {
659
+ throw new Error('vectorSearch: queryEmbedding values must be finite numbers');
660
+ }
661
+ }
662
+ // The metric option is a hint only — Vectorize indexes pin a metric at
663
+ // creation time. We accept it for API parity with PG/CH but don't
664
+ // forward it to the binding.
665
+ void options?.metric;
666
+ const limit = Math.max(1, options?.limit ?? 10);
667
+ const queryOptions = { topK: limit, returnMetadata: true };
668
+ if (this.vectorizeNamespace !== undefined) {
669
+ queryOptions.namespace = this.vectorizeNamespace;
670
+ }
671
+ const result = await this.vectorize.query(queryEmbedding, queryOptions);
672
+ const matches = result?.matches ?? [];
673
+ let hits = [];
674
+ for (const m of matches) {
675
+ const meta = (m.metadata ?? {});
676
+ // Prefer entity payload from index metadata when callers stored it;
677
+ // otherwise fall back to fetching the row from the resolved shard.
678
+ let entity = null;
679
+ const metaType = typeof meta['type'] === 'string' ? meta['type'] : undefined;
680
+ const metaData = typeof meta['data'] === 'object' && meta['data'] !== null
681
+ ? meta['data']
682
+ : undefined;
683
+ if (metaType === type && metaData) {
684
+ entity = { ...metaData, $id: m.id, $type: type };
685
+ }
686
+ else if (metaType === undefined && metaData === undefined) {
687
+ // Index didn't store metadata — read-back from the DO shard.
688
+ const fetched = (await this.get(type, m.id));
689
+ if (fetched) {
690
+ entity = fetched;
691
+ }
692
+ }
693
+ // Skip matches that aren't of the requested type.
694
+ if (!entity)
695
+ continue;
696
+ hits.push({ entity, score: m.score });
697
+ }
698
+ if (options?.minScore !== undefined) {
699
+ const min = options.minScore;
700
+ hits = hits.filter((h) => h.score >= min);
701
+ }
702
+ return hits;
703
+ }
704
+ static toVerbRecord(row) {
705
+ const data = (row.data ?? {});
706
+ return {
707
+ name: data['name'] ?? row.id,
708
+ action: data['action'],
709
+ act: data['act'],
710
+ activity: data['activity'],
711
+ event: data['event'],
712
+ ...(data['reverseBy'] !== undefined && { reverseBy: data['reverseBy'] }),
713
+ ...(data['reverseAt'] !== undefined && { reverseAt: data['reverseAt'] }),
714
+ ...(data['reverseIn'] !== undefined && { reverseIn: data['reverseIn'] }),
715
+ ...(data['inverse'] !== undefined && { inverse: data['inverse'] }),
716
+ ...(data['description'] !== undefined && {
717
+ description: data['description'],
718
+ }),
719
+ ...(data['frame'] !== undefined && {
720
+ frame: data['frame'],
721
+ }),
722
+ ...(data['source'] !== undefined && { source: data['source'] }),
723
+ ...(data['canonical'] !== undefined && { canonical: data['canonical'] }),
724
+ createdAt: new Date(row.created_at),
725
+ };
726
+ }
727
+ }
728
+ /**
729
+ * Convenience factory: create a {@link DOSqliteAdapter}.
730
+ *
731
+ * @example
732
+ * ```ts
733
+ * import { createDOSqliteAdapter } from 'ai-database'
734
+ *
735
+ * const adapter = createDOSqliteAdapter({
736
+ * namespace: env.DATABASE,
737
+ * sharding: 'per-cascade',
738
+ * defaultCascadeId: cascade.id,
739
+ * })
740
+ * ```
741
+ */
742
+ export function createDOSqliteAdapter(options) {
743
+ return new DOSqliteAdapter(options);
744
+ }
745
+ //# sourceMappingURL=do-sqlite-adapter.js.map