@firtoz/drizzle-durable-sqlite 1.0.4 → 2.1.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 (52) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +9 -1
  3. package/dist/chunk-3XAF3IZW.js +206 -0
  4. package/dist/chunk-3XAF3IZW.js.map +1 -0
  5. package/dist/chunk-DTRYQ3SF.js +36 -0
  6. package/dist/chunk-DTRYQ3SF.js.map +1 -0
  7. package/dist/chunk-JHZRO76J.js +288 -0
  8. package/dist/chunk-JHZRO76J.js.map +1 -0
  9. package/dist/chunk-NJ7RJSGZ.js +50 -0
  10. package/dist/chunk-NJ7RJSGZ.js.map +1 -0
  11. package/dist/chunk-ONDKTPGP.js +144 -0
  12. package/dist/chunk-ONDKTPGP.js.map +1 -0
  13. package/dist/chunk-PA5TYHIW.js +73 -0
  14. package/dist/chunk-PA5TYHIW.js.map +1 -0
  15. package/dist/chunk-QHM6T5OI.js +105 -0
  16. package/dist/chunk-QHM6T5OI.js.map +1 -0
  17. package/dist/chunk-YA4MAETI.js +47 -0
  18. package/dist/chunk-YA4MAETI.js.map +1 -0
  19. package/dist/drizzle-mutation-store.d.ts +20 -0
  20. package/dist/drizzle-mutation-store.js +3 -0
  21. package/dist/drizzle-mutation-store.js.map +1 -0
  22. package/dist/drizzle-partial-sync-changelog.d.ts +20 -0
  23. package/dist/drizzle-partial-sync-changelog.js +3 -0
  24. package/dist/drizzle-partial-sync-changelog.js.map +1 -0
  25. package/dist/drizzle-partial-sync-store.d.ts +22 -0
  26. package/dist/drizzle-partial-sync-store.js +4 -0
  27. package/dist/drizzle-partial-sync-store.js.map +1 -0
  28. package/dist/durable-sqlite-collection.d.ts +41 -0
  29. package/dist/durable-sqlite-collection.js +3 -0
  30. package/dist/durable-sqlite-collection.js.map +1 -0
  31. package/dist/durable-sqlite-sync-server.d.ts +48 -0
  32. package/dist/durable-sqlite-sync-server.js +3 -0
  33. package/dist/durable-sqlite-sync-server.js.map +1 -0
  34. package/dist/index.d.ts +20 -0
  35. package/dist/index.js +10 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/partial-sync-predicate-sql.d.ts +29 -0
  38. package/dist/partial-sync-predicate-sql.js +3 -0
  39. package/dist/partial-sync-predicate-sql.js.map +1 -0
  40. package/dist/partial-sync-sqlite-db.d.ts +9 -0
  41. package/dist/partial-sync-sqlite-db.js +3 -0
  42. package/dist/partial-sync-sqlite-db.js.map +1 -0
  43. package/dist/queryable-durable-object.d.ts +128 -0
  44. package/dist/queryable-durable-object.js +3 -0
  45. package/dist/queryable-durable-object.js.map +1 -0
  46. package/dist/syncable-durable-object.d.ts +58 -0
  47. package/dist/syncable-durable-object.js +4 -0
  48. package/dist/syncable-durable-object.js.map +1 -0
  49. package/package.json +22 -20
  50. package/src/durable-sqlite-collection.ts +4 -4
  51. package/src/queryable-durable-object.ts +15 -12
  52. package/src/syncable-durable-object.ts +16 -13
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @firtoz/drizzle-durable-sqlite
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`c3a3cc7`](https://github.com/firtoz/fullstack-toolkit/commit/c3a3cc778ba9ce4b5efe1bcdd8d541f46dec3bfd) Thanks [@firtoz](https://github.com/firtoz)! - Publish compiled ESM and TypeScript declarations from `dist/` for each package (`tsup`, `prepack` build). `exports`, `main`, and `types` resolve to `dist/`; CLI bins point at built JS with a Node shebang where applicable. Shared `scripts/tsup-lib.ts` discovers `src` entries and externals; workspace test apps map `@firtoz/*` to package sources in `tsconfig` for accurate generics during typecheck. `@firtoz/socka` is published under the scoped name with the same `dist/` layout.
8
+
9
+ ### Patch Changes
10
+
11
+ - [`f78c988`](https://github.com/firtoz/fullstack-toolkit/commit/f78c988d37a9cc48490ee3372dccc14a42810bfe) Thanks [@firtoz](https://github.com/firtoz)! - Improve READMEs: npm shields, contextual stack badges, clearer taglines, and new docs for `@firtoz/idb-collections` and `@firtoz/collection-sync`. Align `@firtoz/db-helpers` copy with published `dist/` builds.
12
+
13
+ - Updated dependencies [[`c3a3cc7`](https://github.com/firtoz/fullstack-toolkit/commit/c3a3cc778ba9ce4b5efe1bcdd8d541f46dec3bfd), [`f78c988`](https://github.com/firtoz/fullstack-toolkit/commit/f78c988d37a9cc48490ee3372dccc14a42810bfe)]:
14
+ - @firtoz/collection-sync@6.0.0
15
+ - @firtoz/db-helpers@2.2.0
16
+ - @firtoz/drizzle-utils@1.3.0
17
+ - @firtoz/maybe-error@1.6.0
18
+ - @firtoz/websocket-do@13.0.0
19
+
20
+ ## 2.0.0
21
+
22
+ ### Major Changes
23
+
24
+ - [#70](https://github.com/firtoz/fullstack-toolkit/pull/70) [`e1c08cb`](https://github.com/firtoz/fullstack-toolkit/commit/e1c08cb803574654d5808a984e358258c4171698) Thanks [@firtoz](https://github.com/firtoz)! - **@firtoz/websocket-do:** `BaseSessionHandlers.handleClose` and `StandardSchemaSessionHandlers.handleClose` receive the session instance (aligned with DO teardown).
25
+
26
+ **@firtoz/drizzle-durable-sqlite:** `handleClose` handlers on bundled DO session wiring match the session-aware `BaseSession` contract.
27
+
28
+ ### Patch Changes
29
+
30
+ - [#70](https://github.com/firtoz/fullstack-toolkit/pull/70) [`d35e718`](https://github.com/firtoz/fullstack-toolkit/commit/d35e718bf3292258c2b0006affc7aad5ecc35208) Thanks [@firtoz](https://github.com/firtoz)! - **@firtoz/websocket-do:** Replace Zod-only `ZodSession`, `ZodWebSocketClient`, `ZodWebSocketDO`, and `zodMsgpack` with Standard Schema v1–based `StandardSchemaSession`, `StandardSchemaWebSocketClient`, `StandardSchemaWebSocketDO`, and `standardSchemaMsgpack`. Add `parseStandardSchema` and a direct dependency on `@standard-schema/spec`. Subpath `./zod-client` is removed; use `./schema-client`. Client `send` is now async (`Promise<void>`). Server session `send`/`broadcast` stay `void` with async validation under the hood. Remove the experimental `@firtoz/websocket-do/ws-rpc-protocol` export; use **`socka/core`** (`defineSocka`, typed RPC) instead.
31
+
32
+ **@firtoz/collection-sync:** `connectSync` / `connect-partial-sync` now use `StandardSchemaWebSocketClient` from `@firtoz/websocket-do/schema-client`.
33
+
34
+ **@firtoz/drizzle-durable-sqlite:** `SyncableDurableObject` and `QueryableDurableObject` extend `StandardSchemaWebSocketDO` / `StandardSchemaSession` and use `createStandardSchemaSession` / `standardSchemaSessionOptions` in constructors.
35
+
36
+ - Updated dependencies [[`ffee5b3`](https://github.com/firtoz/fullstack-toolkit/commit/ffee5b313d073366a10e049dc988c9a9c95719be), [`7eb49ad`](https://github.com/firtoz/fullstack-toolkit/commit/7eb49adb100ffc5187a1f858b013b151db82643f), [`e1c08cb`](https://github.com/firtoz/fullstack-toolkit/commit/e1c08cb803574654d5808a984e358258c4171698), [`d35e718`](https://github.com/firtoz/fullstack-toolkit/commit/d35e718bf3292258c2b0006affc7aad5ecc35208), [`138c394`](https://github.com/firtoz/fullstack-toolkit/commit/138c3944b491ebf2e76b7f2c00d651fd5d788bac), [`d35e718`](https://github.com/firtoz/fullstack-toolkit/commit/d35e718bf3292258c2b0006affc7aad5ecc35208)]:
37
+ - @firtoz/websocket-do@12.0.0
38
+ - @firtoz/collection-sync@5.0.0
39
+ - @firtoz/db-helpers@2.1.1
40
+ - @firtoz/drizzle-utils@1.2.1
41
+
3
42
  ## 1.0.4
4
43
 
5
44
  ### Patch Changes
package/README.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # @firtoz/drizzle-durable-sqlite
2
2
 
3
- TanStack DB collection configuration for **Drizzle ORM** on **Cloudflare Durable Object SQLite** (`drizzle-orm/durable-sqlite`). This mirrors [`@firtoz/drizzle-sqlite-wasm`](../drizzle-sqlite-wasm) for the browser (SQLite WASM + workers), but targets Workers/DOs only—no React provider, no OPFS, no Web Workers.
3
+ [![npm version](https://img.shields.io/npm/v/%40firtoz%2Fdrizzle-durable-sqlite.svg)](https://www.npmjs.com/package/@firtoz/drizzle-durable-sqlite)
4
+ [![npm downloads](https://img.shields.io/npm/dm/%40firtoz%2Fdrizzle-durable-sqlite.svg)](https://www.npmjs.com/package/@firtoz/drizzle-durable-sqlite)
5
+ [![license](https://img.shields.io/npm/l/%40firtoz%2Fdrizzle-durable-sqlite.svg)](https://github.com/firtoz/fullstack-toolkit/blob/main/LICENSE)
6
+
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
8
+ [![Drizzle ORM](https://img.shields.io/badge/Drizzle-ORM-000000)](https://orm.drizzle.team/)
9
+ [![Cloudflare](https://img.shields.io/badge/Cloudflare-Durable_Objects-F38020?logo=cloudflare&logoColor=white)](https://developers.cloudflare.com/durable-objects/)
10
+
11
+ **Drizzle + TanStack DB on Durable Object SQLite** — same ideas as [`@firtoz/drizzle-sqlite-wasm`](../drizzle-sqlite-wasm) in the browser, but for Workers/DOs only (no React provider, no OPFS, no web workers).
4
12
 
5
13
  ## Install
6
14
 
@@ -0,0 +1,206 @@
1
+ import { DEFAULT_SYNC_COLLECTION_ID, PartialSyncServerBridge, PartialSyncMutationHandler, createClientMessageSchema, createServerMessageSchema } from '@firtoz/collection-sync';
2
+ import { StandardSchemaWebSocketDO, StandardSchemaSession } from '@firtoz/websocket-do';
3
+ import { drizzle } from 'drizzle-orm/durable-sqlite';
4
+ import { migrate } from 'drizzle-orm/durable-sqlite/migrator';
5
+
6
+ // src/queryable-durable-object.ts
7
+ function createSessionCodecOptions(enableBufferMessages, serializeJson, deserializeJson) {
8
+ const clientSchema = createClientMessageSchema();
9
+ const serverSchema = createServerMessageSchema();
10
+ if (!enableBufferMessages) {
11
+ return {
12
+ clientSchema,
13
+ serverSchema,
14
+ enableBufferMessages: false,
15
+ ...serializeJson && deserializeJson ? { serializeJson, deserializeJson } : {}
16
+ };
17
+ }
18
+ return {
19
+ clientSchema,
20
+ serverSchema,
21
+ enableBufferMessages: true
22
+ };
23
+ }
24
+ async function routeQueryableClientMessage(message, dispatch) {
25
+ const mid = message.collectionId ?? DEFAULT_SYNC_COLLECTION_ID;
26
+ switch (message.type) {
27
+ case "mutateBatch":
28
+ case "syncHello":
29
+ if (dispatch.partialMutationHandler !== void 0 && mid === dispatch.partialMutationHandler.collectionId) {
30
+ await dispatch.partialMutationHandler.handleClientMessage(message);
31
+ }
32
+ return;
33
+ default:
34
+ if (mid === dispatch.partialBridge.collectionId) {
35
+ await dispatch.partialBridge.handleClientMessage(message);
36
+ }
37
+ }
38
+ }
39
+ var QueryableSession = class extends StandardSchemaSession {
40
+ constructor(websocket, sessions, options, sessionSlot) {
41
+ const generatedClientId = crypto.randomUUID();
42
+ super(websocket, sessions, options, {
43
+ createData: () => ({ clientId: generatedClientId }),
44
+ handleValidatedMessage: async (message) => {
45
+ this.clientId = message.clientId;
46
+ const dispatch = this.sessionSlot.dispatch;
47
+ if (dispatch === void 0) {
48
+ this.sessionSlot.pending.push(message);
49
+ return;
50
+ }
51
+ await routeQueryableClientMessage(message, dispatch);
52
+ },
53
+ handleClose: async () => {
54
+ const dispatch = this.sessionSlot.dispatch;
55
+ if (dispatch !== void 0) {
56
+ dispatch.partialBridge.removeClient(this.clientId);
57
+ }
58
+ }
59
+ });
60
+ this.sessionSlot = sessionSlot;
61
+ this.clientId = generatedClientId;
62
+ }
63
+ };
64
+ var QueryableDurableObject = class extends StandardSchemaWebSocketDO {
65
+ constructor(ctx, env, config) {
66
+ let bridgeRef;
67
+ const sessionSlot = { pending: [] };
68
+ super(ctx, env, {
69
+ standardSchemaSessionOptions: (sessionCtx) => {
70
+ const useMsgpack = sessionCtx !== void 0 && new URL(sessionCtx.req.url).searchParams.get("transport") === "msgpack";
71
+ return createSessionCodecOptions(
72
+ useMsgpack,
73
+ config.serializeJson,
74
+ config.deserializeJson
75
+ );
76
+ },
77
+ createStandardSchemaSession: (_sessionCtx, websocket, options) => new QueryableSession(
78
+ websocket,
79
+ this.sessions,
80
+ options,
81
+ sessionSlot
82
+ )
83
+ });
84
+ this.app = this.getBaseApp().get("/health", (c) => c.text("ok"));
85
+ this.ctx.blockConcurrencyWhile(async () => {
86
+ const db = drizzle(ctx.storage, { schema: config.schema });
87
+ migrate(db, config.migrations);
88
+ this.db = db;
89
+ const queryByPredicate = this.queryByPredicate;
90
+ const getPredicateCount = this.getPredicateCount;
91
+ const changesSince = this.changesSince;
92
+ const store = config.createPartialSyncStore !== void 0 ? config.createPartialSyncStore(db) : {
93
+ queryRange: (options) => this.queryRange(options),
94
+ queryByOffset: (options) => this.queryByOffset(options),
95
+ getTotalCount: async () => this.getTotalCount(),
96
+ getSortValue: (row, column) => this.getSortValue(row, column),
97
+ ...queryByPredicate !== void 0 ? {
98
+ queryByPredicate: (opts) => queryByPredicate.call(this, opts)
99
+ } : {},
100
+ ...getPredicateCount !== void 0 ? {
101
+ getPredicateCount: (conditions) => getPredicateCount.call(this, conditions)
102
+ } : {},
103
+ ...changesSince !== void 0 ? {
104
+ changesSince: (opts) => changesSince.call(this, opts)
105
+ } : {}
106
+ };
107
+ const collectionId = config.collectionId ?? DEFAULT_SYNC_COLLECTION_ID;
108
+ bridgeRef = new PartialSyncServerBridge({
109
+ store,
110
+ sendToClient: (clientId, message) => this.sendToClient(clientId, message),
111
+ queryChunkSize: config.queryChunkSize,
112
+ collectionId,
113
+ ...config.resolveClientVisibility !== void 0 ? {
114
+ resolveClientVisibility: config.resolveClientVisibility
115
+ } : {},
116
+ ...config.resolveMovedHint !== void 0 ? { resolveMovedHint: config.resolveMovedHint } : {}
117
+ });
118
+ this.bridge = bridgeRef;
119
+ const mutationStore = this.createClientMutationSyncStore();
120
+ let partialMutationHandler;
121
+ if (mutationStore !== void 0) {
122
+ partialMutationHandler = new PartialSyncMutationHandler({
123
+ store: mutationStore,
124
+ partialBridge: bridgeRef,
125
+ sendToClient: (clientId, message) => this.sendToClient(clientId, message),
126
+ collectionId
127
+ });
128
+ this.partialMutationHandler = partialMutationHandler;
129
+ }
130
+ sessionSlot.dispatch = {
131
+ partialBridge: bridgeRef,
132
+ partialMutationHandler
133
+ };
134
+ for (const message of sessionSlot.pending) {
135
+ await routeQueryableClientMessage(message, sessionSlot.dispatch);
136
+ }
137
+ sessionSlot.pending.length = 0;
138
+ if (config.seedInBackground) {
139
+ void this.seedData().catch((error) => {
140
+ console.error("Background seedData failed", error);
141
+ });
142
+ return;
143
+ }
144
+ await this.seedData();
145
+ });
146
+ }
147
+ queryRange(_options) {
148
+ const message = "QueryableDurableObject: override queryRange() or pass createPartialSyncStore in config";
149
+ return {
150
+ [Symbol.asyncIterator]() {
151
+ return {
152
+ next: () => Promise.reject(new Error(message))
153
+ };
154
+ }
155
+ };
156
+ }
157
+ queryByOffset(_options) {
158
+ const message = "QueryableDurableObject: override queryByOffset() or pass createPartialSyncStore in config";
159
+ return {
160
+ [Symbol.asyncIterator]() {
161
+ return {
162
+ next: () => Promise.reject(new Error(message))
163
+ };
164
+ }
165
+ };
166
+ }
167
+ async getTotalCount() {
168
+ throw new Error(
169
+ "QueryableDurableObject: override getTotalCount() or pass createPartialSyncStore in config"
170
+ );
171
+ }
172
+ getSortValue(row, column) {
173
+ return row[column];
174
+ }
175
+ /**
176
+ * When overridden to return a store, `mutateBatch` is handled by {@link PartialSyncMutationHandler}
177
+ * (interest-scoped `rangePatch` + `ack` with `serverVersion: 0`). `syncHello` is not handled on this path.
178
+ * Range traffic stays on {@link PartialSyncServerBridge}.
179
+ */
180
+ createClientMutationSyncStore() {
181
+ return void 0;
182
+ }
183
+ async seedData() {
184
+ }
185
+ async pushServerChanges(changes) {
186
+ await this.bridge.pushServerChanges(changes);
187
+ }
188
+ sendToClient(clientId, message) {
189
+ for (const session of this.sessions.values()) {
190
+ const typedSession = session;
191
+ if (typedSession.clientId !== clientId) continue;
192
+ typedSession.send(message);
193
+ }
194
+ }
195
+ broadcastExcept(excludeClientId, message) {
196
+ for (const session of this.sessions.values()) {
197
+ const typedSession = session;
198
+ if (typedSession.clientId === excludeClientId) continue;
199
+ typedSession.send(message);
200
+ }
201
+ }
202
+ };
203
+
204
+ export { QueryableDurableObject };
205
+ //# sourceMappingURL=chunk-3XAF3IZW.js.map
206
+ //# sourceMappingURL=chunk-3XAF3IZW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queryable-durable-object.ts"],"names":[],"mappings":"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","file":"chunk-3XAF3IZW.js","sourcesContent":["import {\n\tPartialSyncMutationHandler,\n\tPartialSyncServerBridge,\n\tcreateClientMessageSchema,\n\tcreateServerMessageSchema,\n\tDEFAULT_SYNC_COLLECTION_ID,\n\ttype PartialSyncServerBridgeStore,\n\ttype PartialSyncRowShape,\n\ttype RangeCondition,\n\ttype SyncClientMessage,\n\ttype SyncRange,\n\ttype SyncRangeSort,\n\ttype SyncServerBridgeStore,\n\ttype SyncServerMessage,\n} from \"@firtoz/collection-sync\";\nimport type { SyncMessage } from \"@firtoz/db-helpers\";\nimport {\n\tStandardSchemaSession,\n\tStandardSchemaWebSocketDO,\n\ttype StandardSchemaSessionOptions,\n} from \"@firtoz/websocket-do\";\nimport type { DrizzleSqliteDODatabase } from \"drizzle-orm/durable-sqlite\";\nimport { drizzle } from \"drizzle-orm/durable-sqlite\";\nimport { migrate } from \"drizzle-orm/durable-sqlite/migrator\";\nimport type { Context } from \"hono\";\n\ntype SessionData = { clientId: string };\n\ntype MutationSyncRow = PartialSyncRowShape;\n\ntype SessionDispatch<TRow extends MutationSyncRow> = {\n\tpartialBridge: PartialSyncServerBridge<TRow>;\n\tpartialMutationHandler?: PartialSyncMutationHandler<TRow>;\n};\n\ntype SessionSlot<TRow extends MutationSyncRow> = {\n\tdispatch?: SessionDispatch<TRow>;\n\tpending: SyncClientMessage[];\n};\n\nfunction createSessionCodecOptions<TItem extends PartialSyncRowShape>(\n\tenableBufferMessages: boolean,\n\tserializeJson?: (value: unknown) => string,\n\tdeserializeJson?: (raw: string) => unknown,\n): StandardSchemaSessionOptions<SyncClientMessage, SyncServerMessage<TItem>> {\n\tconst clientSchema = createClientMessageSchema();\n\tconst serverSchema = createServerMessageSchema<TItem>();\n\tif (!enableBufferMessages) {\n\t\treturn {\n\t\t\tclientSchema,\n\t\t\tserverSchema,\n\t\t\tenableBufferMessages: false,\n\t\t\t...(serializeJson && deserializeJson\n\t\t\t\t? { serializeJson, deserializeJson }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\treturn {\n\t\tclientSchema,\n\t\tserverSchema,\n\t\tenableBufferMessages: true,\n\t};\n}\n\nasync function routeQueryableClientMessage<TRow extends MutationSyncRow>(\n\tmessage: SyncClientMessage,\n\tdispatch: SessionDispatch<TRow>,\n): Promise<void> {\n\tconst mid = message.collectionId ?? DEFAULT_SYNC_COLLECTION_ID;\n\tswitch (message.type) {\n\t\tcase \"mutateBatch\":\n\t\tcase \"syncHello\":\n\t\t\tif (\n\t\t\t\tdispatch.partialMutationHandler !== undefined &&\n\t\t\t\tmid === dispatch.partialMutationHandler.collectionId\n\t\t\t) {\n\t\t\t\tawait dispatch.partialMutationHandler.handleClientMessage(message);\n\t\t\t}\n\t\t\treturn;\n\t\tdefault:\n\t\t\tif (mid === dispatch.partialBridge.collectionId) {\n\t\t\t\tawait dispatch.partialBridge.handleClientMessage(message);\n\t\t\t}\n\t}\n}\n\nclass QueryableSession<\n\tTItem extends PartialSyncRowShape,\n\tTEnv extends Cloudflare.Env,\n> extends StandardSchemaSession<\n\tSessionData,\n\tSyncServerMessage<TItem>,\n\tSyncClientMessage,\n\tTEnv\n> {\n\tpublic clientId: string;\n\n\tconstructor(\n\t\twebsocket: WebSocket,\n\t\tsessions: Map<WebSocket, QueryableSession<TItem, TEnv>>,\n\t\toptions: StandardSchemaSessionOptions<\n\t\t\tSyncClientMessage,\n\t\t\tSyncServerMessage<TItem>\n\t\t>,\n\t\tprivate readonly sessionSlot: SessionSlot<TItem>,\n\t) {\n\t\tconst generatedClientId = crypto.randomUUID();\n\t\tsuper(websocket, sessions, options, {\n\t\t\tcreateData: () => ({ clientId: generatedClientId }),\n\t\t\thandleValidatedMessage: async (message: SyncClientMessage) => {\n\t\t\t\tthis.clientId = message.clientId;\n\t\t\t\tconst dispatch = this.sessionSlot.dispatch;\n\t\t\t\tif (dispatch === undefined) {\n\t\t\t\t\tthis.sessionSlot.pending.push(message);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait routeQueryableClientMessage(message, dispatch);\n\t\t\t},\n\t\t\thandleClose: async () => {\n\t\t\t\tconst dispatch = this.sessionSlot.dispatch;\n\t\t\t\tif (dispatch !== undefined) {\n\t\t\t\t\tdispatch.partialBridge.removeClient(this.clientId);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tthis.clientId = generatedClientId;\n\t}\n}\n\nexport type QueryableDurableObjectConfig<\n\tTSchema extends Record<string, unknown>,\n\tTRow extends PartialSyncRowShape = PartialSyncRowShape,\n> = {\n\tschema: TSchema;\n\tmigrations: Parameters<typeof migrate>[1];\n\tqueryChunkSize?: number;\n\tseedInBackground?: boolean;\n\tserializeJson?: (value: unknown) => string;\n\tdeserializeJson?: (raw: string) => unknown;\n\t/**\n\t * When set, used as the partial-sync store instead of overriding\n\t * {@link QueryableDurableObject.queryRange}, {@link QueryableDurableObject.queryByOffset},\n\t * and {@link QueryableDurableObject.getTotalCount}.\n\t */\n\tcreatePartialSyncStore?: (\n\t\tdb: DrizzleSqliteDODatabase<TSchema>,\n\t) => PartialSyncServerBridgeStore<TRow>;\n\t/**\n\t * Multiplex key for partial-sync WebSocket messages.\n\t * When using {@link PartialSyncMutationHandler} on the same socket, use the same id unless you multiplex multiple collections.\n\t */\n\tcollectionId?: string;\n\t/**\n\t * Server-side narrowing of client predicate viewports (e.g. fog of war). Passed to\n\t * {@link PartialSyncServerBridge}.\n\t */\n\tresolveClientVisibility?: (\n\t\tclientId: string,\n\t\trequestedConditions: RangeCondition[],\n\t) => RangeCondition[] | Promise<RangeCondition[]>;\n\t/**\n\t * Optional hints for rows that left the client's range during `rangeReconcile`. Return `null` for\n\t * fog of war (default when omitted).\n\t */\n\tresolveMovedHint?: (\n\t\trow: TRow,\n\t\trange: SyncRange,\n\t) => Record<string, unknown> | null | Promise<Record<string, unknown> | null>;\n};\n\nexport abstract class QueryableDurableObject<\n\tTRow extends PartialSyncRowShape,\n\tTSchema extends Record<string, unknown>,\n\tTEnv extends Cloudflare.Env = Cloudflare.Env,\n> extends StandardSchemaWebSocketDO<\n\tQueryableSession<TRow, TEnv>,\n\tSyncClientMessage,\n\tSyncServerMessage<TRow>,\n\tTEnv\n> {\n\tprotected db!: ReturnType<typeof drizzle>;\n\tprotected bridge!: PartialSyncServerBridge<TRow>;\n\tprotected partialMutationHandler?: PartialSyncMutationHandler<TRow>;\n\n\treadonly app = this.getBaseApp().get(\"/health\", (c: Context) => c.text(\"ok\"));\n\n\tconstructor(\n\t\tctx: DurableObjectState,\n\t\tenv: TEnv,\n\t\tconfig: QueryableDurableObjectConfig<TSchema, TRow>,\n\t) {\n\t\tlet bridgeRef!: PartialSyncServerBridge<TRow>;\n\t\tconst sessionSlot: SessionSlot<TRow> = { pending: [] };\n\t\tsuper(ctx, env, {\n\t\t\tstandardSchemaSessionOptions: (\n\t\t\t\tsessionCtx: Context<{ Bindings: TEnv }> | undefined,\n\t\t\t) => {\n\t\t\t\tconst useMsgpack =\n\t\t\t\t\tsessionCtx !== undefined &&\n\t\t\t\t\tnew URL(sessionCtx.req.url).searchParams.get(\"transport\") ===\n\t\t\t\t\t\t\"msgpack\";\n\t\t\t\treturn createSessionCodecOptions<TRow>(\n\t\t\t\t\tuseMsgpack,\n\t\t\t\t\tconfig.serializeJson,\n\t\t\t\t\tconfig.deserializeJson,\n\t\t\t\t);\n\t\t\t},\n\t\t\tcreateStandardSchemaSession: (\n\t\t\t\t_sessionCtx: Context<{ Bindings: TEnv }> | undefined,\n\t\t\t\twebsocket: WebSocket,\n\t\t\t\toptions: StandardSchemaSessionOptions<\n\t\t\t\t\tSyncClientMessage,\n\t\t\t\t\tSyncServerMessage<unknown>\n\t\t\t\t>,\n\t\t\t) =>\n\t\t\t\tnew QueryableSession<TRow, TEnv>(\n\t\t\t\t\twebsocket,\n\t\t\t\t\tthis.sessions,\n\t\t\t\t\toptions as StandardSchemaSessionOptions<\n\t\t\t\t\t\tSyncClientMessage,\n\t\t\t\t\t\tSyncServerMessage<TRow>\n\t\t\t\t\t>,\n\t\t\t\t\tsessionSlot,\n\t\t\t\t),\n\t\t});\n\n\t\tthis.ctx.blockConcurrencyWhile(async () => {\n\t\t\tconst db = drizzle(ctx.storage, { schema: config.schema });\n\t\t\tmigrate(db, config.migrations);\n\t\t\tthis.db = db;\n\n\t\t\tconst queryByPredicate = this.queryByPredicate;\n\t\t\tconst getPredicateCount = this.getPredicateCount;\n\t\t\tconst changesSince = this.changesSince;\n\n\t\t\tconst store: PartialSyncServerBridgeStore<TRow> =\n\t\t\t\tconfig.createPartialSyncStore !== undefined\n\t\t\t\t\t? config.createPartialSyncStore(db)\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tqueryRange: (options) => this.queryRange(options),\n\t\t\t\t\t\t\tqueryByOffset: (options) => this.queryByOffset(options),\n\t\t\t\t\t\t\tgetTotalCount: async () => this.getTotalCount(),\n\t\t\t\t\t\t\tgetSortValue: (row, column) => this.getSortValue(row, column),\n\t\t\t\t\t\t\t...(queryByPredicate !== undefined\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\tqueryByPredicate: (opts: {\n\t\t\t\t\t\t\t\t\t\t\tconditions: RangeCondition[];\n\t\t\t\t\t\t\t\t\t\t\tsort?: SyncRangeSort;\n\t\t\t\t\t\t\t\t\t\t\tlimit?: number;\n\t\t\t\t\t\t\t\t\t\t\tchunkSize: number;\n\t\t\t\t\t\t\t\t\t\t}) => queryByPredicate.call(this, opts),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t...(getPredicateCount !== undefined\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\tgetPredicateCount: (conditions: RangeCondition[]) =>\n\t\t\t\t\t\t\t\t\t\t\tgetPredicateCount.call(this, conditions),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t...(changesSince !== undefined\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\tchangesSince: (opts: {\n\t\t\t\t\t\t\t\t\t\t\trange: SyncRange;\n\t\t\t\t\t\t\t\t\t\t\tsinceVersion: number;\n\t\t\t\t\t\t\t\t\t\t\tchunkSize: number;\n\t\t\t\t\t\t\t\t\t\t}) => changesSince.call(this, opts),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t};\n\t\t\tconst collectionId = config.collectionId ?? DEFAULT_SYNC_COLLECTION_ID;\n\t\t\tbridgeRef = new PartialSyncServerBridge<TRow>({\n\t\t\t\tstore,\n\t\t\t\tsendToClient: (clientId, message) =>\n\t\t\t\t\tthis.sendToClient(clientId, message),\n\t\t\t\tqueryChunkSize: config.queryChunkSize,\n\t\t\t\tcollectionId,\n\t\t\t\t...(config.resolveClientVisibility !== undefined\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tresolveClientVisibility: config.resolveClientVisibility,\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\t...(config.resolveMovedHint !== undefined\n\t\t\t\t\t? { resolveMovedHint: config.resolveMovedHint }\n\t\t\t\t\t: {}),\n\t\t\t});\n\t\t\tthis.bridge = bridgeRef;\n\n\t\t\tconst mutationStore = this.createClientMutationSyncStore();\n\t\t\tlet partialMutationHandler: PartialSyncMutationHandler<TRow> | undefined;\n\t\t\tif (mutationStore !== undefined) {\n\t\t\t\tpartialMutationHandler = new PartialSyncMutationHandler<TRow>({\n\t\t\t\t\tstore: mutationStore,\n\t\t\t\t\tpartialBridge: bridgeRef,\n\t\t\t\t\tsendToClient: (clientId, message) =>\n\t\t\t\t\t\tthis.sendToClient(clientId, message),\n\t\t\t\t\tcollectionId,\n\t\t\t\t});\n\t\t\t\tthis.partialMutationHandler = partialMutationHandler;\n\t\t\t}\n\n\t\t\tsessionSlot.dispatch = {\n\t\t\t\tpartialBridge: bridgeRef,\n\t\t\t\tpartialMutationHandler,\n\t\t\t};\n\t\t\tfor (const message of sessionSlot.pending) {\n\t\t\t\tawait routeQueryableClientMessage(message, sessionSlot.dispatch);\n\t\t\t}\n\t\t\tsessionSlot.pending.length = 0;\n\t\t\tif (config.seedInBackground) {\n\t\t\t\tvoid this.seedData().catch((error: unknown) => {\n\t\t\t\t\tconsole.error(\"Background seedData failed\", error);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait this.seedData();\n\t\t});\n\t}\n\n\tprotected queryRange(_options: {\n\t\tsort: { column: string; direction: \"asc\" | \"desc\" };\n\t\tlimit: number;\n\t\tafterCursor: unknown | null;\n\t\tchunkSize: number;\n\t}): AsyncIterable<TRow[]> {\n\t\tconst message =\n\t\t\t\"QueryableDurableObject: override queryRange() or pass createPartialSyncStore in config\";\n\t\treturn {\n\t\t\t[Symbol.asyncIterator](): AsyncIterator<TRow[]> {\n\t\t\t\treturn {\n\t\t\t\t\tnext: () => Promise.reject(new Error(message)),\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t}\n\n\tprotected queryByOffset(_options: {\n\t\tsort: { column: string; direction: \"asc\" | \"desc\" };\n\t\tlimit: number;\n\t\toffset: number;\n\t\tchunkSize: number;\n\t}): AsyncIterable<TRow[]> {\n\t\tconst message =\n\t\t\t\"QueryableDurableObject: override queryByOffset() or pass createPartialSyncStore in config\";\n\t\treturn {\n\t\t\t[Symbol.asyncIterator](): AsyncIterator<TRow[]> {\n\t\t\t\treturn {\n\t\t\t\t\tnext: () => Promise.reject(new Error(message)),\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t}\n\n\tprotected async getTotalCount(): Promise<number> {\n\t\tthrow new Error(\n\t\t\t\"QueryableDurableObject: override getTotalCount() or pass createPartialSyncStore in config\",\n\t\t);\n\t}\n\n\tprotected queryByPredicate?(_options: {\n\t\tconditions: RangeCondition[];\n\t\tsort?: SyncRangeSort;\n\t\tlimit?: number;\n\t\tchunkSize: number;\n\t}): AsyncIterable<TRow[]>;\n\n\tprotected getPredicateCount?(_conditions: RangeCondition[]): Promise<number>;\n\n\tprotected changesSince?(_options: {\n\t\trange: SyncRange;\n\t\tsinceVersion: number;\n\t\tchunkSize: number;\n\t}): Promise<{ changes: SyncMessage<TRow>[]; totalCount: number } | null>;\n\n\tprotected getSortValue(row: TRow, column: string): unknown {\n\t\treturn (row as Record<string, unknown>)[column];\n\t}\n\n\t/**\n\t * When overridden to return a store, `mutateBatch` is handled by {@link PartialSyncMutationHandler}\n\t * (interest-scoped `rangePatch` + `ack` with `serverVersion: 0`). `syncHello` is not handled on this path.\n\t * Range traffic stays on {@link PartialSyncServerBridge}.\n\t */\n\tprotected createClientMutationSyncStore():\n\t\t| SyncServerBridgeStore<TRow>\n\t\t| undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected async seedData(): Promise<void> {}\n\n\tasync pushServerChanges(changes: SyncMessage<TRow>[]): Promise<void> {\n\t\tawait this.bridge.pushServerChanges(changes);\n\t}\n\n\tprotected sendToClient(\n\t\tclientId: string,\n\t\tmessage: SyncServerMessage<TRow>,\n\t): void {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tconst typedSession = session as QueryableSession<TRow, TEnv>;\n\t\t\tif (typedSession.clientId !== clientId) continue;\n\t\t\ttypedSession.send(message);\n\t\t}\n\t}\n\n\tprotected broadcastExcept(\n\t\texcludeClientId: string,\n\t\tmessage: SyncServerMessage<TRow>,\n\t): void {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tconst typedSession = session as QueryableSession<TRow, TEnv>;\n\t\t\tif (typedSession.clientId === excludeClientId) continue;\n\t\t\ttypedSession.send(message);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import { getTableColumns, gt } from 'drizzle-orm';
2
+
3
+ // src/drizzle-partial-sync-changelog.ts
4
+ function createDrizzleChangelogHelper(options) {
5
+ const cols = getTableColumns(options.changelogTable);
6
+ const rowIdCol = cols.rowId;
7
+ const operationCol = cols.operation;
8
+ const versionCol = cols.version;
9
+ if (rowIdCol === void 0 || operationCol === void 0 || versionCol === void 0) {
10
+ throw new Error(
11
+ "changelogTable must have rowId, operation, and version columns"
12
+ );
13
+ }
14
+ return {
15
+ append: async (operation, rowId, payload) => {
16
+ const version = /* @__PURE__ */ new Date();
17
+ await options.db.insert(options.changelogTable).values({
18
+ rowId,
19
+ operation,
20
+ version,
21
+ payloadJson: payload === null || payload === void 0 ? null : options.serializeJson(payload)
22
+ });
23
+ },
24
+ selectAfterVersion: async (sinceVersionMs) => {
25
+ const rows = await options.db.select().from(options.changelogTable).where(gt(versionCol, new Date(sinceVersionMs)));
26
+ return rows;
27
+ },
28
+ deleteAll: async () => {
29
+ await options.db.delete(options.changelogTable);
30
+ }
31
+ };
32
+ }
33
+
34
+ export { createDrizzleChangelogHelper };
35
+ //# sourceMappingURL=chunk-DTRYQ3SF.js.map
36
+ //# sourceMappingURL=chunk-DTRYQ3SF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/drizzle-partial-sync-changelog.ts"],"names":[],"mappings":";;;AAcO,SAAS,6BAEd,OAAA,EAAiD;AAClD,EAAA,MAAM,IAAA,GAAO,eAAA,CAAgB,OAAA,CAAQ,cAAc,CAAA;AACnD,EAAA,MAAM,WAAW,IAAA,CAAK,KAAA;AACtB,EAAA,MAAM,eAAe,IAAA,CAAK,SAAA;AAC1B,EAAA,MAAM,aAAa,IAAA,CAAK,OAAA;AACxB,EAAA,IACC,QAAA,KAAa,MAAA,IACb,YAAA,KAAiB,MAAA,IACjB,eAAe,MAAA,EACd;AACD,IAAA,MAAM,IAAI,KAAA;AAAA,MACT;AAAA,KACD;AAAA,EACD;AAEA,EAAA,OAAO;AAAA,IACN,MAAA,EAAQ,OACP,SAAA,EACA,KAAA,EACA,OAAA,KACmB;AACnB,MAAA,MAAM,OAAA,uBAAc,IAAA,EAAK;AACzB,MAAA,MAAM,QAAQ,EAAA,CAAG,MAAA,CAAO,OAAA,CAAQ,cAAc,EAAE,MAAA,CAAO;AAAA,QACtD,KAAA;AAAA,QACA,SAAA;AAAA,QACA,OAAA;AAAA,QACA,WAAA,EACC,YAAY,IAAA,IAAQ,OAAA,KAAY,SAC7B,IAAA,GACA,OAAA,CAAQ,cAAc,OAAO;AAAA,OACN,CAAA;AAAA,IAC7B,CAAA;AAAA,IAEA,kBAAA,EAAoB,OAAO,cAAA,KAA2B;AACrD,MAAA,MAAM,OAAO,MAAM,OAAA,CAAQ,EAAA,CACzB,MAAA,GACA,IAAA,CAAK,OAAA,CAAQ,cAAc,CAAA,CAC3B,MAAM,EAAA,CAAG,UAAA,EAAY,IAAI,IAAA,CAAK,cAAc,CAAC,CAAC,CAAA;AAChD,MAAA,OAAO,IAAA;AAAA,IACR,CAAA;AAAA,IAEA,WAAW,YAA2B;AACrC,MAAA,MAAM,OAAA,CAAQ,EAAA,CAAG,MAAA,CAAO,OAAA,CAAQ,cAAc,CAAA;AAAA,IAC/C;AAAA,GACD;AACD","file":"chunk-DTRYQ3SF.js","sourcesContent":["import { getTableColumns, gt } from \"drizzle-orm\";\nimport type { SQLiteTable } from \"drizzle-orm/sqlite-core\";\nimport type { PartialSyncSqliteDatabase } from \"./partial-sync-sqlite-db\";\n\nexport type ChangelogOperation = \"insert\" | \"update\" | \"delete\";\n\nexport type DrizzleChangelogHelperOptions<\n\tTSchema extends Record<string, unknown>,\n> = {\n\tdb: PartialSyncSqliteDatabase<TSchema>;\n\tchangelogTable: SQLiteTable;\n\tserializeJson: (value: unknown) => string;\n};\n\nexport function createDrizzleChangelogHelper<\n\tTSchema extends Record<string, unknown>,\n>(options: DrizzleChangelogHelperOptions<TSchema>) {\n\tconst cols = getTableColumns(options.changelogTable);\n\tconst rowIdCol = cols.rowId;\n\tconst operationCol = cols.operation;\n\tconst versionCol = cols.version;\n\tif (\n\t\trowIdCol === undefined ||\n\t\toperationCol === undefined ||\n\t\tversionCol === undefined\n\t) {\n\t\tthrow new Error(\n\t\t\t\"changelogTable must have rowId, operation, and version columns\",\n\t\t);\n\t}\n\n\treturn {\n\t\tappend: async (\n\t\t\toperation: ChangelogOperation,\n\t\t\trowId: string,\n\t\t\tpayload: unknown,\n\t\t): Promise<void> => {\n\t\t\tconst version = new Date();\n\t\t\tawait options.db.insert(options.changelogTable).values({\n\t\t\t\trowId,\n\t\t\t\toperation,\n\t\t\t\tversion,\n\t\t\t\tpayloadJson:\n\t\t\t\t\tpayload === null || payload === undefined\n\t\t\t\t\t\t? null\n\t\t\t\t\t\t: options.serializeJson(payload),\n\t\t\t} as Record<string, unknown>);\n\t\t},\n\n\t\tselectAfterVersion: async (sinceVersionMs: number) => {\n\t\t\tconst rows = await options.db\n\t\t\t\t.select()\n\t\t\t\t.from(options.changelogTable)\n\t\t\t\t.where(gt(versionCol, new Date(sinceVersionMs)));\n\t\t\treturn rows;\n\t\t},\n\n\t\tdeleteAll: async (): Promise<void> => {\n\t\t\tawait options.db.delete(options.changelogTable);\n\t\t},\n\t};\n}\n\nexport type DrizzleChangelogHelper<TSchema extends Record<string, unknown>> =\n\tReturnType<typeof createDrizzleChangelogHelper<TSchema>>;\n"]}
@@ -0,0 +1,288 @@
1
+ import { sortColumnFromConfig, predicateWhereFromConditions } from './chunk-QHM6T5OI.js';
2
+ import { compareInterestValues } from '@firtoz/collection-sync/partial-sync-interest';
3
+ import { matchesPredicate, defaultPredicateColumnValue } from '@firtoz/collection-sync/partial-sync-predicate-match';
4
+ import { exhaustiveGuard } from '@firtoz/maybe-error';
5
+ import { getTableColumns, asc, desc, gt, lt, and, count, max, eq, or } from 'drizzle-orm';
6
+
7
+ function createDrizzlePartialSyncStore(options) {
8
+ const { db, table, columnConfig, changelogHelper, deserializeJson } = options;
9
+ const tableColumns = getTableColumns(table);
10
+ const idCol = tableColumns.id;
11
+ const updatedAtCol = tableColumns[options.updatedAtColumnName];
12
+ if (idCol === void 0) {
13
+ throw new Error("Partial sync table must have an id column");
14
+ }
15
+ if (updatedAtCol === void 0) {
16
+ throw new Error(
17
+ `Partial sync table missing updatedAt column: ${String(options.updatedAtColumnName)}`
18
+ );
19
+ }
20
+ async function* queryRange(opts) {
21
+ let remaining = opts.limit;
22
+ let cursor = opts.afterCursor;
23
+ const sortColumn = sortColumnFromConfig(
24
+ table,
25
+ opts.sort.column,
26
+ columnConfig
27
+ );
28
+ while (remaining > 0) {
29
+ const currentLimit = Math.min(opts.chunkSize, remaining);
30
+ const directionExpr = opts.sort.direction === "asc" ? asc(sortColumn) : desc(sortColumn);
31
+ const whereCursor = cursor === null ? void 0 : opts.sort.direction === "asc" ? gt(sortColumn, cursor) : lt(sortColumn, cursor);
32
+ const rows = await db.select().from(table).where(whereCursor ? and(whereCursor) : void 0).orderBy(directionExpr, asc(idCol)).limit(currentLimit);
33
+ if (rows.length === 0) break;
34
+ yield rows;
35
+ remaining -= rows.length;
36
+ if (rows.length < currentLimit) break;
37
+ const last = rows[rows.length - 1];
38
+ cursor = last[opts.sort.column];
39
+ }
40
+ }
41
+ async function* queryByOffset(opts) {
42
+ let remaining = opts.limit;
43
+ let sqlOffset = opts.offset;
44
+ const sortColumn = sortColumnFromConfig(
45
+ table,
46
+ opts.sort.column,
47
+ columnConfig
48
+ );
49
+ while (remaining > 0) {
50
+ const currentLimit = Math.min(opts.chunkSize, remaining);
51
+ const directionExpr = opts.sort.direction === "asc" ? asc(sortColumn) : desc(sortColumn);
52
+ const rows = await db.select().from(table).orderBy(directionExpr, asc(idCol)).limit(currentLimit).offset(sqlOffset);
53
+ if (rows.length === 0) break;
54
+ yield rows;
55
+ remaining -= rows.length;
56
+ sqlOffset += rows.length;
57
+ if (rows.length < currentLimit) break;
58
+ }
59
+ }
60
+ async function getTotalCount() {
61
+ const rows = await db.select({ c: count() }).from(table);
62
+ return rows[0]?.c ?? 0;
63
+ }
64
+ function getSortValue(row, column) {
65
+ return row[column];
66
+ }
67
+ async function* queryByPredicate(opts) {
68
+ const limit = opts.limit ?? opts.chunkSize;
69
+ let remaining = limit;
70
+ let offset = 0;
71
+ const where = predicateWhereFromConditions(
72
+ table,
73
+ opts.conditions,
74
+ columnConfig
75
+ );
76
+ const sortColumnName = opts.sort?.column ?? columnConfig.sortableColumns[0];
77
+ if (sortColumnName === void 0) {
78
+ throw new Error("queryByPredicate requires sort or sortableColumns[0]");
79
+ }
80
+ const sortColumn = sortColumnFromConfig(
81
+ table,
82
+ sortColumnName,
83
+ columnConfig
84
+ );
85
+ const directionExpr = opts.sort?.direction === "desc" ? desc(sortColumn) : asc(sortColumn);
86
+ while (remaining > 0) {
87
+ const currentLimit = Math.min(opts.chunkSize, remaining);
88
+ const rows = await db.select().from(table).where(where).orderBy(directionExpr, asc(idCol)).limit(currentLimit).offset(offset);
89
+ if (rows.length === 0) break;
90
+ yield rows;
91
+ remaining -= rows.length;
92
+ offset += rows.length;
93
+ if (rows.length < currentLimit) break;
94
+ }
95
+ }
96
+ async function getPredicateCount(conditions) {
97
+ const where = predicateWhereFromConditions(table, conditions, columnConfig);
98
+ const rows = await db.select({ c: count() }).from(table).where(where);
99
+ return rows[0]?.c ?? 0;
100
+ }
101
+ function strictlyBeforeInSortOrder(sortCol, idColSQLite, rowSort, rowId, direction) {
102
+ if (direction === "asc") {
103
+ return or(
104
+ lt(sortCol, rowSort),
105
+ and(eq(sortCol, rowSort), lt(idColSQLite, rowId))
106
+ );
107
+ }
108
+ return or(
109
+ gt(sortCol, rowSort),
110
+ and(eq(sortCol, rowSort), lt(idColSQLite, rowId))
111
+ );
112
+ }
113
+ async function rowInIndexRange(row, indexRange) {
114
+ const sortName = indexRange.sort.column;
115
+ const rowRec = row;
116
+ const rowSort = rowRec[sortName];
117
+ const rowId = rowRec.id;
118
+ const sortCol = sortColumnFromConfig(table, sortName, columnConfig);
119
+ if (indexRange.mode === "cursor") {
120
+ const ac = indexRange.afterCursor;
121
+ if (ac !== null) {
122
+ const cmp = compareInterestValues(rowSort, ac);
123
+ if (indexRange.sort.direction === "asc" && cmp <= 0) return false;
124
+ if (indexRange.sort.direction === "desc" && cmp >= 0) return false;
125
+ }
126
+ }
127
+ const before = strictlyBeforeInSortOrder(
128
+ sortCol,
129
+ idCol,
130
+ rowSort,
131
+ rowId,
132
+ indexRange.sort.direction
133
+ );
134
+ let whereRank = before;
135
+ if (indexRange.mode === "cursor" && indexRange.afterCursor !== null) {
136
+ const eligible = indexRange.sort.direction === "asc" ? gt(sortCol, indexRange.afterCursor) : lt(sortCol, indexRange.afterCursor);
137
+ whereRank = and(eligible, before);
138
+ }
139
+ const rankRows = await db.select({ c: count() }).from(table).where(whereRank);
140
+ const rank = rankRows[0]?.c ?? 0;
141
+ if (indexRange.mode === "offset") {
142
+ return rank >= indexRange.offset && rank < indexRange.offset + indexRange.limit;
143
+ }
144
+ return rank < indexRange.limit;
145
+ }
146
+ async function changelogEntryMatchesRange(op, payloadJson, range) {
147
+ if (range.kind === "predicate") {
148
+ const conds = range.conditions;
149
+ switch (op) {
150
+ case "delete": {
151
+ if (payloadJson === null || typeof payloadJson !== "string") {
152
+ return false;
153
+ }
154
+ const prev = deserializeJson(payloadJson);
155
+ return matchesPredicate(prev, conds, defaultPredicateColumnValue);
156
+ }
157
+ case "insert": {
158
+ if (payloadJson === null || typeof payloadJson !== "string") {
159
+ return false;
160
+ }
161
+ const value = deserializeJson(payloadJson);
162
+ return matchesPredicate(value, conds, defaultPredicateColumnValue);
163
+ }
164
+ case "update": {
165
+ if (payloadJson === null || typeof payloadJson !== "string") {
166
+ return false;
167
+ }
168
+ const parsed = deserializeJson(payloadJson);
169
+ return matchesPredicate(
170
+ parsed.value,
171
+ conds,
172
+ defaultPredicateColumnValue
173
+ ) || matchesPredicate(
174
+ parsed.previousValue,
175
+ conds,
176
+ defaultPredicateColumnValue
177
+ );
178
+ }
179
+ default:
180
+ exhaustiveGuard(op);
181
+ }
182
+ }
183
+ if (range.kind === "index") {
184
+ switch (op) {
185
+ case "delete": {
186
+ if (payloadJson === null || typeof payloadJson !== "string") {
187
+ return false;
188
+ }
189
+ const prev = deserializeJson(payloadJson);
190
+ return rowInIndexRange(prev, range);
191
+ }
192
+ case "insert": {
193
+ if (payloadJson === null || typeof payloadJson !== "string") {
194
+ return false;
195
+ }
196
+ const value = deserializeJson(payloadJson);
197
+ return rowInIndexRange(value, range);
198
+ }
199
+ case "update": {
200
+ if (payloadJson === null || typeof payloadJson !== "string") {
201
+ return false;
202
+ }
203
+ const parsed = deserializeJson(payloadJson);
204
+ return await rowInIndexRange(parsed.value, range) || await rowInIndexRange(parsed.previousValue, range);
205
+ }
206
+ default:
207
+ exhaustiveGuard(op);
208
+ }
209
+ }
210
+ return false;
211
+ }
212
+ async function changesSince(opts) {
213
+ const totalCount = await getTotalCount();
214
+ const maxRow = await db.select({ m: max(updatedAtCol) }).from(table);
215
+ const m = maxRow[0]?.m;
216
+ const maxMs = m instanceof Date ? m.getTime() : Number(m ?? 0);
217
+ if (opts.sinceVersion >= maxMs) {
218
+ return { changes: [], totalCount };
219
+ }
220
+ const logRows = await changelogHelper.selectAfterVersion(opts.sinceVersion);
221
+ if (logRows.length === 0) {
222
+ return null;
223
+ }
224
+ const changes = [];
225
+ for (const entry of logRows) {
226
+ const row = entry;
227
+ const op = row.operation;
228
+ const rowId = row.rowId;
229
+ const payloadJson = row.payloadJson;
230
+ if (typeof op !== "string" || typeof rowId !== "string") {
231
+ throw new Error("Invalid changelog row shape");
232
+ }
233
+ if (op !== "insert" && op !== "update" && op !== "delete") {
234
+ throw new Error(`Unknown changelog operation: ${op}`);
235
+ }
236
+ if (!await changelogEntryMatchesRange(
237
+ op,
238
+ payloadJson,
239
+ opts.range
240
+ )) {
241
+ continue;
242
+ }
243
+ switch (op) {
244
+ case "delete":
245
+ changes.push({ type: "delete", key: rowId });
246
+ break;
247
+ case "insert": {
248
+ if (payloadJson === null || typeof payloadJson !== "string") break;
249
+ const value = deserializeJson(payloadJson);
250
+ changes.push({ type: "insert", value });
251
+ break;
252
+ }
253
+ case "update": {
254
+ if (payloadJson === null || typeof payloadJson !== "string") break;
255
+ const parsed = deserializeJson(payloadJson);
256
+ changes.push({
257
+ type: "update",
258
+ value: parsed.value,
259
+ previousValue: parsed.previousValue
260
+ });
261
+ break;
262
+ }
263
+ default:
264
+ exhaustiveGuard(op);
265
+ }
266
+ }
267
+ return { changes, totalCount };
268
+ }
269
+ async function getRow(key) {
270
+ const rows = await db.select().from(table).where(eq(idCol, key)).limit(1);
271
+ const r = rows[0];
272
+ return r !== void 0 ? r : void 0;
273
+ }
274
+ return {
275
+ queryRange,
276
+ queryByOffset,
277
+ getTotalCount,
278
+ getSortValue,
279
+ queryByPredicate,
280
+ getPredicateCount,
281
+ changesSince,
282
+ getRow
283
+ };
284
+ }
285
+
286
+ export { createDrizzlePartialSyncStore };
287
+ //# sourceMappingURL=chunk-JHZRO76J.js.map
288
+ //# sourceMappingURL=chunk-JHZRO76J.js.map