@syncular/server 0.0.6-96 → 0.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.
- package/README.md +1 -2
- package/dist/auth-leases.d.ts +85 -0
- package/dist/auth-leases.d.ts.map +1 -0
- package/dist/auth-leases.js +368 -0
- package/dist/auth-leases.js.map +1 -0
- package/dist/better-sqlite3.d.ts +22 -0
- package/dist/better-sqlite3.d.ts.map +1 -0
- package/dist/better-sqlite3.js +16 -0
- package/dist/better-sqlite3.js.map +1 -0
- package/dist/blobs/access.d.ts +35 -0
- package/dist/blobs/access.d.ts.map +1 -0
- package/dist/blobs/access.js +149 -0
- package/dist/blobs/access.js.map +1 -0
- package/dist/blobs/adapters/database.d.ts +26 -9
- package/dist/blobs/adapters/database.d.ts.map +1 -1
- package/dist/blobs/adapters/database.js +68 -23
- package/dist/blobs/adapters/database.js.map +1 -1
- package/dist/blobs/index.d.ts +1 -0
- package/dist/blobs/index.d.ts.map +1 -1
- package/dist/blobs/index.js +5 -4
- package/dist/blobs/index.js.map +1 -1
- package/dist/blobs/manager.d.ts +69 -44
- package/dist/blobs/manager.d.ts.map +1 -1
- package/dist/blobs/manager.js +250 -65
- package/dist/blobs/manager.js.map +1 -1
- package/dist/blobs/migrate.d.ts.map +1 -1
- package/dist/blobs/migrate.js +16 -8
- package/dist/blobs/migrate.js.map +1 -1
- package/dist/blobs/types.d.ts +4 -0
- package/dist/blobs/types.d.ts.map +1 -1
- package/dist/bun-sqlite.d.ts +19 -0
- package/dist/bun-sqlite.d.ts.map +1 -0
- package/dist/bun-sqlite.js +19 -0
- package/dist/bun-sqlite.js.map +1 -0
- package/dist/clients.d.ts +1 -0
- package/dist/clients.d.ts.map +1 -1
- package/dist/clients.js.map +1 -1
- package/dist/cloudflare/durable-object.d.ts +93 -0
- package/dist/cloudflare/durable-object.d.ts.map +1 -0
- package/dist/cloudflare/durable-object.js +210 -0
- package/dist/cloudflare/durable-object.js.map +1 -0
- package/dist/cloudflare/index.d.ts +22 -0
- package/dist/cloudflare/index.d.ts.map +1 -0
- package/dist/cloudflare/index.js +22 -0
- package/dist/cloudflare/index.js.map +1 -0
- package/dist/cloudflare/r2.d.ts +180 -0
- package/dist/cloudflare/r2.d.ts.map +1 -0
- package/dist/cloudflare/r2.js +258 -0
- package/dist/cloudflare/r2.js.map +1 -0
- package/dist/cloudflare/scope-cache.d.ts +54 -0
- package/dist/cloudflare/scope-cache.d.ts.map +1 -0
- package/dist/cloudflare/scope-cache.js +223 -0
- package/dist/cloudflare/scope-cache.js.map +1 -0
- package/dist/cloudflare/sentry.d.ts +47 -0
- package/dist/cloudflare/sentry.d.ts.map +1 -0
- package/dist/cloudflare/sentry.js +163 -0
- package/dist/cloudflare/sentry.js.map +1 -0
- package/dist/cloudflare/worker.d.ts +46 -0
- package/dist/cloudflare/worker.d.ts.map +1 -0
- package/dist/cloudflare/worker.js +63 -0
- package/dist/cloudflare/worker.js.map +1 -0
- package/dist/commit-integrity.d.ts +38 -0
- package/dist/commit-integrity.d.ts.map +1 -0
- package/dist/commit-integrity.js +260 -0
- package/dist/commit-integrity.js.map +1 -0
- package/dist/compaction.js.map +1 -1
- package/dist/crdt-yjs/index.d.ts +99 -0
- package/dist/crdt-yjs/index.d.ts.map +1 -0
- package/dist/crdt-yjs/index.js +629 -0
- package/dist/crdt-yjs/index.js.map +1 -0
- package/dist/d1.d.ts +13 -0
- package/dist/d1.d.ts.map +1 -0
- package/dist/d1.js +14 -0
- package/dist/d1.js.map +1 -0
- package/dist/dialect/base.d.ts +11 -1
- package/dist/dialect/base.d.ts.map +1 -1
- package/dist/dialect/base.js +43 -6
- package/dist/dialect/base.js.map +1 -1
- package/dist/dialect/helpers.d.ts +6 -0
- package/dist/dialect/helpers.d.ts.map +1 -1
- package/dist/dialect/helpers.js +24 -0
- package/dist/dialect/helpers.js.map +1 -1
- package/dist/dialect/index.js +3 -3
- package/dist/dialect/types.d.ts +40 -0
- package/dist/dialect/types.d.ts.map +1 -1
- package/dist/encrypted-crdt.d.ts +67 -0
- package/dist/encrypted-crdt.d.ts.map +1 -0
- package/dist/encrypted-crdt.js +426 -0
- package/dist/encrypted-crdt.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/filesystem/index.d.ts +44 -0
- package/dist/filesystem/index.d.ts.map +1 -0
- package/dist/filesystem/index.js +164 -0
- package/dist/filesystem/index.js.map +1 -0
- package/dist/handlers/collection.d.ts +11 -3
- package/dist/handlers/collection.d.ts.map +1 -1
- package/dist/handlers/collection.js +34 -56
- package/dist/handlers/collection.js.map +1 -1
- package/dist/handlers/create-handler.d.ts +31 -7
- package/dist/handlers/create-handler.d.ts.map +1 -1
- package/dist/handlers/create-handler.js +332 -115
- package/dist/handlers/create-handler.js.map +1 -1
- package/dist/handlers/index.js +3 -3
- package/dist/handlers/types.d.ts +86 -1
- package/dist/handlers/types.d.ts.map +1 -1
- package/dist/helpers/conflict.js.map +1 -1
- package/dist/helpers/emitted-change.js.map +1 -1
- package/dist/helpers/index.d.ts +2 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +6 -4
- package/dist/helpers/index.js.map +1 -1
- package/dist/helpers/paginate.js.map +1 -1
- package/dist/helpers/scope-authorization.d.ts +7 -0
- package/dist/helpers/scope-authorization.d.ts.map +1 -0
- package/dist/helpers/scope-authorization.js +19 -0
- package/dist/helpers/scope-authorization.js.map +1 -0
- package/dist/helpers/scope-commit-index.d.ts +12 -0
- package/dist/helpers/scope-commit-index.d.ts.map +1 -0
- package/dist/helpers/scope-commit-index.js +35 -0
- package/dist/helpers/scope-commit-index.js.map +1 -0
- package/dist/helpers/scope-strings.js.map +1 -1
- package/dist/hono/api-key-auth.d.ts +49 -0
- package/dist/hono/api-key-auth.d.ts.map +1 -0
- package/dist/hono/api-key-auth.js +108 -0
- package/dist/hono/api-key-auth.js.map +1 -0
- package/dist/hono/audit-redaction.d.ts +20 -0
- package/dist/hono/audit-redaction.d.ts.map +1 -0
- package/dist/hono/audit-redaction.js +85 -0
- package/dist/hono/audit-redaction.js.map +1 -0
- package/dist/hono/blobs.d.ts +75 -0
- package/dist/hono/blobs.d.ts.map +1 -0
- package/dist/hono/blobs.js +586 -0
- package/dist/hono/blobs.js.map +1 -0
- package/dist/hono/console/gateway.d.ts +42 -0
- package/dist/hono/console/gateway.d.ts.map +1 -0
- package/dist/hono/console/gateway.js +2158 -0
- package/dist/hono/console/gateway.js.map +1 -0
- package/dist/hono/console/live-auth.d.ts +7 -0
- package/dist/hono/console/live-auth.d.ts.map +1 -0
- package/dist/hono/console/live-auth.js +39 -0
- package/dist/hono/console/live-auth.js.map +1 -0
- package/dist/hono/console/route-descriptor.d.ts +6 -0
- package/dist/hono/console/route-descriptor.d.ts.map +1 -0
- package/dist/hono/console/route-descriptor.js +16 -0
- package/dist/hono/console/route-descriptor.js.map +1 -0
- package/dist/hono/console/routes/api-keys.d.ts +8 -0
- package/dist/hono/console/routes/api-keys.d.ts.map +1 -0
- package/dist/hono/console/routes/api-keys.js +575 -0
- package/dist/hono/console/routes/api-keys.js.map +1 -0
- package/dist/hono/console/routes/clients.d.ts +8 -0
- package/dist/hono/console/routes/clients.d.ts.map +1 -0
- package/dist/hono/console/routes/clients.js +322 -0
- package/dist/hono/console/routes/clients.js.map +1 -0
- package/dist/hono/console/routes/commits.d.ts +8 -0
- package/dist/hono/console/routes/commits.d.ts.map +1 -0
- package/dist/hono/console/routes/commits.js +882 -0
- package/dist/hono/console/routes/commits.js.map +1 -0
- package/dist/hono/console/routes/context.d.ts +188 -0
- package/dist/hono/console/routes/context.d.ts.map +1 -0
- package/dist/hono/console/routes/context.js +633 -0
- package/dist/hono/console/routes/context.js.map +1 -0
- package/dist/hono/console/routes/events.d.ts +8 -0
- package/dist/hono/console/routes/events.d.ts.map +1 -0
- package/dist/hono/console/routes/events.js +501 -0
- package/dist/hono/console/routes/events.js.map +1 -0
- package/dist/hono/console/routes/maintenance.d.ts +8 -0
- package/dist/hono/console/routes/maintenance.d.ts.map +1 -0
- package/dist/hono/console/routes/maintenance.js +341 -0
- package/dist/hono/console/routes/maintenance.js.map +1 -0
- package/dist/hono/console/routes/shared.d.ts +174 -0
- package/dist/hono/console/routes/shared.d.ts.map +1 -0
- package/dist/hono/console/routes/shared.js +583 -0
- package/dist/hono/console/routes/shared.js.map +1 -0
- package/dist/hono/console/routes/stats.d.ts +8 -0
- package/dist/hono/console/routes/stats.d.ts.map +1 -0
- package/dist/hono/console/routes/stats.js +295 -0
- package/dist/hono/console/routes/stats.js.map +1 -0
- package/dist/hono/console/routes/storage.d.ts +8 -0
- package/dist/hono/console/routes/storage.d.ts.map +1 -0
- package/dist/hono/console/routes/storage.js +121 -0
- package/dist/hono/console/routes/storage.js.map +1 -0
- package/dist/hono/console/routes.d.ts +36 -0
- package/dist/hono/console/routes.d.ts.map +1 -0
- package/dist/hono/console/routes.js +112 -0
- package/dist/hono/console/routes.js.map +1 -0
- package/dist/hono/console/schema-errors.d.ts +2 -0
- package/dist/hono/console/schema-errors.d.ts.map +1 -0
- package/dist/hono/console/schema-errors.js +17 -0
- package/dist/hono/console/schema-errors.js.map +1 -0
- package/dist/hono/console/schemas.d.ts +1515 -0
- package/dist/hono/console/schemas.d.ts.map +1 -0
- package/dist/hono/console/schemas.js +661 -0
- package/dist/hono/console/schemas.js.map +1 -0
- package/dist/hono/console/types.d.ts +213 -0
- package/dist/hono/console/types.d.ts.map +1 -0
- package/dist/hono/console/types.js +2 -0
- package/dist/hono/console/types.js.map +1 -0
- package/dist/hono/console/ui.d.ts +38 -0
- package/dist/hono/console/ui.d.ts.map +1 -0
- package/dist/hono/console/ui.js +43 -0
- package/dist/hono/console/ui.js.map +1 -0
- package/dist/hono/create-server.d.ts +71 -0
- package/dist/hono/create-server.d.ts.map +1 -0
- package/dist/hono/create-server.js +121 -0
- package/dist/hono/create-server.js.map +1 -0
- package/dist/hono/errors.d.ts +13 -0
- package/dist/hono/errors.d.ts.map +1 -0
- package/dist/hono/errors.js +21 -0
- package/dist/hono/errors.js.map +1 -0
- package/dist/hono/index.d.ts +20 -0
- package/dist/hono/index.d.ts.map +1 -0
- package/dist/hono/index.js +31 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/hono/openapi.d.ts +72 -0
- package/dist/hono/openapi.d.ts.map +1 -0
- package/dist/hono/openapi.js +99 -0
- package/dist/hono/openapi.js.map +1 -0
- package/dist/hono/proxy/connection-manager.d.ts +78 -0
- package/dist/hono/proxy/connection-manager.d.ts.map +1 -0
- package/dist/hono/proxy/connection-manager.js +251 -0
- package/dist/hono/proxy/connection-manager.js.map +1 -0
- package/dist/hono/proxy/index.d.ts +8 -0
- package/dist/hono/proxy/index.d.ts.map +1 -0
- package/dist/hono/proxy/index.js +8 -0
- package/dist/hono/proxy/index.js.map +1 -0
- package/dist/hono/proxy/routes.d.ts +86 -0
- package/dist/hono/proxy/routes.d.ts.map +1 -0
- package/dist/hono/proxy/routes.js +183 -0
- package/dist/hono/proxy/routes.js.map +1 -0
- package/dist/hono/rate-limit.d.ts +101 -0
- package/dist/hono/rate-limit.d.ts.map +1 -0
- package/dist/hono/rate-limit.js +184 -0
- package/dist/hono/rate-limit.js.map +1 -0
- package/dist/hono/realtime-sync-packs.d.ts +43 -0
- package/dist/hono/realtime-sync-packs.d.ts.map +1 -0
- package/dist/hono/realtime-sync-packs.js +219 -0
- package/dist/hono/realtime-sync-packs.js.map +1 -0
- package/dist/hono/routes/audit.d.ts +12 -0
- package/dist/hono/routes/audit.d.ts.map +1 -0
- package/dist/hono/routes/audit.js +385 -0
- package/dist/hono/routes/audit.js.map +1 -0
- package/dist/hono/routes/auth-leases.d.ts +8 -0
- package/dist/hono/routes/auth-leases.d.ts.map +1 -0
- package/dist/hono/routes/auth-leases.js +88 -0
- package/dist/hono/routes/auth-leases.js.map +1 -0
- package/dist/hono/routes/combined.d.ts +8 -0
- package/dist/hono/routes/combined.d.ts.map +1 -0
- package/dist/hono/routes/combined.js +392 -0
- package/dist/hono/routes/combined.js.map +1 -0
- package/dist/hono/routes/context.d.ts +314 -0
- package/dist/hono/routes/context.d.ts.map +1 -0
- package/dist/hono/routes/context.js +1147 -0
- package/dist/hono/routes/context.js.map +1 -0
- package/dist/hono/routes/health.d.ts +8 -0
- package/dist/hono/routes/health.d.ts.map +1 -0
- package/dist/hono/routes/health.js +16 -0
- package/dist/hono/routes/health.js.map +1 -0
- package/dist/hono/routes/realtime.d.ts +9 -0
- package/dist/hono/routes/realtime.d.ts.map +1 -0
- package/dist/hono/routes/realtime.js +626 -0
- package/dist/hono/routes/realtime.js.map +1 -0
- package/dist/hono/routes/shared.d.ts +689 -0
- package/dist/hono/routes/shared.d.ts.map +1 -0
- package/dist/hono/routes/shared.js +972 -0
- package/dist/hono/routes/shared.js.map +1 -0
- package/dist/hono/routes/snapshots.d.ts +10 -0
- package/dist/hono/routes/snapshots.d.ts.map +1 -0
- package/dist/hono/routes/snapshots.js +281 -0
- package/dist/hono/routes/snapshots.js.map +1 -0
- package/dist/hono/routes.d.ts +19 -0
- package/dist/hono/routes.d.ts.map +1 -0
- package/dist/hono/routes.js +37 -0
- package/dist/hono/routes.js.map +1 -0
- package/dist/hono/validation.d.ts +5 -0
- package/dist/hono/validation.d.ts.map +1 -0
- package/dist/hono/validation.js +47 -0
- package/dist/hono/validation.js.map +1 -0
- package/dist/hono/websocket-origin.d.ts +9 -0
- package/dist/hono/websocket-origin.d.ts.map +1 -0
- package/dist/hono/websocket-origin.js +96 -0
- package/dist/hono/websocket-origin.js.map +1 -0
- package/dist/hono/ws.d.ts +341 -0
- package/dist/hono/ws.d.ts.map +1 -0
- package/dist/hono/ws.js +711 -0
- package/dist/hono/ws.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -18
- package/dist/index.js.map +1 -1
- package/dist/libsql.d.ts +29 -0
- package/dist/libsql.d.ts.map +1 -0
- package/dist/libsql.js +25 -0
- package/dist/libsql.js.map +1 -0
- package/dist/migrate.js.map +1 -1
- package/dist/neon.d.ts +21 -0
- package/dist/neon.d.ts.map +1 -0
- package/dist/neon.js +22 -0
- package/dist/neon.js.map +1 -0
- package/dist/notify.d.ts +30 -0
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +160 -41
- package/dist/notify.js.map +1 -1
- package/dist/pglite.d.ts +37 -0
- package/dist/pglite.d.ts.map +1 -0
- package/dist/pglite.js +37 -0
- package/dist/pglite.js.map +1 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +2 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/types.d.ts +73 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +30 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/postgres/index.d.ts +68 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +924 -0
- package/dist/postgres/index.js.map +1 -0
- package/dist/proxy/collection.d.ts +0 -2
- package/dist/proxy/collection.d.ts.map +1 -1
- package/dist/proxy/collection.js +2 -17
- package/dist/proxy/collection.js.map +1 -1
- package/dist/proxy/handler.d.ts +1 -1
- package/dist/proxy/handler.d.ts.map +1 -1
- package/dist/proxy/handler.js +3 -4
- package/dist/proxy/handler.js.map +1 -1
- package/dist/proxy/index.d.ts +1 -1
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +3 -3
- package/dist/proxy/index.js.map +1 -1
- package/dist/proxy/mutation-detector.js.map +1 -1
- package/dist/proxy/oplog.d.ts.map +1 -1
- package/dist/proxy/oplog.js +32 -8
- package/dist/proxy/oplog.js.map +1 -1
- package/dist/prune.d.ts +8 -1
- package/dist/prune.d.ts.map +1 -1
- package/dist/prune.js +68 -28
- package/dist/prune.js.map +1 -1
- package/dist/pull.d.ts +31 -0
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +1014 -474
- package/dist/pull.js.map +1 -1
- package/dist/push.d.ts +36 -2
- package/dist/push.d.ts.map +1 -1
- package/dist/push.js +603 -358
- package/dist/push.js.map +1 -1
- package/dist/realtime/in-memory.js.map +1 -1
- package/dist/realtime/index.d.ts +2 -1
- package/dist/realtime/index.d.ts.map +1 -1
- package/dist/realtime/index.js +2 -1
- package/dist/realtime/index.js.map +1 -1
- package/dist/realtime/types.d.ts +12 -0
- package/dist/realtime/types.d.ts.map +1 -1
- package/dist/realtime/types.js +13 -1
- package/dist/realtime/types.js.map +1 -1
- package/dist/relay/client-role/forward-engine.d.ts +63 -0
- package/dist/relay/client-role/forward-engine.d.ts.map +1 -0
- package/dist/relay/client-role/forward-engine.js +267 -0
- package/dist/relay/client-role/forward-engine.js.map +1 -0
- package/dist/relay/client-role/index.d.ts +9 -0
- package/dist/relay/client-role/index.d.ts.map +1 -0
- package/dist/relay/client-role/index.js +9 -0
- package/dist/relay/client-role/index.js.map +1 -0
- package/dist/relay/client-role/pull-engine.d.ts +72 -0
- package/dist/relay/client-role/pull-engine.d.ts.map +1 -0
- package/dist/relay/client-role/pull-engine.js +249 -0
- package/dist/relay/client-role/pull-engine.js.map +1 -0
- package/dist/relay/client-role/sequence-mapper.d.ts +65 -0
- package/dist/relay/client-role/sequence-mapper.d.ts.map +1 -0
- package/dist/relay/client-role/sequence-mapper.js +161 -0
- package/dist/relay/client-role/sequence-mapper.js.map +1 -0
- package/dist/relay/evaluation/relay-paths.d.ts +41 -0
- package/dist/relay/evaluation/relay-paths.d.ts.map +1 -0
- package/dist/relay/evaluation/relay-paths.js +504 -0
- package/dist/relay/evaluation/relay-paths.js.map +1 -0
- package/dist/relay/evaluation/rust-boundary.d.ts +47 -0
- package/dist/relay/evaluation/rust-boundary.d.ts.map +1 -0
- package/dist/relay/evaluation/rust-boundary.js +220 -0
- package/dist/relay/evaluation/rust-boundary.js.map +1 -0
- package/dist/relay/index.d.ts +37 -0
- package/dist/relay/index.d.ts.map +1 -0
- package/dist/relay/index.js +44 -0
- package/dist/relay/index.js.map +1 -0
- package/dist/relay/migrate.d.ts +18 -0
- package/dist/relay/migrate.d.ts.map +1 -0
- package/dist/relay/migrate.js +99 -0
- package/dist/relay/migrate.js.map +1 -0
- package/dist/relay/mode-manager.d.ts +60 -0
- package/dist/relay/mode-manager.d.ts.map +1 -0
- package/dist/relay/mode-manager.js +114 -0
- package/dist/relay/mode-manager.js.map +1 -0
- package/dist/relay/realtime.d.ts +90 -0
- package/dist/relay/realtime.d.ts.map +1 -0
- package/dist/relay/realtime.js +147 -0
- package/dist/relay/realtime.js.map +1 -0
- package/dist/relay/relay.d.ts +190 -0
- package/dist/relay/relay.d.ts.map +1 -0
- package/dist/relay/relay.js +320 -0
- package/dist/relay/relay.js.map +1 -0
- package/dist/relay/schema.d.ts +158 -0
- package/dist/relay/schema.d.ts.map +1 -0
- package/dist/relay/schema.js +7 -0
- package/dist/relay/schema.js.map +1 -0
- package/dist/relay/server-role/index.d.ts +56 -0
- package/dist/relay/server-role/index.d.ts.map +1 -0
- package/dist/relay/server-role/index.js +193 -0
- package/dist/relay/server-role/index.js.map +1 -0
- package/dist/relay/server-role/pull.d.ts +27 -0
- package/dist/relay/server-role/pull.d.ts.map +1 -0
- package/dist/relay/server-role/pull.js +24 -0
- package/dist/relay/server-role/pull.js.map +1 -0
- package/dist/relay/server-role/push.d.ts +29 -0
- package/dist/relay/server-role/push.d.ts.map +1 -0
- package/dist/relay/server-role/push.js +122 -0
- package/dist/relay/server-role/push.js.map +1 -0
- package/dist/s3/index.d.ts +83 -0
- package/dist/s3/index.d.ts.map +1 -0
- package/dist/s3/index.js +223 -0
- package/dist/s3/index.js.map +1 -0
- package/dist/schema.d.ts +137 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -0
- package/dist/schema.js.map +1 -1
- package/dist/service-worker/index.d.ts +114 -0
- package/dist/service-worker/index.d.ts.map +1 -0
- package/dist/service-worker/index.js +487 -0
- package/dist/service-worker/index.js.map +1 -0
- package/dist/snapshot-artifacts/sqlite-bun.d.ts +15 -0
- package/dist/snapshot-artifacts/sqlite-bun.d.ts.map +1 -0
- package/dist/snapshot-artifacts/sqlite-bun.js +128 -0
- package/dist/snapshot-artifacts/sqlite-bun.js.map +1 -0
- package/dist/snapshot-artifacts.d.ts +206 -0
- package/dist/snapshot-artifacts.d.ts.map +1 -0
- package/dist/snapshot-artifacts.js +545 -0
- package/dist/snapshot-artifacts.js.map +1 -0
- package/dist/snapshot-chunks/db-metadata.d.ts +18 -0
- package/dist/snapshot-chunks/db-metadata.d.ts.map +1 -1
- package/dist/snapshot-chunks/db-metadata.js +91 -63
- package/dist/snapshot-chunks/db-metadata.js.map +1 -1
- package/dist/snapshot-chunks/index.js +2 -2
- package/dist/snapshot-chunks/types.d.ts +2 -0
- package/dist/snapshot-chunks/types.d.ts.map +1 -1
- package/dist/snapshot-chunks.d.ts +33 -3
- package/dist/snapshot-chunks.d.ts.map +1 -1
- package/dist/snapshot-chunks.js +136 -55
- package/dist/snapshot-chunks.js.map +1 -1
- package/dist/sqlite/index.d.ts +53 -0
- package/dist/sqlite/index.d.ts.map +1 -0
- package/dist/sqlite/index.js +795 -0
- package/dist/sqlite/index.js.map +1 -0
- package/dist/sqlite3.d.ts +22 -0
- package/dist/sqlite3.d.ts.map +1 -0
- package/dist/sqlite3.js +99 -0
- package/dist/sqlite3.js.map +1 -0
- package/dist/stats.d.ts +8 -0
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +45 -13
- package/dist/stats.js.map +1 -1
- package/dist/subscriptions/cache.d.ts +3 -0
- package/dist/subscriptions/cache.d.ts.map +1 -1
- package/dist/subscriptions/cache.js +44 -0
- package/dist/subscriptions/cache.js.map +1 -1
- package/dist/subscriptions/index.js +2 -2
- package/dist/subscriptions/resolve.d.ts +3 -1
- package/dist/subscriptions/resolve.d.ts.map +1 -1
- package/dist/subscriptions/resolve.js +91 -52
- package/dist/subscriptions/resolve.js.map +1 -1
- package/dist/sync.d.ts +3 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +9 -5
- package/dist/sync.js.map +1 -1
- package/package.json +297 -8
- package/src/auth-leases.ts +649 -0
- package/src/better-sqlite3.ts +35 -0
- package/src/blobs/access.ts +244 -0
- package/src/blobs/adapters/database.ts +125 -43
- package/src/blobs/index.ts +1 -0
- package/src/blobs/manager.ts +366 -65
- package/src/blobs/migrate.ts +16 -8
- package/src/blobs/types.ts +4 -0
- package/src/bun-sqlite-ambient.d.ts +19 -0
- package/src/bun-sqlite.ts +27 -0
- package/src/clients.ts +1 -0
- package/src/cloudflare/durable-object.ts +289 -0
- package/src/cloudflare/index.ts +22 -0
- package/src/cloudflare/r2.ts +526 -0
- package/src/cloudflare/scope-cache.ts +341 -0
- package/src/cloudflare/sentry.ts +230 -0
- package/src/cloudflare/worker.ts +77 -0
- package/src/commit-integrity.ts +371 -0
- package/src/crdt-yjs/index.ts +931 -0
- package/src/d1.ts +16 -0
- package/src/dialect/base.ts +71 -5
- package/src/dialect/helpers.ts +31 -0
- package/src/dialect/types.ts +47 -0
- package/src/encrypted-crdt.ts +786 -0
- package/src/errors.ts +22 -0
- package/src/filesystem/index.ts +262 -0
- package/src/handlers/collection.ts +78 -78
- package/src/handlers/create-handler.ts +540 -147
- package/src/handlers/types.ts +116 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/scope-authorization.ts +27 -0
- package/src/helpers/scope-commit-index.ts +52 -0
- package/src/hono/api-key-auth.ts +177 -0
- package/src/hono/audit-redaction.ts +135 -0
- package/src/hono/blobs.ts +851 -0
- package/src/hono/console/gateway.ts +3046 -0
- package/src/hono/console/live-auth.ts +46 -0
- package/src/hono/console/route-descriptor.ts +22 -0
- package/src/hono/console/routes/api-keys.ts +721 -0
- package/src/hono/console/routes/clients.ts +447 -0
- package/src/hono/console/routes/commits.ts +1137 -0
- package/src/hono/console/routes/context.ts +956 -0
- package/src/hono/console/routes/events.ts +669 -0
- package/src/hono/console/routes/maintenance.ts +461 -0
- package/src/hono/console/routes/shared.ts +816 -0
- package/src/hono/console/routes/stats.ts +392 -0
- package/src/hono/console/routes/storage.ts +159 -0
- package/src/hono/console/routes.ts +146 -0
- package/src/hono/console/schema-errors.ts +23 -0
- package/src/hono/console/schemas.ts +914 -0
- package/src/hono/console/types.ts +223 -0
- package/src/hono/console/ui.ts +100 -0
- package/src/hono/create-server.ts +230 -0
- package/src/hono/errors.ts +50 -0
- package/src/hono/index.ts +54 -0
- package/src/hono/openapi.ts +139 -0
- package/src/hono/proxy/connection-manager.ts +340 -0
- package/src/hono/proxy/index.ts +8 -0
- package/src/hono/proxy/routes.ts +272 -0
- package/src/hono/rate-limit.ts +319 -0
- package/src/hono/realtime-sync-packs.ts +354 -0
- package/src/hono/routes/audit.ts +499 -0
- package/src/hono/routes/auth-leases.ts +119 -0
- package/src/hono/routes/combined.ts +583 -0
- package/src/hono/routes/context.ts +1629 -0
- package/src/hono/routes/health.ts +26 -0
- package/src/hono/routes/realtime.ts +808 -0
- package/src/hono/routes/shared.ts +1626 -0
- package/src/hono/routes/snapshots.ts +345 -0
- package/src/hono/routes.ts +68 -0
- package/src/hono/validation.ts +81 -0
- package/src/hono/websocket-origin.ts +131 -0
- package/src/hono/ws.ts +1134 -0
- package/src/index.ts +6 -0
- package/src/libsql.ts +51 -0
- package/src/neon.ts +28 -0
- package/src/notify.ts +224 -44
- package/src/pglite.ts +68 -0
- package/src/plugins/index.ts +1 -0
- package/src/plugins/types.ts +144 -0
- package/src/postgres/index.ts +1291 -0
- package/src/proxy/collection.ts +5 -27
- package/src/proxy/handler.ts +2 -2
- package/src/proxy/index.ts +0 -2
- package/src/proxy/oplog.ts +41 -10
- package/src/prune.ts +137 -36
- package/src/pull.ts +1504 -591
- package/src/push.ts +881 -443
- package/src/realtime/index.ts +2 -0
- package/src/realtime/types.ts +35 -0
- package/src/relay/bun-types.d.ts +50 -0
- package/src/relay/client-role/forward-engine.ts +355 -0
- package/src/relay/client-role/index.ts +9 -0
- package/src/relay/client-role/pull-engine.ts +329 -0
- package/src/relay/client-role/sequence-mapper.ts +201 -0
- package/src/relay/evaluation/relay-paths.ts +699 -0
- package/src/relay/evaluation/rust-boundary.ts +464 -0
- package/src/relay/index.ts +50 -0
- package/src/relay/migrate.ts +113 -0
- package/src/relay/mode-manager.ts +142 -0
- package/src/relay/realtime.ts +207 -0
- package/src/relay/relay.ts +431 -0
- package/src/relay/schema.ts +171 -0
- package/src/relay/server-role/index.ts +338 -0
- package/src/relay/server-role/pull.ts +43 -0
- package/src/relay/server-role/push.ts +164 -0
- package/src/s3/index.ts +346 -0
- package/src/schema.ts +141 -1
- package/src/service-worker/index.ts +773 -0
- package/src/snapshot-artifacts/sqlite-bun.ts +168 -0
- package/src/snapshot-artifacts.ts +896 -0
- package/src/snapshot-chunks/db-metadata.ts +131 -72
- package/src/snapshot-chunks/types.ts +2 -0
- package/src/snapshot-chunks.ts +194 -70
- package/src/sqlite/index.ts +1064 -0
- package/src/sqlite3.ts +137 -0
- package/src/stats.ts +76 -13
- package/src/subscriptions/cache.ts +58 -0
- package/src/subscriptions/resolve.ts +99 -58
- package/src/sync.ts +16 -6
- package/src/blobs/adapters/database.test.ts +0 -67
- package/src/notify.test.ts +0 -520
- package/src/proxy/handler.test.ts +0 -126
- package/src/proxy/mutation-detector.test.ts +0 -71
- package/src/snapshot-chunks/db-metadata.test.ts +0 -172
- package/src/subscriptions/resolve.test.ts +0 -180
|
@@ -0,0 +1,1291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/server/postgres - PostgreSQL Server Sync Dialect
|
|
3
|
+
*
|
|
4
|
+
* Driver-agnostic PostgreSQL dialect for sync. Works with any Postgres-compatible
|
|
5
|
+
* Kysely dialect (pg, pglite, neon, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Tables:
|
|
8
|
+
* - sync_commits: commit log (idempotency + ordering)
|
|
9
|
+
* - sync_table_commits: commit routing index (fast pull by table)
|
|
10
|
+
* - sync_changes: change log (JSONB scopes for filtering)
|
|
11
|
+
* - sync_client_cursors: per-client cursor tracking (pruning/observability)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ScopeValues, StoredScopes, SyncOp } from '@syncular/core';
|
|
15
|
+
import type { DbExecutor } from '@syncular/server';
|
|
16
|
+
import {
|
|
17
|
+
type AuditRowHistoryArgs,
|
|
18
|
+
type AuditRowHistoryRow,
|
|
19
|
+
BaseServerSyncDialect,
|
|
20
|
+
coerceIsoString,
|
|
21
|
+
coerceNumber,
|
|
22
|
+
createSavepointName,
|
|
23
|
+
type IncrementalPullRow,
|
|
24
|
+
type IncrementalPullRowsArgs,
|
|
25
|
+
isActiveTransaction,
|
|
26
|
+
parseScopes,
|
|
27
|
+
} from '@syncular/server';
|
|
28
|
+
import type { SyncChangeRow, SyncCoreDb } from '@syncular/server/schema';
|
|
29
|
+
import type { Kysely, RawBuilder, Transaction } from 'kysely';
|
|
30
|
+
import { sql } from 'kysely';
|
|
31
|
+
|
|
32
|
+
function buildJsonbScopeContainmentFilter(
|
|
33
|
+
columnReference: string,
|
|
34
|
+
scopes: ScopeValues
|
|
35
|
+
): RawBuilder<unknown> {
|
|
36
|
+
const fragments: RawBuilder<unknown>[] = [];
|
|
37
|
+
|
|
38
|
+
for (const [key, value] of Object.entries(scopes)) {
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
if (value.length === 0) {
|
|
41
|
+
fragments.push(sql`FALSE`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const anyValueMatches = sql.join(
|
|
46
|
+
value.map((entry) => {
|
|
47
|
+
const scopeFilter = JSON.stringify({ [key]: entry });
|
|
48
|
+
return sql`${sql.ref(columnReference)} @> ${scopeFilter}::jsonb`;
|
|
49
|
+
}),
|
|
50
|
+
sql` OR `
|
|
51
|
+
);
|
|
52
|
+
fragments.push(sql`(${anyValueMatches})`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const scopeFilter = JSON.stringify({ [key]: value });
|
|
57
|
+
fragments.push(sql`${sql.ref(columnReference)} @> ${scopeFilter}::jsonb`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (fragments.length === 0) {
|
|
61
|
+
return sql`TRUE`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return sql.join(fragments, sql` AND `);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class PostgresServerSyncDialect extends BaseServerSyncDialect<'postgres'> {
|
|
68
|
+
readonly family = 'postgres' as const;
|
|
69
|
+
readonly supportsForUpdate = true;
|
|
70
|
+
readonly supportsSavepoints = true;
|
|
71
|
+
readonly supportsInsertReturning = true;
|
|
72
|
+
|
|
73
|
+
// ===========================================================================
|
|
74
|
+
// SQL Fragment Hooks
|
|
75
|
+
// ===========================================================================
|
|
76
|
+
|
|
77
|
+
protected buildNumberListFilter(values: number[]): RawBuilder<unknown> {
|
|
78
|
+
return sql`= ANY(${values}::bigint[])`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected buildStringListFilter(values: string[]): RawBuilder<unknown> {
|
|
82
|
+
return sql`= ANY(${values}::text[])`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ===========================================================================
|
|
86
|
+
// Schema Setup
|
|
87
|
+
// ===========================================================================
|
|
88
|
+
|
|
89
|
+
async ensureSyncSchema<DB extends SyncCoreDb>(db: Kysely<DB>): Promise<void> {
|
|
90
|
+
await db.schema
|
|
91
|
+
.createTable('sync_commits')
|
|
92
|
+
.ifNotExists()
|
|
93
|
+
.addColumn('commit_seq', 'bigserial', (col) => col.primaryKey())
|
|
94
|
+
.addColumn('partition_id', 'text', (col) =>
|
|
95
|
+
col.notNull().defaultTo('default')
|
|
96
|
+
)
|
|
97
|
+
.addColumn('actor_id', 'text', (col) => col.notNull())
|
|
98
|
+
.addColumn('client_id', 'text', (col) => col.notNull())
|
|
99
|
+
.addColumn('client_commit_id', 'text', (col) => col.notNull())
|
|
100
|
+
.addColumn('created_at', 'timestamptz', (col) =>
|
|
101
|
+
col.notNull().defaultTo(sql`now()`)
|
|
102
|
+
)
|
|
103
|
+
.addColumn('meta', 'jsonb')
|
|
104
|
+
.addColumn('result_json', 'jsonb')
|
|
105
|
+
.addColumn('change_count', 'integer', (col) => col.notNull().defaultTo(0))
|
|
106
|
+
.addColumn('commit_digest', 'text')
|
|
107
|
+
.addColumn('commit_chain_root', 'text')
|
|
108
|
+
.addColumn('affected_tables', sql`text[]`, (col) =>
|
|
109
|
+
col.notNull().defaultTo(sql`ARRAY[]::text[]`)
|
|
110
|
+
)
|
|
111
|
+
.execute();
|
|
112
|
+
|
|
113
|
+
// Ensure new columns exist for dev environments that already created the table.
|
|
114
|
+
await sql`ALTER TABLE sync_commits
|
|
115
|
+
ADD COLUMN IF NOT EXISTS change_count integer NOT NULL DEFAULT 0`.execute(
|
|
116
|
+
db
|
|
117
|
+
);
|
|
118
|
+
await sql`ALTER TABLE sync_commits
|
|
119
|
+
ADD COLUMN IF NOT EXISTS affected_tables text[] NOT NULL DEFAULT ARRAY[]::text[]`.execute(
|
|
120
|
+
db
|
|
121
|
+
);
|
|
122
|
+
await sql`ALTER TABLE sync_commits
|
|
123
|
+
ADD COLUMN IF NOT EXISTS partition_id text NOT NULL DEFAULT 'default'`.execute(
|
|
124
|
+
db
|
|
125
|
+
);
|
|
126
|
+
await sql`ALTER TABLE sync_commits
|
|
127
|
+
ADD COLUMN IF NOT EXISTS commit_digest text`.execute(db);
|
|
128
|
+
await sql`ALTER TABLE sync_commits
|
|
129
|
+
ADD COLUMN IF NOT EXISTS commit_chain_root text`.execute(db);
|
|
130
|
+
|
|
131
|
+
await sql`DROP INDEX IF EXISTS idx_sync_commits_client_commit`.execute(db);
|
|
132
|
+
await db.schema
|
|
133
|
+
.createIndex('idx_sync_commits_client_commit')
|
|
134
|
+
.ifNotExists()
|
|
135
|
+
.on('sync_commits')
|
|
136
|
+
.columns(['partition_id', 'client_id', 'client_commit_id'])
|
|
137
|
+
.unique()
|
|
138
|
+
.execute();
|
|
139
|
+
await db.schema
|
|
140
|
+
.createIndex('idx_sync_commits_partition_client_commit_seq')
|
|
141
|
+
.ifNotExists()
|
|
142
|
+
.on('sync_commits')
|
|
143
|
+
.columns(['partition_id', 'client_id', 'commit_seq'])
|
|
144
|
+
.execute();
|
|
145
|
+
|
|
146
|
+
// Table-based commit routing index
|
|
147
|
+
await db.schema
|
|
148
|
+
.createTable('sync_table_commits')
|
|
149
|
+
.ifNotExists()
|
|
150
|
+
.addColumn('partition_id', 'text', (col) =>
|
|
151
|
+
col.notNull().defaultTo('default')
|
|
152
|
+
)
|
|
153
|
+
.addColumn('table', 'text', (col) => col.notNull())
|
|
154
|
+
.addColumn('commit_seq', 'bigint', (col) =>
|
|
155
|
+
col.notNull().references('sync_commits.commit_seq').onDelete('cascade')
|
|
156
|
+
)
|
|
157
|
+
.addPrimaryKeyConstraint('sync_table_commits_pk', [
|
|
158
|
+
'partition_id',
|
|
159
|
+
'table',
|
|
160
|
+
'commit_seq',
|
|
161
|
+
])
|
|
162
|
+
.execute();
|
|
163
|
+
|
|
164
|
+
await sql`ALTER TABLE sync_table_commits
|
|
165
|
+
ADD COLUMN IF NOT EXISTS partition_id text NOT NULL DEFAULT 'default'`.execute(
|
|
166
|
+
db
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
await db.schema
|
|
170
|
+
.createIndex('idx_sync_table_commits_commit_seq')
|
|
171
|
+
.ifNotExists()
|
|
172
|
+
.on('sync_table_commits')
|
|
173
|
+
.columns(['partition_id', 'commit_seq'])
|
|
174
|
+
.execute();
|
|
175
|
+
|
|
176
|
+
await db.schema
|
|
177
|
+
.createTable('sync_scope_commits')
|
|
178
|
+
.ifNotExists()
|
|
179
|
+
.addColumn('partition_id', 'text', (col) =>
|
|
180
|
+
col.notNull().defaultTo('default')
|
|
181
|
+
)
|
|
182
|
+
.addColumn('table', 'text', (col) => col.notNull())
|
|
183
|
+
.addColumn('scope_key', 'text', (col) => col.notNull())
|
|
184
|
+
.addColumn('commit_seq', 'bigint', (col) =>
|
|
185
|
+
col.notNull().references('sync_commits.commit_seq').onDelete('cascade')
|
|
186
|
+
)
|
|
187
|
+
.addPrimaryKeyConstraint('sync_scope_commits_pk', [
|
|
188
|
+
'partition_id',
|
|
189
|
+
'table',
|
|
190
|
+
'scope_key',
|
|
191
|
+
'commit_seq',
|
|
192
|
+
])
|
|
193
|
+
.execute();
|
|
194
|
+
|
|
195
|
+
await sql`ALTER TABLE sync_scope_commits
|
|
196
|
+
ADD COLUMN IF NOT EXISTS partition_id text NOT NULL DEFAULT 'default'`.execute(
|
|
197
|
+
db
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
await db.schema
|
|
201
|
+
.createIndex('idx_sync_scope_commits_commit_seq')
|
|
202
|
+
.ifNotExists()
|
|
203
|
+
.on('sync_scope_commits')
|
|
204
|
+
.columns(['partition_id', 'commit_seq'])
|
|
205
|
+
.execute();
|
|
206
|
+
|
|
207
|
+
// Changes table with JSONB scopes
|
|
208
|
+
await db.schema
|
|
209
|
+
.createTable('sync_changes')
|
|
210
|
+
.ifNotExists()
|
|
211
|
+
.addColumn('change_id', 'bigserial', (col) => col.primaryKey())
|
|
212
|
+
.addColumn('partition_id', 'text', (col) =>
|
|
213
|
+
col.notNull().defaultTo('default')
|
|
214
|
+
)
|
|
215
|
+
.addColumn('commit_seq', 'bigint', (col) =>
|
|
216
|
+
col.notNull().references('sync_commits.commit_seq').onDelete('cascade')
|
|
217
|
+
)
|
|
218
|
+
.addColumn('table', 'text', (col) => col.notNull())
|
|
219
|
+
.addColumn('row_id', 'text', (col) => col.notNull())
|
|
220
|
+
.addColumn('op', 'text', (col) => col.notNull())
|
|
221
|
+
.addColumn('row_json', 'jsonb')
|
|
222
|
+
.addColumn('row_version', 'bigint')
|
|
223
|
+
.addColumn('scopes', 'jsonb', (col) => col.notNull())
|
|
224
|
+
.execute();
|
|
225
|
+
|
|
226
|
+
await sql`ALTER TABLE sync_changes
|
|
227
|
+
ADD COLUMN IF NOT EXISTS partition_id text NOT NULL DEFAULT 'default'`.execute(
|
|
228
|
+
db
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
await db.schema
|
|
232
|
+
.createIndex('idx_sync_changes_commit_seq')
|
|
233
|
+
.ifNotExists()
|
|
234
|
+
.on('sync_changes')
|
|
235
|
+
.columns(['partition_id', 'commit_seq'])
|
|
236
|
+
.execute();
|
|
237
|
+
|
|
238
|
+
await db.schema
|
|
239
|
+
.createIndex('idx_sync_changes_table')
|
|
240
|
+
.ifNotExists()
|
|
241
|
+
.on('sync_changes')
|
|
242
|
+
.columns(['partition_id', 'table'])
|
|
243
|
+
.execute();
|
|
244
|
+
|
|
245
|
+
await db.schema
|
|
246
|
+
.createIndex('idx_sync_changes_table_commit_seq_change_id')
|
|
247
|
+
.ifNotExists()
|
|
248
|
+
.on('sync_changes')
|
|
249
|
+
.columns(['partition_id', 'table', 'commit_seq', 'change_id'])
|
|
250
|
+
.execute();
|
|
251
|
+
|
|
252
|
+
await this.ensureIndex(
|
|
253
|
+
db,
|
|
254
|
+
'idx_sync_changes_scopes',
|
|
255
|
+
'CREATE INDEX idx_sync_changes_scopes ON sync_changes USING GIN (scopes)'
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
await db.schema
|
|
259
|
+
.createTable('sync_client_cursors')
|
|
260
|
+
.ifNotExists()
|
|
261
|
+
.addColumn('partition_id', 'text', (col) =>
|
|
262
|
+
col.notNull().defaultTo('default')
|
|
263
|
+
)
|
|
264
|
+
.addColumn('client_id', 'text', (col) => col.notNull())
|
|
265
|
+
.addColumn('actor_id', 'text', (col) => col.notNull())
|
|
266
|
+
.addColumn('cursor', 'bigint', (col) => col.notNull().defaultTo(0))
|
|
267
|
+
.addColumn('effective_scopes', 'jsonb', (col) =>
|
|
268
|
+
col.notNull().defaultTo(sql`'{}'::jsonb`)
|
|
269
|
+
)
|
|
270
|
+
.addColumn('realtime_subscriptions', 'jsonb')
|
|
271
|
+
.addColumn('updated_at', 'timestamptz', (col) =>
|
|
272
|
+
col.notNull().defaultTo(sql`now()`)
|
|
273
|
+
)
|
|
274
|
+
.addPrimaryKeyConstraint('sync_client_cursors_pk', [
|
|
275
|
+
'partition_id',
|
|
276
|
+
'client_id',
|
|
277
|
+
])
|
|
278
|
+
.execute();
|
|
279
|
+
|
|
280
|
+
await sql`ALTER TABLE sync_client_cursors
|
|
281
|
+
ADD COLUMN IF NOT EXISTS partition_id text NOT NULL DEFAULT 'default'`.execute(
|
|
282
|
+
db
|
|
283
|
+
);
|
|
284
|
+
await sql`ALTER TABLE sync_client_cursors
|
|
285
|
+
ADD COLUMN IF NOT EXISTS realtime_subscriptions jsonb`.execute(db);
|
|
286
|
+
|
|
287
|
+
await db.schema
|
|
288
|
+
.createIndex('idx_sync_client_cursors_updated_at')
|
|
289
|
+
.ifNotExists()
|
|
290
|
+
.on('sync_client_cursors')
|
|
291
|
+
.columns(['updated_at'])
|
|
292
|
+
.execute();
|
|
293
|
+
|
|
294
|
+
await db.schema
|
|
295
|
+
.createTable('sync_snapshot_chunks')
|
|
296
|
+
.ifNotExists()
|
|
297
|
+
.addColumn('chunk_id', 'text', (col) => col.primaryKey())
|
|
298
|
+
.addColumn('partition_id', 'text', (col) =>
|
|
299
|
+
col.notNull().defaultTo('default')
|
|
300
|
+
)
|
|
301
|
+
.addColumn('scope_key', 'text', (col) => col.notNull())
|
|
302
|
+
.addColumn('scope', 'text', (col) => col.notNull())
|
|
303
|
+
.addColumn('as_of_commit_seq', 'bigint', (col) => col.notNull())
|
|
304
|
+
.addColumn('row_cursor', 'text', (col) => col.notNull().defaultTo(''))
|
|
305
|
+
.addColumn('row_limit', 'integer', (col) => col.notNull())
|
|
306
|
+
.addColumn('next_row_cursor', 'text')
|
|
307
|
+
.addColumn('is_last_page', 'integer', (col) => col.notNull().defaultTo(0))
|
|
308
|
+
.addColumn('encoding', 'text', (col) => col.notNull())
|
|
309
|
+
.addColumn('compression', 'text', (col) => col.notNull())
|
|
310
|
+
.addColumn('sha256', 'text', (col) => col.notNull())
|
|
311
|
+
.addColumn('byte_length', 'integer', (col) => col.notNull())
|
|
312
|
+
.addColumn('blob_hash', 'text', (col) => col.notNull().defaultTo(''))
|
|
313
|
+
.addColumn('body', 'bytea') // Deprecated: use blob storage
|
|
314
|
+
.addColumn('created_at', 'timestamptz', (col) =>
|
|
315
|
+
col.notNull().defaultTo(sql`now()`)
|
|
316
|
+
)
|
|
317
|
+
.addColumn('expires_at', 'timestamptz', (col) => col.notNull())
|
|
318
|
+
.execute();
|
|
319
|
+
|
|
320
|
+
await sql`ALTER TABLE sync_snapshot_chunks
|
|
321
|
+
ADD COLUMN IF NOT EXISTS partition_id text NOT NULL DEFAULT 'default'`.execute(
|
|
322
|
+
db
|
|
323
|
+
);
|
|
324
|
+
await sql`ALTER TABLE sync_snapshot_chunks
|
|
325
|
+
ADD COLUMN IF NOT EXISTS next_row_cursor text`.execute(db);
|
|
326
|
+
await sql`ALTER TABLE sync_snapshot_chunks
|
|
327
|
+
ADD COLUMN IF NOT EXISTS is_last_page integer NOT NULL DEFAULT 0`.execute(
|
|
328
|
+
db
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
await db.schema
|
|
332
|
+
.createIndex('idx_sync_snapshot_chunks_expires_at')
|
|
333
|
+
.ifNotExists()
|
|
334
|
+
.on('sync_snapshot_chunks')
|
|
335
|
+
.columns(['expires_at'])
|
|
336
|
+
.execute();
|
|
337
|
+
|
|
338
|
+
await db.schema
|
|
339
|
+
.createIndex('idx_sync_snapshot_chunks_page_key')
|
|
340
|
+
.ifNotExists()
|
|
341
|
+
.on('sync_snapshot_chunks')
|
|
342
|
+
.columns([
|
|
343
|
+
'partition_id',
|
|
344
|
+
'scope_key',
|
|
345
|
+
'scope',
|
|
346
|
+
'as_of_commit_seq',
|
|
347
|
+
'row_cursor',
|
|
348
|
+
'row_limit',
|
|
349
|
+
'encoding',
|
|
350
|
+
'compression',
|
|
351
|
+
])
|
|
352
|
+
.unique()
|
|
353
|
+
.execute();
|
|
354
|
+
|
|
355
|
+
await db.schema
|
|
356
|
+
.createTable('sync_snapshot_artifacts')
|
|
357
|
+
.ifNotExists()
|
|
358
|
+
.addColumn('artifact_id', 'text', (col) => col.primaryKey())
|
|
359
|
+
.addColumn('partition_id', 'text', (col) =>
|
|
360
|
+
col.notNull().defaultTo('default')
|
|
361
|
+
)
|
|
362
|
+
.addColumn('scope_key', 'text', (col) => col.notNull())
|
|
363
|
+
.addColumn('subscription_id', 'text', (col) => col.notNull())
|
|
364
|
+
.addColumn('table', 'text', (col) => col.notNull())
|
|
365
|
+
.addColumn('artifact_kind', 'text', (col) => col.notNull())
|
|
366
|
+
.addColumn('schema_version', 'text', (col) => col.notNull())
|
|
367
|
+
.addColumn('as_of_commit_seq', 'bigint', (col) => col.notNull())
|
|
368
|
+
.addColumn('row_cursor', 'text', (col) => col.notNull().defaultTo(''))
|
|
369
|
+
.addColumn('row_limit', 'integer', (col) => col.notNull())
|
|
370
|
+
.addColumn('row_count', 'integer', (col) => col.notNull())
|
|
371
|
+
.addColumn('next_row_cursor', 'text')
|
|
372
|
+
.addColumn('is_first_page', 'integer', (col) =>
|
|
373
|
+
col.notNull().defaultTo(0)
|
|
374
|
+
)
|
|
375
|
+
.addColumn('is_last_page', 'integer', (col) => col.notNull().defaultTo(0))
|
|
376
|
+
.addColumn('compression', 'text', (col) => col.notNull())
|
|
377
|
+
.addColumn('sha256', 'text', (col) => col.notNull())
|
|
378
|
+
.addColumn('byte_length', 'integer', (col) => col.notNull())
|
|
379
|
+
.addColumn('manifest_digest', 'text', (col) => col.notNull())
|
|
380
|
+
.addColumn('feature_set_json', 'text', (col) => col.notNull())
|
|
381
|
+
.addColumn('manifest_json', 'text', (col) => col.notNull())
|
|
382
|
+
.addColumn('blob_hash', 'text', (col) => col.notNull())
|
|
383
|
+
.addColumn('created_at', 'timestamptz', (col) =>
|
|
384
|
+
col.notNull().defaultTo(sql`now()`)
|
|
385
|
+
)
|
|
386
|
+
.addColumn('expires_at', 'timestamptz', (col) => col.notNull())
|
|
387
|
+
.execute();
|
|
388
|
+
|
|
389
|
+
await db.schema
|
|
390
|
+
.createIndex('idx_sync_snapshot_artifacts_expires_at')
|
|
391
|
+
.ifNotExists()
|
|
392
|
+
.on('sync_snapshot_artifacts')
|
|
393
|
+
.columns(['expires_at'])
|
|
394
|
+
.execute();
|
|
395
|
+
|
|
396
|
+
await db.schema
|
|
397
|
+
.createIndex('idx_sync_snapshot_artifacts_page_key')
|
|
398
|
+
.ifNotExists()
|
|
399
|
+
.on('sync_snapshot_artifacts')
|
|
400
|
+
.columns([
|
|
401
|
+
'partition_id',
|
|
402
|
+
'scope_key',
|
|
403
|
+
'subscription_id',
|
|
404
|
+
'table',
|
|
405
|
+
'as_of_commit_seq',
|
|
406
|
+
'row_cursor',
|
|
407
|
+
'row_limit',
|
|
408
|
+
'artifact_kind',
|
|
409
|
+
'schema_version',
|
|
410
|
+
'compression',
|
|
411
|
+
])
|
|
412
|
+
.unique()
|
|
413
|
+
.execute();
|
|
414
|
+
|
|
415
|
+
await db.schema
|
|
416
|
+
.createTable('sync_crdt_updates')
|
|
417
|
+
.ifNotExists()
|
|
418
|
+
.addColumn('seq', 'bigserial', (col) => col.primaryKey())
|
|
419
|
+
.addColumn('partition_id', 'text', (col) =>
|
|
420
|
+
col.notNull().defaultTo('default')
|
|
421
|
+
)
|
|
422
|
+
.addColumn('stream_id', 'text', (col) => col.notNull())
|
|
423
|
+
.addColumn('app_table', 'text', (col) => col.notNull())
|
|
424
|
+
.addColumn('row_id', 'text', (col) => col.notNull())
|
|
425
|
+
.addColumn('field_name', 'text', (col) => col.notNull())
|
|
426
|
+
.addColumn('update_id', 'text', (col) => col.notNull())
|
|
427
|
+
.addColumn('actor_id', 'text')
|
|
428
|
+
.addColumn('client_id', 'text')
|
|
429
|
+
.addColumn('key_id', 'text', (col) => col.notNull())
|
|
430
|
+
.addColumn('ciphertext', 'text', (col) => col.notNull())
|
|
431
|
+
.addColumn('scopes', 'jsonb', (col) =>
|
|
432
|
+
col.notNull().defaultTo(sql`'{}'::jsonb`)
|
|
433
|
+
)
|
|
434
|
+
.addColumn('created_at', 'timestamptz', (col) =>
|
|
435
|
+
col.notNull().defaultTo(sql`now()`)
|
|
436
|
+
)
|
|
437
|
+
.execute();
|
|
438
|
+
await sql`ALTER TABLE sync_crdt_updates
|
|
439
|
+
ADD COLUMN IF NOT EXISTS partition_id text NOT NULL DEFAULT 'default'`.execute(
|
|
440
|
+
db
|
|
441
|
+
);
|
|
442
|
+
await db.schema
|
|
443
|
+
.createIndex('idx_sync_crdt_updates_update_id')
|
|
444
|
+
.ifNotExists()
|
|
445
|
+
.on('sync_crdt_updates')
|
|
446
|
+
.columns(['partition_id', 'update_id'])
|
|
447
|
+
.unique()
|
|
448
|
+
.execute();
|
|
449
|
+
await db.schema
|
|
450
|
+
.createIndex('idx_sync_crdt_updates_stream_seq')
|
|
451
|
+
.ifNotExists()
|
|
452
|
+
.on('sync_crdt_updates')
|
|
453
|
+
.columns(['partition_id', 'stream_id', 'seq'])
|
|
454
|
+
.execute();
|
|
455
|
+
await db.schema
|
|
456
|
+
.createIndex('idx_sync_crdt_updates_scope_table')
|
|
457
|
+
.ifNotExists()
|
|
458
|
+
.on('sync_crdt_updates')
|
|
459
|
+
.columns(['partition_id', 'app_table', 'row_id', 'field_name'])
|
|
460
|
+
.execute();
|
|
461
|
+
await this.ensureIndex(
|
|
462
|
+
db,
|
|
463
|
+
'idx_sync_crdt_updates_scopes',
|
|
464
|
+
'CREATE INDEX idx_sync_crdt_updates_scopes ON sync_crdt_updates USING GIN (scopes)'
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
await db.schema
|
|
468
|
+
.createTable('sync_crdt_checkpoints')
|
|
469
|
+
.ifNotExists()
|
|
470
|
+
.addColumn('seq', 'bigserial', (col) => col.primaryKey())
|
|
471
|
+
.addColumn('partition_id', 'text', (col) =>
|
|
472
|
+
col.notNull().defaultTo('default')
|
|
473
|
+
)
|
|
474
|
+
.addColumn('stream_id', 'text', (col) => col.notNull())
|
|
475
|
+
.addColumn('app_table', 'text', (col) => col.notNull())
|
|
476
|
+
.addColumn('row_id', 'text', (col) => col.notNull())
|
|
477
|
+
.addColumn('field_name', 'text', (col) => col.notNull())
|
|
478
|
+
.addColumn('checkpoint_id', 'text', (col) => col.notNull())
|
|
479
|
+
.addColumn('covers_seq', 'bigint', (col) => col.notNull())
|
|
480
|
+
.addColumn('actor_id', 'text')
|
|
481
|
+
.addColumn('client_id', 'text')
|
|
482
|
+
.addColumn('key_id', 'text', (col) => col.notNull())
|
|
483
|
+
.addColumn('ciphertext', 'text', (col) => col.notNull())
|
|
484
|
+
.addColumn('scopes', 'jsonb', (col) =>
|
|
485
|
+
col.notNull().defaultTo(sql`'{}'::jsonb`)
|
|
486
|
+
)
|
|
487
|
+
.addColumn('created_at', 'timestamptz', (col) =>
|
|
488
|
+
col.notNull().defaultTo(sql`now()`)
|
|
489
|
+
)
|
|
490
|
+
.execute();
|
|
491
|
+
await sql`ALTER TABLE sync_crdt_checkpoints
|
|
492
|
+
ADD COLUMN IF NOT EXISTS partition_id text NOT NULL DEFAULT 'default'`.execute(
|
|
493
|
+
db
|
|
494
|
+
);
|
|
495
|
+
await db.schema
|
|
496
|
+
.createIndex('idx_sync_crdt_checkpoints_checkpoint_id')
|
|
497
|
+
.ifNotExists()
|
|
498
|
+
.on('sync_crdt_checkpoints')
|
|
499
|
+
.columns(['partition_id', 'checkpoint_id'])
|
|
500
|
+
.unique()
|
|
501
|
+
.execute();
|
|
502
|
+
await db.schema
|
|
503
|
+
.createIndex('idx_sync_crdt_checkpoints_stream_covers')
|
|
504
|
+
.ifNotExists()
|
|
505
|
+
.on('sync_crdt_checkpoints')
|
|
506
|
+
.columns(['partition_id', 'stream_id', 'covers_seq'])
|
|
507
|
+
.execute();
|
|
508
|
+
await db.schema
|
|
509
|
+
.createIndex('idx_sync_crdt_checkpoints_scope_table')
|
|
510
|
+
.ifNotExists()
|
|
511
|
+
.on('sync_crdt_checkpoints')
|
|
512
|
+
.columns(['partition_id', 'app_table', 'row_id', 'field_name'])
|
|
513
|
+
.execute();
|
|
514
|
+
await this.ensureIndex(
|
|
515
|
+
db,
|
|
516
|
+
'idx_sync_crdt_checkpoints_scopes',
|
|
517
|
+
'CREATE INDEX idx_sync_crdt_checkpoints_scopes ON sync_crdt_checkpoints USING GIN (scopes)'
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ===========================================================================
|
|
522
|
+
// Transaction Control
|
|
523
|
+
// ===========================================================================
|
|
524
|
+
|
|
525
|
+
async executeInTransaction<DB extends SyncCoreDb, T>(
|
|
526
|
+
db: Kysely<DB>,
|
|
527
|
+
fn: (executor: DbExecutor<DB>) => Promise<T>
|
|
528
|
+
): Promise<T> {
|
|
529
|
+
if (isActiveTransaction(db)) {
|
|
530
|
+
const savepoint = createSavepointName();
|
|
531
|
+
await sql.raw(`SAVEPOINT ${savepoint}`).execute(db);
|
|
532
|
+
try {
|
|
533
|
+
const result = await fn(db);
|
|
534
|
+
await sql.raw(`RELEASE SAVEPOINT ${savepoint}`).execute(db);
|
|
535
|
+
return result;
|
|
536
|
+
} catch (error) {
|
|
537
|
+
await sql.raw(`ROLLBACK TO SAVEPOINT ${savepoint}`).execute(db);
|
|
538
|
+
await sql.raw(`RELEASE SAVEPOINT ${savepoint}`).execute(db);
|
|
539
|
+
throw error;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return db.transaction().execute(fn);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async setRepeatableRead<DB extends SyncCoreDb>(
|
|
546
|
+
trx: DbExecutor<DB>
|
|
547
|
+
): Promise<void> {
|
|
548
|
+
await sql`SET TRANSACTION ISOLATION LEVEL REPEATABLE READ`.execute(trx);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ===========================================================================
|
|
552
|
+
// Overrides (dialect-specific optimizations / casts)
|
|
553
|
+
// ===========================================================================
|
|
554
|
+
|
|
555
|
+
async readCommitSeqsForPull<DB extends SyncCoreDb>(
|
|
556
|
+
db: Kysely<DB> | Transaction<DB>,
|
|
557
|
+
args: {
|
|
558
|
+
cursor: number;
|
|
559
|
+
limitCommits: number;
|
|
560
|
+
tables: string[];
|
|
561
|
+
partitionId?: string;
|
|
562
|
+
}
|
|
563
|
+
): Promise<number[]> {
|
|
564
|
+
const partitionId = args.partitionId ?? 'default';
|
|
565
|
+
if (args.tables.length === 0) return [];
|
|
566
|
+
|
|
567
|
+
// Single-table fast path: skip DISTINCT since (partition_id, table, commit_seq) is PK
|
|
568
|
+
if (args.tables.length === 1) {
|
|
569
|
+
const res = await sql<{ commit_seq: unknown }>`
|
|
570
|
+
SELECT commit_seq
|
|
571
|
+
FROM sync_table_commits
|
|
572
|
+
WHERE partition_id = ${partitionId}
|
|
573
|
+
AND "table" = ${args.tables[0]}
|
|
574
|
+
AND commit_seq > ${args.cursor}
|
|
575
|
+
ORDER BY commit_seq ASC
|
|
576
|
+
LIMIT ${args.limitCommits}
|
|
577
|
+
`.execute(db);
|
|
578
|
+
|
|
579
|
+
return res.rows
|
|
580
|
+
.map((r) => coerceNumber(r.commit_seq))
|
|
581
|
+
.filter(
|
|
582
|
+
(n): n is number =>
|
|
583
|
+
typeof n === 'number' && Number.isFinite(n) && n > args.cursor
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Multi-table: use ANY() with DISTINCT
|
|
588
|
+
return super.readCommitSeqsForPull(db, args);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async recordClientCursor<DB extends SyncCoreDb>(
|
|
592
|
+
db: Kysely<DB> | Transaction<DB>,
|
|
593
|
+
args: {
|
|
594
|
+
partitionId?: string;
|
|
595
|
+
clientId: string;
|
|
596
|
+
actorId: string;
|
|
597
|
+
cursor: number;
|
|
598
|
+
effectiveScopes: ScopeValues;
|
|
599
|
+
realtimeSubscriptions?: unknown;
|
|
600
|
+
}
|
|
601
|
+
): Promise<void> {
|
|
602
|
+
const partitionId = args.partitionId ?? 'default';
|
|
603
|
+
const now = new Date().toISOString();
|
|
604
|
+
const scopesJson = JSON.stringify(args.effectiveScopes);
|
|
605
|
+
|
|
606
|
+
if (args.realtimeSubscriptions === undefined) {
|
|
607
|
+
await sql`
|
|
608
|
+
INSERT INTO sync_client_cursors (partition_id, client_id, actor_id, cursor, effective_scopes, updated_at)
|
|
609
|
+
VALUES (${partitionId}, ${args.clientId}, ${args.actorId}, ${args.cursor}, ${scopesJson}::jsonb, ${now})
|
|
610
|
+
ON CONFLICT(partition_id, client_id) DO UPDATE SET
|
|
611
|
+
actor_id = ${args.actorId},
|
|
612
|
+
cursor = ${args.cursor},
|
|
613
|
+
effective_scopes = ${scopesJson}::jsonb,
|
|
614
|
+
updated_at = ${now}
|
|
615
|
+
`.execute(db);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const realtimeSubscriptionsJson = JSON.stringify(
|
|
620
|
+
args.realtimeSubscriptions
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
await sql`
|
|
624
|
+
INSERT INTO sync_client_cursors (partition_id, client_id, actor_id, cursor, effective_scopes, realtime_subscriptions, updated_at)
|
|
625
|
+
VALUES (${partitionId}, ${args.clientId}, ${args.actorId}, ${args.cursor}, ${scopesJson}::jsonb, ${realtimeSubscriptionsJson}::jsonb, ${now})
|
|
626
|
+
ON CONFLICT(partition_id, client_id) DO UPDATE SET
|
|
627
|
+
actor_id = ${args.actorId},
|
|
628
|
+
cursor = ${args.cursor},
|
|
629
|
+
effective_scopes = ${scopesJson}::jsonb,
|
|
630
|
+
realtime_subscriptions = ${realtimeSubscriptionsJson}::jsonb,
|
|
631
|
+
updated_at = ${now}
|
|
632
|
+
`.execute(db);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// ===========================================================================
|
|
636
|
+
// Commit/Change Log Queries (dialect-specific)
|
|
637
|
+
// ===========================================================================
|
|
638
|
+
|
|
639
|
+
async readChangesForCommits<DB extends SyncCoreDb>(
|
|
640
|
+
db: DbExecutor<DB>,
|
|
641
|
+
args: {
|
|
642
|
+
commitSeqs: number[];
|
|
643
|
+
table: string;
|
|
644
|
+
scopes: ScopeValues;
|
|
645
|
+
partitionId?: string;
|
|
646
|
+
}
|
|
647
|
+
): Promise<SyncChangeRow[]> {
|
|
648
|
+
const partitionId = args.partitionId ?? 'default';
|
|
649
|
+
if (args.commitSeqs.length === 0) return [];
|
|
650
|
+
|
|
651
|
+
const scopeFilter = buildJsonbScopeContainmentFilter('scopes', args.scopes);
|
|
652
|
+
|
|
653
|
+
const res = await sql<{
|
|
654
|
+
commit_seq: unknown;
|
|
655
|
+
table: string;
|
|
656
|
+
row_id: string;
|
|
657
|
+
op: string;
|
|
658
|
+
row_json: unknown | null;
|
|
659
|
+
row_version: unknown | null;
|
|
660
|
+
scopes: unknown;
|
|
661
|
+
}>`
|
|
662
|
+
SELECT commit_seq, "table", row_id, op, row_json, row_version, scopes
|
|
663
|
+
FROM sync_changes
|
|
664
|
+
WHERE commit_seq = ANY(${args.commitSeqs}::bigint[])
|
|
665
|
+
AND partition_id = ${partitionId}
|
|
666
|
+
AND "table" = ${args.table}
|
|
667
|
+
AND (${scopeFilter})
|
|
668
|
+
ORDER BY commit_seq ASC, change_id ASC
|
|
669
|
+
`.execute(db);
|
|
670
|
+
|
|
671
|
+
return res.rows.map((row) => ({
|
|
672
|
+
commit_seq: coerceNumber(row.commit_seq) ?? 0,
|
|
673
|
+
table: row.table,
|
|
674
|
+
row_id: row.row_id,
|
|
675
|
+
op: row.op as SyncOp,
|
|
676
|
+
row_json: row.row_json ?? null,
|
|
677
|
+
row_version: coerceNumber(row.row_version),
|
|
678
|
+
scopes: parseScopes(row.scopes),
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async readAuditRowHistory<DB extends SyncCoreDb>(
|
|
683
|
+
db: DbExecutor<DB>,
|
|
684
|
+
args: AuditRowHistoryArgs
|
|
685
|
+
): Promise<AuditRowHistoryRow[]> {
|
|
686
|
+
const partitionId = args.partitionId ?? 'default';
|
|
687
|
+
const scopeFilter = buildJsonbScopeContainmentFilter(
|
|
688
|
+
'c.scopes',
|
|
689
|
+
args.scopes
|
|
690
|
+
);
|
|
691
|
+
const limit = Math.max(1, Math.min(200, args.limit));
|
|
692
|
+
const whereClauses = [
|
|
693
|
+
sql`c.partition_id = ${partitionId}`,
|
|
694
|
+
sql`cm.partition_id = ${partitionId}`,
|
|
695
|
+
sql`c."table" = ${args.table}`,
|
|
696
|
+
sql`c.row_id = ${args.rowId}`,
|
|
697
|
+
sql`(${scopeFilter})`,
|
|
698
|
+
];
|
|
699
|
+
|
|
700
|
+
if (args.beforeCommitSeq !== undefined) {
|
|
701
|
+
whereClauses.push(sql`c.commit_seq < ${args.beforeCommitSeq}`);
|
|
702
|
+
}
|
|
703
|
+
if (args.afterCommitSeq !== undefined) {
|
|
704
|
+
whereClauses.push(sql`c.commit_seq >= ${args.afterCommitSeq}`);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const res = await sql<{
|
|
708
|
+
commit_seq: unknown;
|
|
709
|
+
actor_id: string;
|
|
710
|
+
client_id: string;
|
|
711
|
+
client_commit_id: string;
|
|
712
|
+
created_at: unknown;
|
|
713
|
+
change_id: unknown;
|
|
714
|
+
table: string;
|
|
715
|
+
row_id: string;
|
|
716
|
+
op: string;
|
|
717
|
+
row_json: unknown | null;
|
|
718
|
+
row_version: unknown | null;
|
|
719
|
+
scopes: unknown;
|
|
720
|
+
}>`
|
|
721
|
+
SELECT
|
|
722
|
+
c.commit_seq,
|
|
723
|
+
cm.actor_id,
|
|
724
|
+
cm.client_id,
|
|
725
|
+
cm.client_commit_id,
|
|
726
|
+
cm.created_at,
|
|
727
|
+
c.change_id,
|
|
728
|
+
c."table",
|
|
729
|
+
c.row_id,
|
|
730
|
+
c.op,
|
|
731
|
+
c.row_json,
|
|
732
|
+
c.row_version,
|
|
733
|
+
c.scopes
|
|
734
|
+
FROM sync_changes c
|
|
735
|
+
JOIN sync_commits cm
|
|
736
|
+
ON cm.partition_id = c.partition_id
|
|
737
|
+
AND cm.commit_seq = c.commit_seq
|
|
738
|
+
WHERE ${sql.join(whereClauses, sql` AND `)}
|
|
739
|
+
ORDER BY c.commit_seq DESC, c.change_id DESC
|
|
740
|
+
LIMIT ${limit + 1}
|
|
741
|
+
`.execute(db);
|
|
742
|
+
|
|
743
|
+
return res.rows.map((row) => ({
|
|
744
|
+
commit_seq: coerceNumber(row.commit_seq) ?? 0,
|
|
745
|
+
actor_id: row.actor_id,
|
|
746
|
+
client_id: row.client_id,
|
|
747
|
+
client_commit_id: row.client_commit_id,
|
|
748
|
+
created_at: coerceIsoString(row.created_at),
|
|
749
|
+
change_id: coerceNumber(row.change_id) ?? 0,
|
|
750
|
+
table: row.table,
|
|
751
|
+
row_id: row.row_id,
|
|
752
|
+
op: row.op as SyncOp,
|
|
753
|
+
row_json: row.row_json ?? null,
|
|
754
|
+
row_version: coerceNumber(row.row_version),
|
|
755
|
+
scopes: parseScopes(row.scopes),
|
|
756
|
+
}));
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
protected override async readIncrementalPullRowsBatch<DB extends SyncCoreDb>(
|
|
760
|
+
db: DbExecutor<DB>,
|
|
761
|
+
args: Omit<IncrementalPullRowsArgs, 'batchSize'>
|
|
762
|
+
): Promise<IncrementalPullRow[]> {
|
|
763
|
+
const partitionId = args.partitionId ?? 'default';
|
|
764
|
+
const limitCommits = Math.max(1, Math.min(1000, args.limitCommits));
|
|
765
|
+
|
|
766
|
+
const scopeFilter = buildJsonbScopeContainmentFilter(
|
|
767
|
+
'c.scopes',
|
|
768
|
+
args.scopes
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
const scopeIndexedCommitSeqs = await this.readScopeIndexedCommitSeqsForPull(
|
|
772
|
+
db,
|
|
773
|
+
{
|
|
774
|
+
partitionId,
|
|
775
|
+
table: args.table,
|
|
776
|
+
scopes: args.scopes,
|
|
777
|
+
cursor: args.cursor,
|
|
778
|
+
limitCommits,
|
|
779
|
+
}
|
|
780
|
+
);
|
|
781
|
+
if (scopeIndexedCommitSeqs.length > 0) {
|
|
782
|
+
const commitSeqsFilter = this.buildNumberListFilter(
|
|
783
|
+
scopeIndexedCommitSeqs
|
|
784
|
+
);
|
|
785
|
+
const scannedMaxCommitSeq =
|
|
786
|
+
scopeIndexedCommitSeqs[scopeIndexedCommitSeqs.length - 1] ??
|
|
787
|
+
args.cursor;
|
|
788
|
+
const res = await sql<{
|
|
789
|
+
commit_seq: unknown;
|
|
790
|
+
actor_id: string;
|
|
791
|
+
created_at: unknown;
|
|
792
|
+
commit_digest: string | null;
|
|
793
|
+
commit_chain_root: string | null;
|
|
794
|
+
change_id: unknown;
|
|
795
|
+
table: string;
|
|
796
|
+
row_id: string;
|
|
797
|
+
op: string;
|
|
798
|
+
row_json: unknown | null;
|
|
799
|
+
row_version: unknown | null;
|
|
800
|
+
scopes: unknown;
|
|
801
|
+
}>`
|
|
802
|
+
SELECT
|
|
803
|
+
cm.commit_seq,
|
|
804
|
+
cm.actor_id,
|
|
805
|
+
cm.created_at,
|
|
806
|
+
cm.commit_digest,
|
|
807
|
+
cm.commit_chain_root,
|
|
808
|
+
c.change_id,
|
|
809
|
+
c."table",
|
|
810
|
+
c.row_id,
|
|
811
|
+
c.op,
|
|
812
|
+
c.row_json,
|
|
813
|
+
c.row_version,
|
|
814
|
+
c.scopes
|
|
815
|
+
FROM sync_commits cm
|
|
816
|
+
JOIN sync_changes c ON c.commit_seq = cm.commit_seq
|
|
817
|
+
WHERE cm.commit_seq ${commitSeqsFilter}
|
|
818
|
+
AND cm.partition_id = ${partitionId}
|
|
819
|
+
AND c.partition_id = ${partitionId}
|
|
820
|
+
AND c."table" = ${args.table}
|
|
821
|
+
AND (${scopeFilter})
|
|
822
|
+
ORDER BY cm.commit_seq ASC, c.change_id ASC
|
|
823
|
+
`.execute(db);
|
|
824
|
+
|
|
825
|
+
return res.rows.map((row) => ({
|
|
826
|
+
commit_seq: coerceNumber(row.commit_seq) ?? 0,
|
|
827
|
+
scanned_max_commit_seq: scannedMaxCommitSeq,
|
|
828
|
+
actor_id: row.actor_id,
|
|
829
|
+
created_at: coerceIsoString(row.created_at),
|
|
830
|
+
commit_digest: row.commit_digest ?? null,
|
|
831
|
+
commit_chain_root: row.commit_chain_root ?? null,
|
|
832
|
+
change_id: coerceNumber(row.change_id) ?? 0,
|
|
833
|
+
table: row.table,
|
|
834
|
+
row_id: row.row_id,
|
|
835
|
+
op: row.op as SyncOp,
|
|
836
|
+
row_json: row.row_json ?? null,
|
|
837
|
+
row_version: coerceNumber(row.row_version),
|
|
838
|
+
scopes: parseScopes(row.scopes),
|
|
839
|
+
}));
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const res = await sql<{
|
|
843
|
+
commit_seq: unknown;
|
|
844
|
+
scanned_max_commit_seq: unknown;
|
|
845
|
+
actor_id: string;
|
|
846
|
+
created_at: unknown;
|
|
847
|
+
commit_digest: string | null;
|
|
848
|
+
commit_chain_root: string | null;
|
|
849
|
+
change_id: unknown;
|
|
850
|
+
table: string;
|
|
851
|
+
row_id: string;
|
|
852
|
+
op: string;
|
|
853
|
+
row_json: unknown | null;
|
|
854
|
+
row_version: unknown | null;
|
|
855
|
+
scopes: unknown;
|
|
856
|
+
}>`
|
|
857
|
+
WITH commit_window AS (
|
|
858
|
+
SELECT tc.commit_seq
|
|
859
|
+
FROM sync_table_commits tc
|
|
860
|
+
JOIN sync_commits cm ON cm.commit_seq = tc.commit_seq
|
|
861
|
+
WHERE tc.partition_id = ${partitionId}
|
|
862
|
+
AND tc."table" = ${args.table}
|
|
863
|
+
AND cm.partition_id = ${partitionId}
|
|
864
|
+
AND tc.commit_seq > ${args.cursor}
|
|
865
|
+
ORDER BY tc.commit_seq ASC
|
|
866
|
+
LIMIT ${limitCommits}
|
|
867
|
+
),
|
|
868
|
+
commit_seqs AS (
|
|
869
|
+
SELECT cw.commit_seq
|
|
870
|
+
FROM commit_window cw
|
|
871
|
+
WHERE EXISTS (
|
|
872
|
+
SELECT 1
|
|
873
|
+
FROM sync_changes c
|
|
874
|
+
WHERE c.commit_seq = cw.commit_seq
|
|
875
|
+
AND c.partition_id = ${partitionId}
|
|
876
|
+
AND c."table" = ${args.table}
|
|
877
|
+
AND (${scopeFilter})
|
|
878
|
+
)
|
|
879
|
+
ORDER BY cw.commit_seq ASC
|
|
880
|
+
)
|
|
881
|
+
SELECT
|
|
882
|
+
cm.commit_seq,
|
|
883
|
+
(SELECT max(commit_seq) FROM commit_window) AS scanned_max_commit_seq,
|
|
884
|
+
cm.actor_id,
|
|
885
|
+
cm.created_at,
|
|
886
|
+
cm.commit_digest,
|
|
887
|
+
cm.commit_chain_root,
|
|
888
|
+
c.change_id,
|
|
889
|
+
c."table",
|
|
890
|
+
c.row_id,
|
|
891
|
+
c.op,
|
|
892
|
+
c.row_json,
|
|
893
|
+
c.row_version,
|
|
894
|
+
c.scopes
|
|
895
|
+
FROM commit_seqs cs
|
|
896
|
+
JOIN sync_commits cm ON cm.commit_seq = cs.commit_seq
|
|
897
|
+
JOIN sync_changes c ON c.commit_seq = cs.commit_seq
|
|
898
|
+
WHERE cm.partition_id = ${partitionId}
|
|
899
|
+
AND c.partition_id = ${partitionId}
|
|
900
|
+
AND c."table" = ${args.table}
|
|
901
|
+
AND (${scopeFilter})
|
|
902
|
+
ORDER BY cm.commit_seq ASC, c.change_id ASC
|
|
903
|
+
`.execute(db);
|
|
904
|
+
|
|
905
|
+
return res.rows.map((row) => ({
|
|
906
|
+
commit_seq: coerceNumber(row.commit_seq) ?? 0,
|
|
907
|
+
scanned_max_commit_seq: coerceNumber(row.scanned_max_commit_seq),
|
|
908
|
+
actor_id: row.actor_id,
|
|
909
|
+
created_at: coerceIsoString(row.created_at),
|
|
910
|
+
commit_digest: row.commit_digest ?? null,
|
|
911
|
+
commit_chain_root: row.commit_chain_root ?? null,
|
|
912
|
+
change_id: coerceNumber(row.change_id) ?? 0,
|
|
913
|
+
table: row.table,
|
|
914
|
+
row_id: row.row_id,
|
|
915
|
+
op: row.op as SyncOp,
|
|
916
|
+
row_json: row.row_json ?? null,
|
|
917
|
+
row_version: coerceNumber(row.row_version),
|
|
918
|
+
scopes: parseScopes(row.scopes),
|
|
919
|
+
}));
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
async compactChanges<DB extends SyncCoreDb>(
|
|
923
|
+
db: DbExecutor<DB>,
|
|
924
|
+
args: { fullHistoryHours: number }
|
|
925
|
+
): Promise<number> {
|
|
926
|
+
const cutoffIso = new Date(
|
|
927
|
+
Date.now() - args.fullHistoryHours * 60 * 60 * 1000
|
|
928
|
+
).toISOString();
|
|
929
|
+
|
|
930
|
+
const res = await sql`
|
|
931
|
+
WITH ranked AS (
|
|
932
|
+
SELECT
|
|
933
|
+
c.change_id,
|
|
934
|
+
row_number() OVER (
|
|
935
|
+
PARTITION BY c.partition_id, c."table", c.row_id, c.scopes
|
|
936
|
+
ORDER BY c.commit_seq DESC, c.change_id DESC
|
|
937
|
+
) AS rn
|
|
938
|
+
FROM sync_changes c
|
|
939
|
+
JOIN sync_commits cm ON cm.commit_seq = c.commit_seq
|
|
940
|
+
WHERE cm.created_at < ${cutoffIso}
|
|
941
|
+
)
|
|
942
|
+
DELETE FROM sync_changes
|
|
943
|
+
WHERE change_id IN (SELECT change_id FROM ranked WHERE rn > 1)
|
|
944
|
+
`.execute(db);
|
|
945
|
+
|
|
946
|
+
const deletedChanges = Number(res.numAffectedRows ?? 0);
|
|
947
|
+
|
|
948
|
+
// Remove routing index entries that no longer have any remaining changes
|
|
949
|
+
await sql`
|
|
950
|
+
DELETE FROM sync_scope_commits sc
|
|
951
|
+
USING sync_commits cm
|
|
952
|
+
WHERE cm.commit_seq = sc.commit_seq
|
|
953
|
+
AND cm.partition_id = sc.partition_id
|
|
954
|
+
AND cm.created_at < ${cutoffIso}
|
|
955
|
+
AND NOT EXISTS (
|
|
956
|
+
SELECT 1
|
|
957
|
+
FROM sync_changes c
|
|
958
|
+
WHERE c.commit_seq = sc.commit_seq
|
|
959
|
+
AND c.partition_id = sc.partition_id
|
|
960
|
+
AND c."table" = sc."table"
|
|
961
|
+
)
|
|
962
|
+
`.execute(db);
|
|
963
|
+
|
|
964
|
+
await sql`
|
|
965
|
+
DELETE FROM sync_table_commits tc
|
|
966
|
+
USING sync_commits cm
|
|
967
|
+
WHERE cm.commit_seq = tc.commit_seq
|
|
968
|
+
AND cm.partition_id = tc.partition_id
|
|
969
|
+
AND cm.created_at < ${cutoffIso}
|
|
970
|
+
AND NOT EXISTS (
|
|
971
|
+
SELECT 1
|
|
972
|
+
FROM sync_changes c
|
|
973
|
+
WHERE c.commit_seq = tc.commit_seq
|
|
974
|
+
AND c.partition_id = tc.partition_id
|
|
975
|
+
AND c."table" = tc."table"
|
|
976
|
+
)
|
|
977
|
+
`.execute(db);
|
|
978
|
+
|
|
979
|
+
return deletedChanges;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// ===========================================================================
|
|
983
|
+
// Scope Conversion Helpers
|
|
984
|
+
// ===========================================================================
|
|
985
|
+
|
|
986
|
+
scopesToDb(scopes: StoredScopes): StoredScopes {
|
|
987
|
+
return scopes;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
dbToArray(value: unknown): string[] {
|
|
991
|
+
if (Array.isArray(value)) {
|
|
992
|
+
return value.filter((k: unknown): k is string => typeof k === 'string');
|
|
993
|
+
}
|
|
994
|
+
return [];
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
arrayToDb(values: string[]): string[] {
|
|
998
|
+
return values.filter((v) => v.length > 0);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// ===========================================================================
|
|
1002
|
+
// Console Schema (Request Events)
|
|
1003
|
+
// ===========================================================================
|
|
1004
|
+
|
|
1005
|
+
async ensureConsoleSchema<DB extends SyncCoreDb>(
|
|
1006
|
+
db: Kysely<DB>
|
|
1007
|
+
): Promise<void> {
|
|
1008
|
+
await sql`
|
|
1009
|
+
CREATE TABLE IF NOT EXISTS sync_request_events (
|
|
1010
|
+
event_id BIGSERIAL PRIMARY KEY,
|
|
1011
|
+
partition_id TEXT NOT NULL DEFAULT 'default',
|
|
1012
|
+
request_id TEXT,
|
|
1013
|
+
trace_id TEXT,
|
|
1014
|
+
span_id TEXT,
|
|
1015
|
+
event_type TEXT NOT NULL,
|
|
1016
|
+
actor_id TEXT NOT NULL,
|
|
1017
|
+
client_id TEXT NOT NULL,
|
|
1018
|
+
transport_path TEXT NOT NULL DEFAULT 'direct',
|
|
1019
|
+
sync_path TEXT NOT NULL DEFAULT 'http-combined',
|
|
1020
|
+
status_code INTEGER NOT NULL,
|
|
1021
|
+
outcome TEXT NOT NULL,
|
|
1022
|
+
response_status TEXT NOT NULL DEFAULT 'unknown',
|
|
1023
|
+
error_code TEXT,
|
|
1024
|
+
duration_ms INTEGER NOT NULL,
|
|
1025
|
+
commit_seq BIGINT,
|
|
1026
|
+
operation_count INTEGER,
|
|
1027
|
+
row_count INTEGER,
|
|
1028
|
+
subscription_count INTEGER,
|
|
1029
|
+
scopes_summary JSONB,
|
|
1030
|
+
response_summary JSONB,
|
|
1031
|
+
tables TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
|
1032
|
+
error_message TEXT,
|
|
1033
|
+
payload_ref TEXT,
|
|
1034
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1035
|
+
)
|
|
1036
|
+
`.execute(db);
|
|
1037
|
+
await sql`
|
|
1038
|
+
ALTER TABLE sync_request_events
|
|
1039
|
+
ADD COLUMN IF NOT EXISTS partition_id TEXT NOT NULL DEFAULT 'default'
|
|
1040
|
+
`.execute(db);
|
|
1041
|
+
await sql`
|
|
1042
|
+
ALTER TABLE sync_request_events
|
|
1043
|
+
ADD COLUMN IF NOT EXISTS request_id TEXT
|
|
1044
|
+
`.execute(db);
|
|
1045
|
+
await sql`
|
|
1046
|
+
ALTER TABLE sync_request_events
|
|
1047
|
+
ADD COLUMN IF NOT EXISTS trace_id TEXT
|
|
1048
|
+
`.execute(db);
|
|
1049
|
+
await sql`
|
|
1050
|
+
ALTER TABLE sync_request_events
|
|
1051
|
+
ADD COLUMN IF NOT EXISTS span_id TEXT
|
|
1052
|
+
`.execute(db);
|
|
1053
|
+
await sql`
|
|
1054
|
+
ALTER TABLE sync_request_events
|
|
1055
|
+
ADD COLUMN IF NOT EXISTS transport_path TEXT NOT NULL DEFAULT 'direct'
|
|
1056
|
+
`.execute(db);
|
|
1057
|
+
await sql`
|
|
1058
|
+
ALTER TABLE sync_request_events
|
|
1059
|
+
ADD COLUMN IF NOT EXISTS sync_path TEXT NOT NULL DEFAULT 'http-combined'
|
|
1060
|
+
`.execute(db);
|
|
1061
|
+
await sql`
|
|
1062
|
+
ALTER TABLE sync_request_events
|
|
1063
|
+
ADD COLUMN IF NOT EXISTS response_status TEXT NOT NULL DEFAULT 'unknown'
|
|
1064
|
+
`.execute(db);
|
|
1065
|
+
await sql`
|
|
1066
|
+
ALTER TABLE sync_request_events
|
|
1067
|
+
ADD COLUMN IF NOT EXISTS error_code TEXT
|
|
1068
|
+
`.execute(db);
|
|
1069
|
+
await sql`
|
|
1070
|
+
ALTER TABLE sync_request_events
|
|
1071
|
+
ADD COLUMN IF NOT EXISTS subscription_count INTEGER
|
|
1072
|
+
`.execute(db);
|
|
1073
|
+
await sql`
|
|
1074
|
+
ALTER TABLE sync_request_events
|
|
1075
|
+
ADD COLUMN IF NOT EXISTS scopes_summary JSONB
|
|
1076
|
+
`.execute(db);
|
|
1077
|
+
await sql`
|
|
1078
|
+
ALTER TABLE sync_request_events
|
|
1079
|
+
ADD COLUMN IF NOT EXISTS response_summary JSONB
|
|
1080
|
+
`.execute(db);
|
|
1081
|
+
await sql`
|
|
1082
|
+
ALTER TABLE sync_request_events
|
|
1083
|
+
ADD COLUMN IF NOT EXISTS payload_ref TEXT
|
|
1084
|
+
`.execute(db);
|
|
1085
|
+
|
|
1086
|
+
await this.ensureIndex(
|
|
1087
|
+
db,
|
|
1088
|
+
'idx_sync_request_events_created_at',
|
|
1089
|
+
'CREATE INDEX idx_sync_request_events_created_at ON sync_request_events(created_at DESC)'
|
|
1090
|
+
);
|
|
1091
|
+
await this.ensureIndex(
|
|
1092
|
+
db,
|
|
1093
|
+
'idx_sync_request_events_event_type',
|
|
1094
|
+
'CREATE INDEX idx_sync_request_events_event_type ON sync_request_events(event_type)'
|
|
1095
|
+
);
|
|
1096
|
+
await this.ensureIndex(
|
|
1097
|
+
db,
|
|
1098
|
+
'idx_sync_request_events_client_id',
|
|
1099
|
+
'CREATE INDEX idx_sync_request_events_client_id ON sync_request_events(client_id)'
|
|
1100
|
+
);
|
|
1101
|
+
await this.ensureIndex(
|
|
1102
|
+
db,
|
|
1103
|
+
'idx_sync_request_events_request_id',
|
|
1104
|
+
'CREATE INDEX idx_sync_request_events_request_id ON sync_request_events(request_id)'
|
|
1105
|
+
);
|
|
1106
|
+
await this.ensureIndex(
|
|
1107
|
+
db,
|
|
1108
|
+
'idx_sync_request_events_trace_id',
|
|
1109
|
+
'CREATE INDEX idx_sync_request_events_trace_id ON sync_request_events(trace_id)'
|
|
1110
|
+
);
|
|
1111
|
+
await this.ensureIndex(
|
|
1112
|
+
db,
|
|
1113
|
+
'idx_sync_request_events_partition_created_at',
|
|
1114
|
+
'CREATE INDEX idx_sync_request_events_partition_created_at ON sync_request_events(partition_id, created_at DESC)'
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
await sql`
|
|
1118
|
+
CREATE TABLE IF NOT EXISTS sync_request_payloads (
|
|
1119
|
+
payload_ref TEXT PRIMARY KEY,
|
|
1120
|
+
partition_id TEXT NOT NULL DEFAULT 'default',
|
|
1121
|
+
request_payload JSONB NOT NULL,
|
|
1122
|
+
response_payload JSONB,
|
|
1123
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1124
|
+
)
|
|
1125
|
+
`.execute(db);
|
|
1126
|
+
await this.ensureIndex(
|
|
1127
|
+
db,
|
|
1128
|
+
'idx_sync_request_payloads_created_at',
|
|
1129
|
+
'CREATE INDEX idx_sync_request_payloads_created_at ON sync_request_payloads(created_at DESC)'
|
|
1130
|
+
);
|
|
1131
|
+
await this.ensureIndex(
|
|
1132
|
+
db,
|
|
1133
|
+
'idx_sync_request_payloads_partition_created_at',
|
|
1134
|
+
'CREATE INDEX idx_sync_request_payloads_partition_created_at ON sync_request_payloads(partition_id, created_at DESC)'
|
|
1135
|
+
);
|
|
1136
|
+
|
|
1137
|
+
await sql`
|
|
1138
|
+
CREATE TABLE IF NOT EXISTS sync_operation_events (
|
|
1139
|
+
operation_id BIGSERIAL PRIMARY KEY,
|
|
1140
|
+
operation_type TEXT NOT NULL,
|
|
1141
|
+
console_user_id TEXT,
|
|
1142
|
+
partition_id TEXT,
|
|
1143
|
+
target_client_id TEXT,
|
|
1144
|
+
request_payload JSONB,
|
|
1145
|
+
result_payload JSONB,
|
|
1146
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1147
|
+
)
|
|
1148
|
+
`.execute(db);
|
|
1149
|
+
await this.ensureIndex(
|
|
1150
|
+
db,
|
|
1151
|
+
'idx_sync_operation_events_created_at',
|
|
1152
|
+
'CREATE INDEX idx_sync_operation_events_created_at ON sync_operation_events(created_at DESC)'
|
|
1153
|
+
);
|
|
1154
|
+
await this.ensureIndex(
|
|
1155
|
+
db,
|
|
1156
|
+
'idx_sync_operation_events_type',
|
|
1157
|
+
'CREATE INDEX idx_sync_operation_events_type ON sync_operation_events(operation_type)'
|
|
1158
|
+
);
|
|
1159
|
+
|
|
1160
|
+
await sql`
|
|
1161
|
+
CREATE TABLE IF NOT EXISTS sync_realtime_events (
|
|
1162
|
+
event_id BIGSERIAL PRIMARY KEY,
|
|
1163
|
+
partition_id TEXT NOT NULL DEFAULT 'default',
|
|
1164
|
+
actor_id TEXT NOT NULL,
|
|
1165
|
+
client_id TEXT NOT NULL,
|
|
1166
|
+
transport_path TEXT NOT NULL DEFAULT 'direct',
|
|
1167
|
+
event_type TEXT NOT NULL,
|
|
1168
|
+
reason TEXT,
|
|
1169
|
+
cursor BIGINT,
|
|
1170
|
+
latest_cursor BIGINT,
|
|
1171
|
+
commit_seq BIGINT,
|
|
1172
|
+
scope_count INTEGER,
|
|
1173
|
+
skipped_count INTEGER,
|
|
1174
|
+
sync_pack_encoding TEXT,
|
|
1175
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1176
|
+
)
|
|
1177
|
+
`.execute(db);
|
|
1178
|
+
await this.ensureIndex(
|
|
1179
|
+
db,
|
|
1180
|
+
'idx_sync_realtime_events_created_at',
|
|
1181
|
+
'CREATE INDEX idx_sync_realtime_events_created_at ON sync_realtime_events(created_at DESC)'
|
|
1182
|
+
);
|
|
1183
|
+
await this.ensureIndex(
|
|
1184
|
+
db,
|
|
1185
|
+
'idx_sync_realtime_events_partition_client_created_at',
|
|
1186
|
+
'CREATE INDEX idx_sync_realtime_events_partition_client_created_at ON sync_realtime_events(partition_id, client_id, created_at DESC)'
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
await sql`
|
|
1190
|
+
CREATE TABLE IF NOT EXISTS sync_client_diagnostic_snapshots (
|
|
1191
|
+
snapshot_id BIGSERIAL PRIMARY KEY,
|
|
1192
|
+
partition_id TEXT NOT NULL DEFAULT 'default',
|
|
1193
|
+
client_id TEXT NOT NULL,
|
|
1194
|
+
actor_id TEXT,
|
|
1195
|
+
runtime_kind TEXT,
|
|
1196
|
+
runtime_version TEXT,
|
|
1197
|
+
schema_version INTEGER,
|
|
1198
|
+
reported_at TIMESTAMPTZ NOT NULL,
|
|
1199
|
+
received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1200
|
+
lifecycle_phase TEXT,
|
|
1201
|
+
connection_state TEXT,
|
|
1202
|
+
freshness_state TEXT NOT NULL,
|
|
1203
|
+
health_max_severity TEXT,
|
|
1204
|
+
diagnostic_codes_summary JSONB,
|
|
1205
|
+
queue_summary JSONB,
|
|
1206
|
+
timing_summary JSONB,
|
|
1207
|
+
redaction_summary JSONB,
|
|
1208
|
+
snapshot_json JSONB NOT NULL
|
|
1209
|
+
)
|
|
1210
|
+
`.execute(db);
|
|
1211
|
+
await this.ensureIndex(
|
|
1212
|
+
db,
|
|
1213
|
+
'idx_sync_client_diagnostic_snapshots_received_at',
|
|
1214
|
+
'CREATE INDEX idx_sync_client_diagnostic_snapshots_received_at ON sync_client_diagnostic_snapshots(received_at DESC)'
|
|
1215
|
+
);
|
|
1216
|
+
await this.ensureIndex(
|
|
1217
|
+
db,
|
|
1218
|
+
'idx_sync_client_diagnostic_snapshots_latest',
|
|
1219
|
+
'CREATE INDEX idx_sync_client_diagnostic_snapshots_latest ON sync_client_diagnostic_snapshots(partition_id, client_id, received_at DESC)'
|
|
1220
|
+
);
|
|
1221
|
+
await this.ensureIndex(
|
|
1222
|
+
db,
|
|
1223
|
+
'idx_sync_client_diagnostic_snapshots_health',
|
|
1224
|
+
'CREATE INDEX idx_sync_client_diagnostic_snapshots_health ON sync_client_diagnostic_snapshots(partition_id, health_max_severity, received_at DESC)'
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
// API Keys table
|
|
1228
|
+
await sql`
|
|
1229
|
+
CREATE TABLE IF NOT EXISTS sync_api_keys (
|
|
1230
|
+
key_id TEXT PRIMARY KEY,
|
|
1231
|
+
key_hash TEXT NOT NULL,
|
|
1232
|
+
key_prefix TEXT NOT NULL,
|
|
1233
|
+
name TEXT NOT NULL,
|
|
1234
|
+
key_type TEXT NOT NULL,
|
|
1235
|
+
scope_keys TEXT[] DEFAULT ARRAY[]::TEXT[],
|
|
1236
|
+
actor_id TEXT,
|
|
1237
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1238
|
+
expires_at TIMESTAMPTZ,
|
|
1239
|
+
last_used_at TIMESTAMPTZ,
|
|
1240
|
+
revoked_at TIMESTAMPTZ
|
|
1241
|
+
)
|
|
1242
|
+
`.execute(db);
|
|
1243
|
+
|
|
1244
|
+
await this.ensureIndex(
|
|
1245
|
+
db,
|
|
1246
|
+
'idx_sync_api_keys_key_hash',
|
|
1247
|
+
'CREATE INDEX idx_sync_api_keys_key_hash ON sync_api_keys(key_hash)'
|
|
1248
|
+
);
|
|
1249
|
+
await this.ensureIndex(
|
|
1250
|
+
db,
|
|
1251
|
+
'idx_sync_api_keys_key_type',
|
|
1252
|
+
'CREATE INDEX idx_sync_api_keys_key_type ON sync_api_keys(key_type)'
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// ===========================================================================
|
|
1257
|
+
// Private Helpers
|
|
1258
|
+
// ===========================================================================
|
|
1259
|
+
|
|
1260
|
+
private async ensureIndex<DB extends SyncCoreDb>(
|
|
1261
|
+
db: Kysely<DB>,
|
|
1262
|
+
indexName: string,
|
|
1263
|
+
createSql: string
|
|
1264
|
+
): Promise<void> {
|
|
1265
|
+
const exists = await sql<{ ok: 1 }>`
|
|
1266
|
+
SELECT 1 as ok
|
|
1267
|
+
FROM pg_indexes
|
|
1268
|
+
WHERE schemaname = 'public'
|
|
1269
|
+
AND indexname = ${indexName}
|
|
1270
|
+
LIMIT 1
|
|
1271
|
+
`.execute(db);
|
|
1272
|
+
|
|
1273
|
+
if (exists.rows.length > 0) return;
|
|
1274
|
+
await sql.raw(createSql).execute(db);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
export function createPostgresServerDialect(): PostgresServerSyncDialect {
|
|
1279
|
+
return new PostgresServerSyncDialect();
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* Explicit server-side dialect alias for Neon-backed Postgres runtimes.
|
|
1284
|
+
*
|
|
1285
|
+
* Neon uses the same SQL family and sync schema behavior as Postgres, but this
|
|
1286
|
+
* helper makes the runtime/provider intent visible in app code and host
|
|
1287
|
+
* integrations.
|
|
1288
|
+
*/
|
|
1289
|
+
export function createNeonServerDialect(): PostgresServerSyncDialect {
|
|
1290
|
+
return createPostgresServerDialect();
|
|
1291
|
+
}
|