@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
package/src/pull.ts
CHANGED
|
@@ -1,38 +1,66 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type BinarySnapshotColumn,
|
|
3
|
+
type BinarySnapshotColumnType,
|
|
4
|
+
type BinarySnapshotRowsEncoder,
|
|
5
|
+
bytesToReadableStream,
|
|
2
6
|
captureSyncException,
|
|
7
|
+
concatByteChunks,
|
|
3
8
|
countSyncMetric,
|
|
9
|
+
createSnapshotManifest,
|
|
4
10
|
distributionSyncMetric,
|
|
5
|
-
|
|
6
|
-
encodeSnapshotRows,
|
|
11
|
+
encodeBinarySnapshotTable,
|
|
7
12
|
gzipBytes,
|
|
8
|
-
gzipBytesToStream,
|
|
9
13
|
randomId,
|
|
10
14
|
type ScopeValues,
|
|
15
|
+
SYNC_SCOPED_SNAPSHOT_ARTIFACT_KIND_SQLITE_V1,
|
|
11
16
|
SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
12
17
|
SYNC_SNAPSHOT_CHUNK_ENCODING,
|
|
13
18
|
type SyncBootstrapState,
|
|
14
19
|
type SyncChange,
|
|
15
20
|
type SyncCommit,
|
|
21
|
+
type SyncCrdtStateVectorHint,
|
|
16
22
|
type SyncPullRequest,
|
|
17
23
|
type SyncPullResponse,
|
|
18
24
|
type SyncPullSubscriptionResponse,
|
|
25
|
+
type SyncScopedSnapshotArtifactKind,
|
|
19
26
|
type SyncSnapshot,
|
|
27
|
+
type SyncSnapshotArtifactCompression,
|
|
28
|
+
type SyncSnapshotArtifactsRequest,
|
|
29
|
+
type SyncSnapshotChunkRef,
|
|
20
30
|
sha256Hex,
|
|
31
|
+
snapshotScopeDigestFromCacheKey,
|
|
21
32
|
startSyncSpan,
|
|
22
33
|
} from '@syncular/core';
|
|
23
34
|
import type { Kysely } from 'kysely';
|
|
24
|
-
import
|
|
35
|
+
import {
|
|
36
|
+
createWireSubscriptionIntegrity,
|
|
37
|
+
SYNCULAR_COMMIT_GENESIS_ROOT,
|
|
38
|
+
} from './commit-integrity';
|
|
39
|
+
import type {
|
|
40
|
+
DbExecutor,
|
|
41
|
+
IncrementalPullRow,
|
|
42
|
+
ServerSyncDialect,
|
|
43
|
+
} from './dialect/types';
|
|
25
44
|
import {
|
|
26
45
|
getServerBootstrapOrderFor,
|
|
27
|
-
getServerHandlerOrThrow,
|
|
28
46
|
type ServerHandlerCollection,
|
|
29
47
|
} from './handlers/collection';
|
|
30
48
|
import type { ServerTableHandler, SyncServerAuth } from './handlers/types';
|
|
31
49
|
import { EXTERNAL_CLIENT_ID } from './notify';
|
|
50
|
+
import {
|
|
51
|
+
type SyncServerPullPlugin,
|
|
52
|
+
sortServerPullPlugins,
|
|
53
|
+
} from './plugins/types';
|
|
32
54
|
import type { SyncCoreDb } from './schema';
|
|
33
55
|
import {
|
|
56
|
+
createScopedSnapshotArtifactScopeCacheKey,
|
|
57
|
+
readBestScopedSnapshotArtifactRefForPageCapacity,
|
|
58
|
+
} from './snapshot-artifacts';
|
|
59
|
+
import {
|
|
60
|
+
createSnapshotChunkScopeCacheKey,
|
|
34
61
|
insertSnapshotChunk,
|
|
35
62
|
readSnapshotChunkRefByPageKey,
|
|
63
|
+
type SnapshotChunkRefWithContinuation,
|
|
36
64
|
} from './snapshot-chunks';
|
|
37
65
|
import type { SnapshotChunkStorage } from './snapshot-chunks/types';
|
|
38
66
|
import {
|
|
@@ -42,24 +70,294 @@ import {
|
|
|
42
70
|
import { resolveEffectiveScopesForSubscriptions } from './subscriptions/resolve';
|
|
43
71
|
|
|
44
72
|
const defaultScopeCache = createMemoryScopeCache();
|
|
73
|
+
const DEFAULT_MAX_BINARY_SNAPSHOT_BUNDLE_ROWS = 50_000;
|
|
74
|
+
const DEFAULT_SNAPSHOT_CHUNK_GZIP_LEVEL = 1;
|
|
75
|
+
const MAX_PULL_TRANSACTION_RETRIES = 2;
|
|
76
|
+
const PULL_TRANSACTION_RETRY_DELAY_MS = 15;
|
|
77
|
+
|
|
78
|
+
interface PullBootstrapTimings {
|
|
79
|
+
snapshotQueryMs: number;
|
|
80
|
+
binaryEncodeMs: number;
|
|
81
|
+
chunkCacheLookupMs: number;
|
|
82
|
+
artifactCacheLookupMs: number;
|
|
83
|
+
chunkGzipMs: number;
|
|
84
|
+
chunkHashMs: number;
|
|
85
|
+
chunkPersistMs: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface SnapshotChunkEncodeResult {
|
|
89
|
+
body: Uint8Array;
|
|
90
|
+
sha256: string;
|
|
91
|
+
gzipMs: number;
|
|
92
|
+
hashMs: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function toResponseChunkRef(ref: SyncSnapshotChunkRef): SyncSnapshotChunkRef {
|
|
96
|
+
return {
|
|
97
|
+
id: ref.id,
|
|
98
|
+
byteLength: ref.byteLength,
|
|
99
|
+
sha256: ref.sha256,
|
|
100
|
+
encoding: ref.encoding,
|
|
101
|
+
compression: ref.compression,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function createChunkedSnapshotManifest(args: {
|
|
106
|
+
table: string;
|
|
107
|
+
asOfCommitSeq: number;
|
|
108
|
+
scopeKey: string;
|
|
109
|
+
rowCursor: string | null;
|
|
110
|
+
rowLimit: number;
|
|
111
|
+
nextRowCursor: string | null;
|
|
112
|
+
isFirstPage: boolean;
|
|
113
|
+
isLastPage: boolean;
|
|
114
|
+
chunks: readonly SyncSnapshotChunkRef[];
|
|
115
|
+
}): Promise<SyncSnapshot['manifest']> {
|
|
116
|
+
return createSnapshotManifest({
|
|
117
|
+
version: 1,
|
|
118
|
+
table: args.table,
|
|
119
|
+
asOfCommitSeq: args.asOfCommitSeq,
|
|
120
|
+
scopeDigest: snapshotScopeDigestFromCacheKey(args.scopeKey),
|
|
121
|
+
rowCursor: args.rowCursor,
|
|
122
|
+
rowLimit: args.rowLimit,
|
|
123
|
+
nextRowCursor: args.nextRowCursor,
|
|
124
|
+
isFirstPage: args.isFirstPage,
|
|
125
|
+
isLastPage: args.isLastPage,
|
|
126
|
+
chunks: args.chunks.map(toResponseChunkRef),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface SnapshotArtifactSelection {
|
|
131
|
+
artifactKind: SyncScopedSnapshotArtifactKind;
|
|
132
|
+
compression: SyncSnapshotArtifactCompression;
|
|
133
|
+
schemaVersion: string;
|
|
134
|
+
featureSet: readonly string[];
|
|
135
|
+
}
|
|
45
136
|
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
137
|
+
function normalizeFeatureSet(
|
|
138
|
+
features: readonly string[] | undefined
|
|
139
|
+
): string[] {
|
|
140
|
+
return Array.from(new Set(features ?? [])).sort();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function resolveSnapshotArtifactSelection(
|
|
144
|
+
request: SyncSnapshotArtifactsRequest | undefined,
|
|
145
|
+
clientSchemaVersion: number
|
|
146
|
+
): SnapshotArtifactSelection | null {
|
|
147
|
+
if (!request) return null;
|
|
148
|
+
if (
|
|
149
|
+
!request.artifactKinds.includes(
|
|
150
|
+
SYNC_SCOPED_SNAPSHOT_ARTIFACT_KIND_SQLITE_V1
|
|
151
|
+
)
|
|
152
|
+
) {
|
|
153
|
+
return null;
|
|
49
154
|
}
|
|
155
|
+
if (!request.compressions?.includes(SYNC_SNAPSHOT_CHUNK_COMPRESSION)) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
artifactKind: SYNC_SCOPED_SNAPSHOT_ARTIFACT_KIND_SQLITE_V1,
|
|
160
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
161
|
+
schemaVersion: String(clientSchemaVersion),
|
|
162
|
+
featureSet: normalizeFeatureSet(request.featureSet),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function resolveSnapshotBinaryColumns<
|
|
167
|
+
DB extends SyncCoreDb,
|
|
168
|
+
Auth extends SyncServerAuth,
|
|
169
|
+
>(
|
|
170
|
+
handler: ServerTableHandler<DB, Auth>,
|
|
171
|
+
schemaVersion: number
|
|
172
|
+
): readonly BinarySnapshotColumn[] | undefined {
|
|
173
|
+
const versioned = handler.snapshotBinaryColumnsForVersion?.(schemaVersion);
|
|
174
|
+
return versioned === undefined
|
|
175
|
+
? handler.snapshotBinaryColumns
|
|
176
|
+
: (versioned ?? undefined);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function resolveSnapshotBinaryEncoder<
|
|
180
|
+
DB extends SyncCoreDb,
|
|
181
|
+
Auth extends SyncServerAuth,
|
|
182
|
+
>(
|
|
183
|
+
handler: ServerTableHandler<DB, Auth>,
|
|
184
|
+
schemaVersion: number
|
|
185
|
+
): BinarySnapshotRowsEncoder | undefined {
|
|
186
|
+
const versioned = handler.snapshotBinaryEncoderForVersion?.(schemaVersion);
|
|
187
|
+
return versioned === undefined
|
|
188
|
+
? handler.snapshotBinaryEncoder
|
|
189
|
+
: (versioned ?? undefined);
|
|
190
|
+
}
|
|
50
191
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
192
|
+
function createPullBootstrapTimings(): PullBootstrapTimings {
|
|
193
|
+
return {
|
|
194
|
+
snapshotQueryMs: 0,
|
|
195
|
+
binaryEncodeMs: 0,
|
|
196
|
+
chunkCacheLookupMs: 0,
|
|
197
|
+
artifactCacheLookupMs: 0,
|
|
198
|
+
chunkGzipMs: 0,
|
|
199
|
+
chunkHashMs: 0,
|
|
200
|
+
chunkPersistMs: 0,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function resolveBinarySnapshotBundleRowLimit(args: {
|
|
205
|
+
limitSnapshotRows: number;
|
|
206
|
+
pagesRemaining: number;
|
|
207
|
+
}): number {
|
|
208
|
+
const pageSize = Math.max(1, args.limitSnapshotRows);
|
|
209
|
+
const pagesRemaining = Math.max(1, args.pagesRemaining);
|
|
210
|
+
const maxBundlePages = Math.max(
|
|
211
|
+
1,
|
|
212
|
+
Math.ceil(DEFAULT_MAX_BINARY_SNAPSHOT_BUNDLE_ROWS / pageSize)
|
|
213
|
+
);
|
|
214
|
+
return pageSize * Math.min(pagesRemaining, maxBundlePages);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function gzipByteChunks(
|
|
218
|
+
chunks: readonly Uint8Array[],
|
|
219
|
+
gzipLevel: number
|
|
220
|
+
): Promise<Uint8Array> {
|
|
221
|
+
return gzipBytes(concatByteChunks(chunks), {
|
|
222
|
+
level: gzipLevel,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function encodeCompressedSnapshotChunk(
|
|
227
|
+
chunks: readonly Uint8Array[],
|
|
228
|
+
gzipLevel: number
|
|
229
|
+
): Promise<SnapshotChunkEncodeResult> {
|
|
230
|
+
const gzipStartedAt = Date.now();
|
|
231
|
+
const body = await gzipByteChunks(chunks, gzipLevel);
|
|
232
|
+
const gzipMs = Math.max(0, Date.now() - gzipStartedAt);
|
|
233
|
+
const hashStartedAt = Date.now();
|
|
234
|
+
const sha256 = await sha256Hex(body);
|
|
235
|
+
const hashMs = Math.max(0, Date.now() - hashStartedAt);
|
|
236
|
+
return { body, sha256, gzipMs, hashMs };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function encodeCompressedSnapshotChunkToStream(
|
|
240
|
+
chunks: readonly Uint8Array[],
|
|
241
|
+
gzipLevel: number
|
|
242
|
+
): Promise<{
|
|
243
|
+
stream: ReadableStream<Uint8Array>;
|
|
244
|
+
byteLength: number;
|
|
245
|
+
sha256: string;
|
|
246
|
+
gzipMs: number;
|
|
247
|
+
hashMs: number;
|
|
248
|
+
}> {
|
|
249
|
+
const encoded = await encodeCompressedSnapshotChunk(chunks, gzipLevel);
|
|
250
|
+
return {
|
|
251
|
+
stream: bytesToReadableStream(encoded.body),
|
|
252
|
+
byteLength: encoded.body.length,
|
|
253
|
+
sha256: encoded.sha256,
|
|
254
|
+
gzipMs: encoded.gzipMs,
|
|
255
|
+
hashMs: encoded.hashMs,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
interface SnapshotColumnInference {
|
|
260
|
+
name: string;
|
|
261
|
+
type: BinarySnapshotColumnType | null;
|
|
262
|
+
nullable: boolean;
|
|
263
|
+
presentCount: number;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function encodeBinarySnapshotRows(
|
|
267
|
+
table: string,
|
|
268
|
+
rows: readonly unknown[],
|
|
269
|
+
columns?: readonly BinarySnapshotColumn[]
|
|
270
|
+
): Uint8Array {
|
|
271
|
+
const recordRows = rows.map((row) => toSnapshotRecordRow(table, row));
|
|
272
|
+
return encodeBinarySnapshotTable({
|
|
273
|
+
table,
|
|
274
|
+
columns: columns ?? inferBinarySnapshotColumns(recordRows),
|
|
275
|
+
rows: recordRows,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function toSnapshotRecordRow(
|
|
280
|
+
table: string,
|
|
281
|
+
row: unknown
|
|
282
|
+
): Record<string, unknown> {
|
|
283
|
+
if (
|
|
284
|
+
row == null ||
|
|
285
|
+
typeof row !== 'object' ||
|
|
286
|
+
Array.isArray(row) ||
|
|
287
|
+
row instanceof Uint8Array ||
|
|
288
|
+
row instanceof ArrayBuffer
|
|
289
|
+
) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`Cannot encode binary snapshot for table ${table}: snapshot rows must be objects`
|
|
292
|
+
);
|
|
54
293
|
}
|
|
294
|
+
return row as Record<string, unknown>;
|
|
295
|
+
}
|
|
55
296
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
297
|
+
function inferBinarySnapshotColumns(
|
|
298
|
+
rows: readonly Record<string, unknown>[]
|
|
299
|
+
): BinarySnapshotColumn[] {
|
|
300
|
+
const columns: SnapshotColumnInference[] = [];
|
|
301
|
+
const columnsByName = new Map<string, SnapshotColumnInference>();
|
|
302
|
+
|
|
303
|
+
for (const row of rows) {
|
|
304
|
+
for (const name in row) {
|
|
305
|
+
if (!Object.hasOwn(row, name)) continue;
|
|
306
|
+
const value = row[name];
|
|
307
|
+
let column = columnsByName.get(name);
|
|
308
|
+
if (!column) {
|
|
309
|
+
column = { name, type: null, nullable: false, presentCount: 0 };
|
|
310
|
+
columnsByName.set(name, column);
|
|
311
|
+
columns.push(column);
|
|
312
|
+
}
|
|
313
|
+
column.presentCount += 1;
|
|
314
|
+
if (value == null) {
|
|
315
|
+
column.nullable = true;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
column.type = mergeBinarySnapshotColumnTypes(
|
|
319
|
+
column.type,
|
|
320
|
+
inferBinarySnapshotColumnType(value)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
61
323
|
}
|
|
62
|
-
|
|
324
|
+
|
|
325
|
+
return columns.map((column) => ({
|
|
326
|
+
name: column.name,
|
|
327
|
+
type: column.type ?? 'json',
|
|
328
|
+
...(column.nullable || column.presentCount < rows.length
|
|
329
|
+
? { nullable: true }
|
|
330
|
+
: {}),
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function inferBinarySnapshotColumnType(
|
|
335
|
+
value: unknown
|
|
336
|
+
): BinarySnapshotColumnType {
|
|
337
|
+
if (typeof value === 'string') return 'string';
|
|
338
|
+
if (typeof value === 'boolean') return 'boolean';
|
|
339
|
+
if (typeof value === 'bigint') return 'integer';
|
|
340
|
+
if (typeof value === 'number') {
|
|
341
|
+
return Number.isSafeInteger(value) ? 'integer' : 'float';
|
|
342
|
+
}
|
|
343
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
344
|
+
return 'bytes';
|
|
345
|
+
}
|
|
346
|
+
return 'json';
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function mergeBinarySnapshotColumnTypes(
|
|
350
|
+
current: BinarySnapshotColumnType | null,
|
|
351
|
+
next: BinarySnapshotColumnType
|
|
352
|
+
): BinarySnapshotColumnType {
|
|
353
|
+
if (!current || current === next) return next;
|
|
354
|
+
if (
|
|
355
|
+
(current === 'integer' && next === 'float') ||
|
|
356
|
+
(current === 'float' && next === 'integer')
|
|
357
|
+
) {
|
|
358
|
+
return 'float';
|
|
359
|
+
}
|
|
360
|
+
return 'json';
|
|
63
361
|
}
|
|
64
362
|
|
|
65
363
|
export interface PullResult {
|
|
@@ -71,6 +369,8 @@ export interface PullResult {
|
|
|
71
369
|
effectiveScopes: ScopeValues;
|
|
72
370
|
/** Minimum nextCursor across active subscriptions (for pruning cursor tracking). */
|
|
73
371
|
clientCursor: number;
|
|
372
|
+
/** Internal bootstrap timing breakdown used for benchmark-gated diagnostics. */
|
|
373
|
+
bootstrapTimings?: PullBootstrapTimings;
|
|
74
374
|
}
|
|
75
375
|
|
|
76
376
|
interface PendingExternalChunkWrite {
|
|
@@ -82,23 +382,34 @@ interface PendingExternalChunkWrite {
|
|
|
82
382
|
asOfCommitSeq: number;
|
|
83
383
|
rowCursor: string | null;
|
|
84
384
|
rowLimit: number;
|
|
385
|
+
nextRowCursor: string | null;
|
|
386
|
+
isLastPage: boolean;
|
|
85
387
|
};
|
|
86
|
-
|
|
388
|
+
payloadParts: Uint8Array[];
|
|
87
389
|
expiresAt: string;
|
|
88
390
|
}
|
|
89
391
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
392
|
+
async function runWithConcurrency<T>(
|
|
393
|
+
items: readonly T[],
|
|
394
|
+
concurrency: number,
|
|
395
|
+
worker: (item: T) => Promise<void>
|
|
396
|
+
): Promise<void> {
|
|
397
|
+
if (items.length === 0) return;
|
|
398
|
+
|
|
399
|
+
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
400
|
+
let nextIndex = 0;
|
|
401
|
+
|
|
402
|
+
async function runWorker(): Promise<void> {
|
|
403
|
+
while (nextIndex < items.length) {
|
|
404
|
+
const index = nextIndex;
|
|
405
|
+
nextIndex += 1;
|
|
406
|
+
const item = items[index];
|
|
407
|
+
if (item === undefined) continue;
|
|
408
|
+
await worker(item);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
|
|
102
413
|
}
|
|
103
414
|
|
|
104
415
|
/**
|
|
@@ -116,6 +427,27 @@ function sanitizeLimit(
|
|
|
116
427
|
return Math.max(min, Math.min(max, value));
|
|
117
428
|
}
|
|
118
429
|
|
|
430
|
+
function sanitizeGzipLevel(value: number | undefined): number {
|
|
431
|
+
if (value === undefined || value === null || !Number.isFinite(value)) {
|
|
432
|
+
return DEFAULT_SNAPSHOT_CHUNK_GZIP_LEVEL;
|
|
433
|
+
}
|
|
434
|
+
return Math.max(0, Math.min(9, Math.trunc(value)));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function isSerializablePullError(error: Error): boolean {
|
|
438
|
+
const withCode = error as Error & { code?: string };
|
|
439
|
+
return (
|
|
440
|
+
withCode.code === '40001' ||
|
|
441
|
+
error.message.toLowerCase().includes('could not serialize access')
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function delay(ms: number): Promise<void> {
|
|
446
|
+
await new Promise((resolve) => {
|
|
447
|
+
setTimeout(resolve, ms);
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
119
451
|
/**
|
|
120
452
|
* Merge all scope values into a flat ScopeValues for cursor tracking.
|
|
121
453
|
*/
|
|
@@ -149,6 +481,90 @@ interface PullResponseStats {
|
|
|
149
481
|
snapshotPageCount: number;
|
|
150
482
|
}
|
|
151
483
|
|
|
484
|
+
function assertPullChangeIdentityUnchanged(
|
|
485
|
+
source: string,
|
|
486
|
+
before: SyncChange,
|
|
487
|
+
after: SyncChange
|
|
488
|
+
): void {
|
|
489
|
+
if (before.table !== after.table) {
|
|
490
|
+
throw new Error(
|
|
491
|
+
`${source} cannot change change.table (${before.table} -> ${after.table})`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
if (before.row_id !== after.row_id) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
`${source} cannot change change.row_id (${before.row_id} -> ${after.row_id})`
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (before.op !== after.op) {
|
|
500
|
+
throw new Error(
|
|
501
|
+
`${source} cannot change change.op (${before.op} -> ${after.op})`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function projectPullChangeForClientSchema<
|
|
507
|
+
DB extends SyncCoreDb,
|
|
508
|
+
Auth extends SyncServerAuth,
|
|
509
|
+
>(args: {
|
|
510
|
+
handler: ServerTableHandler<DB, Auth>;
|
|
511
|
+
change: SyncChange;
|
|
512
|
+
schemaVersion: number;
|
|
513
|
+
}): SyncChange {
|
|
514
|
+
const projected = args.handler.projectChangeForVersion
|
|
515
|
+
? args.handler.projectChangeForVersion(args.change, args.schemaVersion)
|
|
516
|
+
: args.change;
|
|
517
|
+
assertPullChangeIdentityUnchanged(
|
|
518
|
+
'Server table handler projectChangeForVersion',
|
|
519
|
+
args.change,
|
|
520
|
+
projected
|
|
521
|
+
);
|
|
522
|
+
return projected;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function transformPullChanges<
|
|
526
|
+
DB extends SyncCoreDb,
|
|
527
|
+
Auth extends SyncServerAuth,
|
|
528
|
+
>(args: {
|
|
529
|
+
plugins: readonly SyncServerPullPlugin<DB, Auth>[];
|
|
530
|
+
ctx: { db: DbExecutor<DB>; actorId: string; auth: Auth };
|
|
531
|
+
tableHandler: ServerTableHandler<DB, Auth>;
|
|
532
|
+
subscription: {
|
|
533
|
+
id: string;
|
|
534
|
+
table: string;
|
|
535
|
+
scopes: ScopeValues;
|
|
536
|
+
params: Record<string, unknown> | undefined;
|
|
537
|
+
cursor: number;
|
|
538
|
+
crdtStateVectors: readonly SyncCrdtStateVectorHint[];
|
|
539
|
+
};
|
|
540
|
+
changes: readonly SyncChange[];
|
|
541
|
+
}): Promise<SyncChange[]> {
|
|
542
|
+
let changes = [...args.changes];
|
|
543
|
+
for (const plugin of args.plugins) {
|
|
544
|
+
if (!plugin.transformPullChanges) continue;
|
|
545
|
+
const nextChanges = await plugin.transformPullChanges({
|
|
546
|
+
ctx: args.ctx,
|
|
547
|
+
tableHandler: args.tableHandler,
|
|
548
|
+
subscription: args.subscription,
|
|
549
|
+
changes,
|
|
550
|
+
});
|
|
551
|
+
if (nextChanges.length !== changes.length) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
`Server pull plugin "${plugin.name}" cannot change pull change count (${changes.length} -> ${nextChanges.length})`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
for (let i = 0; i < changes.length; i += 1) {
|
|
557
|
+
assertPullChangeIdentityUnchanged(
|
|
558
|
+
`Server pull plugin "${plugin.name}"`,
|
|
559
|
+
changes[i]!,
|
|
560
|
+
nextChanges[i]!
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
changes = [...nextChanges];
|
|
564
|
+
}
|
|
565
|
+
return changes;
|
|
566
|
+
}
|
|
567
|
+
|
|
152
568
|
function summarizePullResponse(response: SyncPullResponse): PullResponseStats {
|
|
153
569
|
const subscriptions = response.subscriptions ?? [];
|
|
154
570
|
let activeSubscriptionCount = 0;
|
|
@@ -239,31 +655,44 @@ function recordPullMetrics(args: {
|
|
|
239
655
|
);
|
|
240
656
|
}
|
|
241
657
|
|
|
242
|
-
|
|
243
|
-
* Read synthetic commits created by notifyExternalDataChange() after a given cursor.
|
|
244
|
-
* Returns commit_seq and affected tables for each external change commit.
|
|
245
|
-
*/
|
|
246
|
-
async function readExternalDataChanges<DB extends SyncCoreDb>(
|
|
658
|
+
async function readLatestExternalCommitByTable<DB extends SyncCoreDb>(
|
|
247
659
|
trx: DbExecutor<DB>,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
660
|
+
args: { partitionId: string; afterCursor: number; tables: string[] }
|
|
661
|
+
): Promise<Map<string, number>> {
|
|
662
|
+
const tableNames = Array.from(
|
|
663
|
+
new Set(args.tables.filter((table) => typeof table === 'string'))
|
|
664
|
+
);
|
|
665
|
+
const latestByTable = new Map<string, number>();
|
|
666
|
+
if (tableNames.length === 0) {
|
|
667
|
+
return latestByTable;
|
|
668
|
+
}
|
|
669
|
+
|
|
251
670
|
type SyncExecutor = Pick<Kysely<SyncCoreDb>, 'selectFrom'>;
|
|
252
671
|
const executor = trx as SyncExecutor;
|
|
253
|
-
|
|
254
672
|
const rows = await executor
|
|
255
|
-
.selectFrom('
|
|
256
|
-
.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
673
|
+
.selectFrom('sync_table_commits as tc')
|
|
674
|
+
.innerJoin('sync_commits as cm', (join) =>
|
|
675
|
+
join
|
|
676
|
+
.onRef('cm.commit_seq', '=', 'tc.commit_seq')
|
|
677
|
+
.onRef('cm.partition_id', '=', 'tc.partition_id')
|
|
678
|
+
)
|
|
679
|
+
.select(['tc.table as table'])
|
|
680
|
+
.select((eb) => eb.fn.max('tc.commit_seq').as('latest_commit_seq'))
|
|
681
|
+
.where('tc.partition_id', '=', args.partitionId)
|
|
682
|
+
.where('cm.client_id', '=', EXTERNAL_CLIENT_ID)
|
|
683
|
+
.where('cm.change_count', '=', 0)
|
|
684
|
+
.where('tc.commit_seq', '>', args.afterCursor)
|
|
685
|
+
.where('tc.table', 'in', tableNames)
|
|
686
|
+
.groupBy('tc.table')
|
|
261
687
|
.execute();
|
|
262
688
|
|
|
263
|
-
|
|
264
|
-
commitSeq
|
|
265
|
-
|
|
266
|
-
|
|
689
|
+
for (const row of rows) {
|
|
690
|
+
const commitSeq = Number(row.latest_commit_seq ?? -1);
|
|
691
|
+
if (!Number.isFinite(commitSeq) || commitSeq < 0) continue;
|
|
692
|
+
latestByTable.set(row.table, commitSeq);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return latestByTable;
|
|
267
696
|
}
|
|
268
697
|
|
|
269
698
|
export async function pull<
|
|
@@ -287,10 +716,39 @@ export async function pull<
|
|
|
287
716
|
* Defaults to process-local memory cache.
|
|
288
717
|
*/
|
|
289
718
|
scopeCache?: ScopeCacheBackend;
|
|
719
|
+
/**
|
|
720
|
+
* Gzip compression level for generated snapshot chunks. The protocol remains
|
|
721
|
+
* gzip-only; this tunes CPU/size tradeoffs for deployments that know their
|
|
722
|
+
* network constraints.
|
|
723
|
+
*
|
|
724
|
+
* Default: 1, range: 0-9.
|
|
725
|
+
*/
|
|
726
|
+
snapshotChunkGzipLevel?: number;
|
|
727
|
+
/**
|
|
728
|
+
* Schema/cache semantic version included in generated snapshot chunk cache
|
|
729
|
+
* keys. Changing this value invalidates cached bootstrap chunks without
|
|
730
|
+
* requiring table data to change.
|
|
731
|
+
*/
|
|
732
|
+
snapshotChunkCacheSchemaVersion?: number | string | null;
|
|
733
|
+
/**
|
|
734
|
+
* Optional server plugins for protocol-level pull transforms.
|
|
735
|
+
*/
|
|
736
|
+
plugins?: readonly SyncServerPullPlugin<DB, Auth>[];
|
|
290
737
|
}): Promise<PullResult> {
|
|
291
738
|
const { request, dialect } = args;
|
|
292
739
|
const db = args.db;
|
|
740
|
+
const pullPlugins = sortServerPullPlugins(args.plugins);
|
|
293
741
|
const partitionId = args.auth.partitionId ?? 'default';
|
|
742
|
+
const snapshotChunkGzipLevel = sanitizeGzipLevel(args.snapshotChunkGzipLevel);
|
|
743
|
+
const clientSchemaVersion = request.schemaVersion;
|
|
744
|
+
if (!Number.isInteger(clientSchemaVersion) || clientSchemaVersion < 1) {
|
|
745
|
+
throw new Error('Pull request schemaVersion must be a positive integer');
|
|
746
|
+
}
|
|
747
|
+
const snapshotChunkCacheSchemaVersion =
|
|
748
|
+
args.snapshotChunkCacheSchemaVersion === null ||
|
|
749
|
+
args.snapshotChunkCacheSchemaVersion === undefined
|
|
750
|
+
? clientSchemaVersion
|
|
751
|
+
: `${clientSchemaVersion}:${args.snapshotChunkCacheSchemaVersion}`;
|
|
294
752
|
const requestedSubscriptionCount = Array.isArray(request.subscriptions)
|
|
295
753
|
? request.subscriptions.length
|
|
296
754
|
: 0;
|
|
@@ -308,12 +766,12 @@ export async function pull<
|
|
|
308
766
|
async (span) => {
|
|
309
767
|
try {
|
|
310
768
|
// Validate and sanitize request limits
|
|
311
|
-
const limitCommits = sanitizeLimit(request.limitCommits,
|
|
769
|
+
const limitCommits = sanitizeLimit(request.limitCommits, 1000, 1, 1000);
|
|
312
770
|
const limitSnapshotRows = sanitizeLimit(
|
|
313
771
|
request.limitSnapshotRows,
|
|
314
772
|
1000,
|
|
315
773
|
1,
|
|
316
|
-
|
|
774
|
+
50000
|
|
317
775
|
);
|
|
318
776
|
const maxSnapshotPages = sanitizeLimit(
|
|
319
777
|
request.maxSnapshotPages,
|
|
@@ -322,8 +780,13 @@ export async function pull<
|
|
|
322
780
|
50
|
|
323
781
|
);
|
|
324
782
|
const dedupeRows = request.dedupeRows === true;
|
|
325
|
-
const
|
|
326
|
-
|
|
783
|
+
const snapshotChunkEncoding = SYNC_SNAPSHOT_CHUNK_ENCODING;
|
|
784
|
+
const snapshotArtifactSelection = resolveSnapshotArtifactSelection(
|
|
785
|
+
request.snapshotArtifacts,
|
|
786
|
+
clientSchemaVersion
|
|
787
|
+
);
|
|
788
|
+
const snapshotArtifactSchemaVersion =
|
|
789
|
+
snapshotArtifactSelection?.schemaVersion ?? null;
|
|
327
790
|
// Resolve effective scopes for each subscription
|
|
328
791
|
const resolved = await resolveEffectiveScopesForSubscriptions({
|
|
329
792
|
db,
|
|
@@ -333,591 +796,1041 @@ export async function pull<
|
|
|
333
796
|
scopeCache: args.scopeCache ?? defaultScopeCache,
|
|
334
797
|
});
|
|
335
798
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
// Compute minimum cursor across all active subscriptions to scope the query.
|
|
352
|
-
let minSubCursor = Number.MAX_SAFE_INTEGER;
|
|
353
|
-
for (const sub of resolved) {
|
|
354
|
-
if (
|
|
355
|
-
sub.status === 'revoked' ||
|
|
356
|
-
Object.keys(sub.scopes).length === 0
|
|
357
|
-
)
|
|
358
|
-
continue;
|
|
359
|
-
const cursor = Math.max(-1, sub.cursor ?? -1);
|
|
360
|
-
if (cursor >= 0 && cursor < minSubCursor) {
|
|
361
|
-
minSubCursor = cursor;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const externalDataChanges =
|
|
366
|
-
minSubCursor < Number.MAX_SAFE_INTEGER && minSubCursor >= 0
|
|
367
|
-
? await readExternalDataChanges(trx, dialect, {
|
|
799
|
+
for (
|
|
800
|
+
let attemptIndex = 0;
|
|
801
|
+
attemptIndex < MAX_PULL_TRANSACTION_RETRIES;
|
|
802
|
+
attemptIndex += 1
|
|
803
|
+
) {
|
|
804
|
+
const pendingExternalChunkWrites: PendingExternalChunkWrite[] = [];
|
|
805
|
+
const bootstrapTimings = createPullBootstrapTimings();
|
|
806
|
+
|
|
807
|
+
try {
|
|
808
|
+
const result = await dialect.executeInTransaction(
|
|
809
|
+
db,
|
|
810
|
+
async (trx) => {
|
|
811
|
+
await dialect.setRepeatableRead(trx);
|
|
812
|
+
|
|
813
|
+
const maxCommitSeq = await dialect.readMaxCommitSeq(trx, {
|
|
368
814
|
partitionId,
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
for (const change of externalDataChanges) {
|
|
374
|
-
for (const table of change.tables) {
|
|
375
|
-
const previous = maxExternalCommitByTable.get(table) ?? -1;
|
|
376
|
-
if (change.commitSeq > previous) {
|
|
377
|
-
maxExternalCommitByTable.set(table, change.commitSeq);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
for (const sub of resolved) {
|
|
383
|
-
const cursor = Math.max(-1, sub.cursor ?? -1);
|
|
384
|
-
// Validate table handler exists (throws if not registered)
|
|
385
|
-
getServerHandlerOrThrow(args.handlers, sub.table);
|
|
815
|
+
});
|
|
816
|
+
const minCommitSeq = await dialect.readMinCommitSeq(trx, {
|
|
817
|
+
partitionId,
|
|
818
|
+
});
|
|
386
819
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
820
|
+
const subResponses: SyncPullSubscriptionResponse[] = [];
|
|
821
|
+
const activeSubscriptions: { scopes: ScopeValues }[] = [];
|
|
822
|
+
const nextCursors: number[] = [];
|
|
823
|
+
|
|
824
|
+
// Detect external data changes (synthetic commits from notifyExternalDataChange)
|
|
825
|
+
// Compute minimum cursor across all active subscriptions to scope the query.
|
|
826
|
+
let minSubCursor = Number.MAX_SAFE_INTEGER;
|
|
827
|
+
const activeTables = new Set<string>();
|
|
828
|
+
for (const sub of resolved) {
|
|
829
|
+
if (
|
|
830
|
+
sub.status === 'revoked' ||
|
|
831
|
+
Object.keys(sub.scopes).length === 0
|
|
832
|
+
)
|
|
833
|
+
continue;
|
|
834
|
+
activeTables.add(sub.table);
|
|
835
|
+
const cursor = Math.max(-1, sub.cursor ?? -1);
|
|
836
|
+
if (cursor >= 0 && cursor < minSubCursor) {
|
|
837
|
+
minSubCursor = cursor;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
401
840
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
841
|
+
const maxExternalCommitByTable =
|
|
842
|
+
minSubCursor < Number.MAX_SAFE_INTEGER && minSubCursor >= 0
|
|
843
|
+
? await readLatestExternalCommitByTable(trx, {
|
|
844
|
+
partitionId,
|
|
845
|
+
afterCursor: minSubCursor,
|
|
846
|
+
tables: Array.from(activeTables),
|
|
847
|
+
})
|
|
848
|
+
: new Map<string, number>();
|
|
849
|
+
|
|
850
|
+
for (const sub of resolved) {
|
|
851
|
+
const cursor = Math.max(-1, sub.cursor ?? -1);
|
|
852
|
+
// Validate table handler exists (throws if not registered)
|
|
853
|
+
if (!args.handlers.byTable.has(sub.table)) {
|
|
854
|
+
throw new Error(`Unknown table: ${sub.table}`);
|
|
855
|
+
}
|
|
407
856
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const initState: SyncBootstrapState = {
|
|
423
|
-
asOfCommitSeq: maxCommitSeq,
|
|
424
|
-
tables,
|
|
425
|
-
tableIndex: 0,
|
|
426
|
-
rowCursor: null,
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
const requestedState = sub.bootstrapState ?? null;
|
|
430
|
-
const state =
|
|
431
|
-
requestedState &&
|
|
432
|
-
typeof requestedState.asOfCommitSeq === 'number' &&
|
|
433
|
-
Array.isArray(requestedState.tables) &&
|
|
434
|
-
typeof requestedState.tableIndex === 'number'
|
|
435
|
-
? (requestedState as SyncBootstrapState)
|
|
436
|
-
: initState;
|
|
437
|
-
|
|
438
|
-
// If the bootstrap state's asOfCommitSeq is no longer catch-up-able, restart bootstrap.
|
|
439
|
-
const effectiveState =
|
|
440
|
-
state.asOfCommitSeq < minCommitSeq - 1 ? initState : state;
|
|
441
|
-
|
|
442
|
-
const tableName =
|
|
443
|
-
effectiveState.tables[effectiveState.tableIndex];
|
|
444
|
-
|
|
445
|
-
// No tables (or ran past the end): treat bootstrap as complete.
|
|
446
|
-
if (!tableName) {
|
|
447
|
-
subResponses.push({
|
|
448
|
-
id: sub.id,
|
|
449
|
-
status: 'active',
|
|
450
|
-
scopes: effectiveScopes,
|
|
451
|
-
bootstrap: true,
|
|
452
|
-
bootstrapState: null,
|
|
453
|
-
nextCursor: effectiveState.asOfCommitSeq,
|
|
454
|
-
commits: [],
|
|
455
|
-
snapshots: [],
|
|
456
|
-
});
|
|
457
|
-
nextCursors.push(effectiveState.asOfCommitSeq);
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
857
|
+
if (
|
|
858
|
+
sub.status === 'revoked' ||
|
|
859
|
+
Object.keys(sub.scopes).length === 0
|
|
860
|
+
) {
|
|
861
|
+
subResponses.push({
|
|
862
|
+
id: sub.id,
|
|
863
|
+
status: 'revoked',
|
|
864
|
+
scopes: {},
|
|
865
|
+
bootstrap: false,
|
|
866
|
+
nextCursor: cursor,
|
|
867
|
+
commits: [],
|
|
868
|
+
});
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
460
871
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
872
|
+
const effectiveScopes = sub.scopes;
|
|
873
|
+
activeSubscriptions.push({ scopes: effectiveScopes });
|
|
874
|
+
const latestExternalCommitForTable =
|
|
875
|
+
maxExternalCommitByTable.get(sub.table);
|
|
876
|
+
|
|
877
|
+
const needsBootstrap =
|
|
878
|
+
sub.bootstrapState != null ||
|
|
879
|
+
cursor < 0 ||
|
|
880
|
+
cursor > maxCommitSeq ||
|
|
881
|
+
(minCommitSeq > 0 && cursor < minCommitSeq - 1) ||
|
|
882
|
+
(latestExternalCommitForTable !== undefined &&
|
|
883
|
+
latestExternalCommitForTable > cursor);
|
|
884
|
+
|
|
885
|
+
if (needsBootstrap) {
|
|
886
|
+
const tables = getServerBootstrapOrderFor(
|
|
887
|
+
args.handlers,
|
|
888
|
+
sub.table
|
|
889
|
+
).map((handler) => handler.table);
|
|
890
|
+
const initState: SyncBootstrapState = {
|
|
891
|
+
asOfCommitSeq: maxCommitSeq,
|
|
892
|
+
tables,
|
|
893
|
+
tableIndex: 0,
|
|
894
|
+
rowCursor: null,
|
|
895
|
+
};
|
|
474
896
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
897
|
+
const requestedState = sub.bootstrapState ?? null;
|
|
898
|
+
const state =
|
|
899
|
+
requestedState &&
|
|
900
|
+
typeof requestedState.asOfCommitSeq === 'number' &&
|
|
901
|
+
Array.isArray(requestedState.tables) &&
|
|
902
|
+
typeof requestedState.tableIndex === 'number'
|
|
903
|
+
? (requestedState as SyncBootstrapState)
|
|
904
|
+
: initState;
|
|
905
|
+
|
|
906
|
+
// If the bootstrap state's asOfCommitSeq is no longer catch-up-able, restart bootstrap.
|
|
907
|
+
const effectiveState =
|
|
908
|
+
state.asOfCommitSeq < minCommitSeq - 1
|
|
909
|
+
? initState
|
|
910
|
+
: state;
|
|
911
|
+
|
|
912
|
+
const tableName =
|
|
913
|
+
effectiveState.tables[effectiveState.tableIndex];
|
|
914
|
+
|
|
915
|
+
// No tables (or ran past the end): treat bootstrap as complete.
|
|
916
|
+
if (!tableName) {
|
|
917
|
+
subResponses.push({
|
|
918
|
+
id: sub.id,
|
|
919
|
+
status: 'active',
|
|
920
|
+
scopes: effectiveScopes,
|
|
921
|
+
bootstrap: true,
|
|
922
|
+
bootstrapState: null,
|
|
923
|
+
nextCursor: effectiveState.asOfCommitSeq,
|
|
924
|
+
commits: [],
|
|
925
|
+
snapshots: [],
|
|
926
|
+
});
|
|
927
|
+
nextCursors.push(effectiveState.asOfCommitSeq);
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const snapshots: SyncSnapshot[] = [];
|
|
932
|
+
let nextState: SyncBootstrapState | null = effectiveState;
|
|
933
|
+
const cacheKey = await createSnapshotChunkScopeCacheKey({
|
|
934
|
+
partitionId,
|
|
935
|
+
scopes: effectiveScopes,
|
|
936
|
+
schemaVersion: snapshotChunkCacheSchemaVersion,
|
|
937
|
+
encoding: snapshotChunkEncoding,
|
|
938
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
939
|
+
gzipLevel: snapshotChunkGzipLevel,
|
|
940
|
+
});
|
|
941
|
+
const artifactScopeKey =
|
|
942
|
+
snapshotArtifactSelection && snapshotArtifactSchemaVersion
|
|
943
|
+
? await createScopedSnapshotArtifactScopeCacheKey({
|
|
944
|
+
partitionId,
|
|
945
|
+
subscriptionId: sub.id,
|
|
946
|
+
scopes: effectiveScopes,
|
|
947
|
+
schemaVersion: snapshotArtifactSchemaVersion,
|
|
948
|
+
artifactKind:
|
|
949
|
+
snapshotArtifactSelection.artifactKind,
|
|
950
|
+
compression: snapshotArtifactSelection.compression,
|
|
951
|
+
features: snapshotArtifactSelection.featureSet,
|
|
952
|
+
})
|
|
953
|
+
: null;
|
|
954
|
+
|
|
955
|
+
interface SnapshotBundle {
|
|
956
|
+
table: string;
|
|
957
|
+
tableIndex: number;
|
|
958
|
+
startCursor: string | null;
|
|
959
|
+
nextRowCursor: string | null;
|
|
960
|
+
isFirstPage: boolean;
|
|
961
|
+
isLastPage: boolean;
|
|
962
|
+
pageCount: number;
|
|
963
|
+
cacheRowLimit: number | null;
|
|
964
|
+
ttlMs: number;
|
|
965
|
+
binaryColumns?: readonly BinarySnapshotColumn[];
|
|
966
|
+
binaryEncoder?: BinarySnapshotRowsEncoder;
|
|
967
|
+
binaryRows: unknown[];
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const createSnapshotBundle = (
|
|
971
|
+
table: string,
|
|
972
|
+
tableIndex: number,
|
|
973
|
+
rowCursor: string | null,
|
|
974
|
+
ttlMs: number,
|
|
975
|
+
binaryColumns?: readonly BinarySnapshotColumn[],
|
|
976
|
+
binaryEncoder?: BinarySnapshotRowsEncoder
|
|
977
|
+
): SnapshotBundle => {
|
|
978
|
+
return {
|
|
979
|
+
table,
|
|
980
|
+
tableIndex,
|
|
981
|
+
startCursor: rowCursor,
|
|
982
|
+
nextRowCursor: null,
|
|
983
|
+
isFirstPage: rowCursor == null,
|
|
984
|
+
isLastPage: false,
|
|
985
|
+
pageCount: 0,
|
|
986
|
+
cacheRowLimit: null,
|
|
987
|
+
ttlMs,
|
|
988
|
+
binaryColumns,
|
|
989
|
+
binaryEncoder,
|
|
990
|
+
binaryRows: [],
|
|
991
|
+
};
|
|
992
|
+
};
|
|
483
993
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
994
|
+
const snapshotBootstrapStateAfter = (args: {
|
|
995
|
+
tableIndex: number;
|
|
996
|
+
nextRowCursor: string | null;
|
|
997
|
+
isLastPage: boolean;
|
|
998
|
+
}): SyncBootstrapState | null => {
|
|
999
|
+
if (!args.isLastPage) {
|
|
1000
|
+
return {
|
|
1001
|
+
...effectiveState,
|
|
1002
|
+
tableIndex: args.tableIndex,
|
|
1003
|
+
rowCursor: args.nextRowCursor,
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
if (args.tableIndex + 1 < effectiveState.tables.length) {
|
|
1007
|
+
return {
|
|
1008
|
+
...effectiveState,
|
|
1009
|
+
tableIndex: args.tableIndex + 1,
|
|
1010
|
+
rowCursor: null,
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
return null;
|
|
1014
|
+
};
|
|
495
1015
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
1016
|
+
const encodeSnapshotBundlePayload = (
|
|
1017
|
+
bundle: SnapshotBundle
|
|
1018
|
+
): Uint8Array[] => {
|
|
1019
|
+
const encodeStartedAt = Date.now();
|
|
1020
|
+
const payload = bundle.binaryEncoder
|
|
1021
|
+
? bundle.binaryEncoder(bundle.binaryRows)
|
|
1022
|
+
: encodeBinarySnapshotRows(
|
|
1023
|
+
bundle.table,
|
|
1024
|
+
bundle.binaryRows,
|
|
1025
|
+
bundle.binaryColumns
|
|
1026
|
+
);
|
|
1027
|
+
bootstrapTimings.binaryEncodeMs += Math.max(
|
|
1028
|
+
0,
|
|
1029
|
+
Date.now() - encodeStartedAt
|
|
1030
|
+
);
|
|
1031
|
+
return [payload];
|
|
512
1032
|
};
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
1033
|
+
|
|
1034
|
+
const flushSnapshotBundle = async (
|
|
1035
|
+
bundle: SnapshotBundle
|
|
1036
|
+
): Promise<void> => {
|
|
1037
|
+
const nowIso = new Date().toISOString();
|
|
1038
|
+
const bundleRowLimit = Math.max(
|
|
1039
|
+
1,
|
|
1040
|
+
bundle.cacheRowLimit ??
|
|
1041
|
+
limitSnapshotRows * bundle.pageCount
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
const cacheLookupStartedAt = Date.now();
|
|
1045
|
+
const cached = await readSnapshotChunkRefByPageKey(trx, {
|
|
517
1046
|
partitionId,
|
|
518
1047
|
scopeKey: cacheKey,
|
|
519
1048
|
scope: bundle.table,
|
|
520
1049
|
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
521
1050
|
rowCursor: bundle.startCursor,
|
|
522
1051
|
rowLimit: bundleRowLimit,
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1052
|
+
encoding: snapshotChunkEncoding,
|
|
1053
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1054
|
+
nowIso,
|
|
1055
|
+
});
|
|
1056
|
+
bootstrapTimings.chunkCacheLookupMs += Math.max(
|
|
1057
|
+
0,
|
|
1058
|
+
Date.now() - cacheLookupStartedAt
|
|
1059
|
+
);
|
|
1060
|
+
|
|
1061
|
+
let chunkRef: SyncSnapshotChunkRef | null = cached;
|
|
1062
|
+
if (!chunkRef) {
|
|
1063
|
+
const expiresAt = new Date(
|
|
1064
|
+
Date.now() + Math.max(1000, bundle.ttlMs)
|
|
1065
|
+
).toISOString();
|
|
1066
|
+
|
|
1067
|
+
if (args.chunkStorage) {
|
|
1068
|
+
const snapshot: SyncSnapshot = {
|
|
1069
|
+
table: bundle.table,
|
|
1070
|
+
rows: [],
|
|
1071
|
+
chunks: [],
|
|
1072
|
+
isFirstPage: bundle.isFirstPage,
|
|
1073
|
+
isLastPage: bundle.isLastPage,
|
|
1074
|
+
bootstrapStateAfter: snapshotBootstrapStateAfter({
|
|
1075
|
+
tableIndex: bundle.tableIndex,
|
|
1076
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
1077
|
+
isLastPage: bundle.isLastPage,
|
|
1078
|
+
}),
|
|
1079
|
+
};
|
|
1080
|
+
snapshots.push(snapshot);
|
|
1081
|
+
pendingExternalChunkWrites.push({
|
|
1082
|
+
snapshot,
|
|
1083
|
+
cacheLookup: {
|
|
1084
|
+
partitionId,
|
|
1085
|
+
scopeKey: cacheKey,
|
|
1086
|
+
scope: bundle.table,
|
|
1087
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
1088
|
+
rowCursor: bundle.startCursor,
|
|
1089
|
+
rowLimit: bundleRowLimit,
|
|
1090
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
1091
|
+
isLastPage: bundle.isLastPage,
|
|
1092
|
+
},
|
|
1093
|
+
payloadParts: encodeSnapshotBundlePayload(bundle),
|
|
1094
|
+
expiresAt,
|
|
1095
|
+
});
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const encodedChunk =
|
|
1099
|
+
await encodeCompressedSnapshotChunk(
|
|
1100
|
+
encodeSnapshotBundlePayload(bundle),
|
|
1101
|
+
snapshotChunkGzipLevel
|
|
1102
|
+
);
|
|
1103
|
+
bootstrapTimings.chunkGzipMs += encodedChunk.gzipMs;
|
|
1104
|
+
bootstrapTimings.chunkHashMs += encodedChunk.hashMs;
|
|
1105
|
+
const chunkId = randomId();
|
|
1106
|
+
const chunkPersistStartedAt = Date.now();
|
|
1107
|
+
chunkRef = await insertSnapshotChunk(trx, {
|
|
1108
|
+
chunkId,
|
|
1109
|
+
partitionId,
|
|
1110
|
+
scopeKey: cacheKey,
|
|
1111
|
+
scope: bundle.table,
|
|
1112
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
1113
|
+
rowCursor: bundle.startCursor,
|
|
1114
|
+
rowLimit: bundleRowLimit,
|
|
1115
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
1116
|
+
isLastPage: bundle.isLastPage,
|
|
1117
|
+
encoding: snapshotChunkEncoding,
|
|
1118
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1119
|
+
sha256: encodedChunk.sha256,
|
|
1120
|
+
body: encodedChunk.body,
|
|
1121
|
+
expiresAt,
|
|
1122
|
+
});
|
|
1123
|
+
bootstrapTimings.chunkPersistMs += Math.max(
|
|
1124
|
+
0,
|
|
1125
|
+
Date.now() - chunkPersistStartedAt
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const chunk = toResponseChunkRef(chunkRef);
|
|
1130
|
+
snapshots.push({
|
|
1131
|
+
table: bundle.table,
|
|
1132
|
+
rows: [],
|
|
1133
|
+
chunks: [chunk],
|
|
1134
|
+
manifest: await createChunkedSnapshotManifest({
|
|
1135
|
+
table: bundle.table,
|
|
1136
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
1137
|
+
scopeKey: cacheKey,
|
|
1138
|
+
rowCursor: bundle.startCursor,
|
|
1139
|
+
rowLimit: bundleRowLimit,
|
|
1140
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
1141
|
+
isFirstPage: bundle.isFirstPage,
|
|
1142
|
+
isLastPage: bundle.isLastPage,
|
|
1143
|
+
chunks: [chunk],
|
|
1144
|
+
}),
|
|
1145
|
+
isFirstPage: bundle.isFirstPage,
|
|
1146
|
+
isLastPage: bundle.isLastPage,
|
|
1147
|
+
bootstrapStateAfter: snapshotBootstrapStateAfter({
|
|
1148
|
+
tableIndex: bundle.tableIndex,
|
|
1149
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
1150
|
+
isLastPage: bundle.isLastPage,
|
|
1151
|
+
}),
|
|
1152
|
+
});
|
|
1153
|
+
};
|
|
547
1154
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
1155
|
+
let activeBundle: SnapshotBundle | null = null;
|
|
1156
|
+
|
|
1157
|
+
for (
|
|
1158
|
+
let pageIndex = 0;
|
|
1159
|
+
pageIndex < maxSnapshotPages;
|
|
1160
|
+
pageIndex++
|
|
1161
|
+
) {
|
|
1162
|
+
if (!nextState) break;
|
|
1163
|
+
|
|
1164
|
+
const nextTableName: string | undefined =
|
|
1165
|
+
nextState.tables[nextState.tableIndex];
|
|
1166
|
+
if (!nextTableName) {
|
|
1167
|
+
if (activeBundle) {
|
|
1168
|
+
activeBundle.isLastPage = true;
|
|
1169
|
+
await flushSnapshotBundle(activeBundle);
|
|
1170
|
+
activeBundle = null;
|
|
1171
|
+
}
|
|
1172
|
+
nextState = null;
|
|
1173
|
+
break;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
const tableHandler =
|
|
1177
|
+
args.handlers.byTable.get(nextTableName);
|
|
1178
|
+
if (!tableHandler) {
|
|
1179
|
+
throw new Error(`Unknown table: ${nextTableName}`);
|
|
1180
|
+
}
|
|
1181
|
+
if (
|
|
1182
|
+
!activeBundle ||
|
|
1183
|
+
activeBundle.table !== nextTableName
|
|
1184
|
+
) {
|
|
1185
|
+
if (activeBundle) {
|
|
1186
|
+
await flushSnapshotBundle(activeBundle);
|
|
1187
|
+
}
|
|
1188
|
+
activeBundle = createSnapshotBundle(
|
|
1189
|
+
nextTableName,
|
|
1190
|
+
nextState.tableIndex,
|
|
1191
|
+
nextState.rowCursor,
|
|
1192
|
+
tableHandler.snapshotChunkTtlMs ??
|
|
1193
|
+
24 * 60 * 60 * 1000,
|
|
1194
|
+
resolveSnapshotBinaryColumns(
|
|
1195
|
+
tableHandler,
|
|
1196
|
+
clientSchemaVersion
|
|
1197
|
+
),
|
|
1198
|
+
resolveSnapshotBinaryEncoder(
|
|
1199
|
+
tableHandler,
|
|
1200
|
+
clientSchemaVersion
|
|
1201
|
+
)
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (artifactScopeKey && activeBundle.pageCount === 0) {
|
|
1206
|
+
const pagesRemaining = Math.max(
|
|
1207
|
+
1,
|
|
1208
|
+
maxSnapshotPages - pageIndex
|
|
1209
|
+
);
|
|
1210
|
+
const artifactRowLimit =
|
|
1211
|
+
resolveBinarySnapshotBundleRowLimit({
|
|
1212
|
+
limitSnapshotRows,
|
|
1213
|
+
pagesRemaining,
|
|
1214
|
+
});
|
|
1215
|
+
const artifactLookupStartedAt = Date.now();
|
|
1216
|
+
const artifact =
|
|
1217
|
+
await readBestScopedSnapshotArtifactRefForPageCapacity(
|
|
1218
|
+
trx,
|
|
1219
|
+
{
|
|
1220
|
+
partitionId,
|
|
1221
|
+
scopeKey: artifactScopeKey,
|
|
1222
|
+
subscriptionId: sub.id,
|
|
1223
|
+
table: nextTableName,
|
|
1224
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
1225
|
+
rowCursor: nextState.rowCursor,
|
|
1226
|
+
maxRowLimit: artifactRowLimit,
|
|
1227
|
+
artifactKind:
|
|
1228
|
+
snapshotArtifactSelection!.artifactKind,
|
|
1229
|
+
schemaVersion: snapshotArtifactSchemaVersion!,
|
|
1230
|
+
compression:
|
|
1231
|
+
snapshotArtifactSelection!.compression,
|
|
1232
|
+
}
|
|
1233
|
+
);
|
|
1234
|
+
bootstrapTimings.artifactCacheLookupMs += Math.max(
|
|
1235
|
+
0,
|
|
1236
|
+
Date.now() - artifactLookupStartedAt
|
|
1237
|
+
);
|
|
1238
|
+
|
|
1239
|
+
if (
|
|
1240
|
+
artifact &&
|
|
1241
|
+
(artifact.isLastPage ||
|
|
1242
|
+
artifact.nextRowCursor !== null)
|
|
1243
|
+
) {
|
|
1244
|
+
snapshots.push({
|
|
1245
|
+
table: nextTableName,
|
|
1246
|
+
rows: [],
|
|
1247
|
+
artifacts: [artifact],
|
|
1248
|
+
isFirstPage: artifact.isFirstPage,
|
|
1249
|
+
isLastPage: artifact.isLastPage,
|
|
1250
|
+
bootstrapStateAfter: snapshotBootstrapStateAfter({
|
|
1251
|
+
tableIndex: nextState.tableIndex,
|
|
1252
|
+
nextRowCursor: artifact.nextRowCursor,
|
|
1253
|
+
isLastPage: artifact.isLastPage,
|
|
1254
|
+
}),
|
|
1255
|
+
});
|
|
1256
|
+
activeBundle = null;
|
|
1257
|
+
const selectedArtifactRowLimit =
|
|
1258
|
+
artifact.manifest.rowLimit;
|
|
1259
|
+
pageIndex +=
|
|
1260
|
+
Math.max(
|
|
1261
|
+
1,
|
|
1262
|
+
Math.ceil(
|
|
1263
|
+
selectedArtifactRowLimit / limitSnapshotRows
|
|
1264
|
+
)
|
|
1265
|
+
) - 1;
|
|
1266
|
+
|
|
1267
|
+
nextState = snapshotBootstrapStateAfter({
|
|
1268
|
+
tableIndex: nextState.tableIndex,
|
|
1269
|
+
nextRowCursor: artifact.nextRowCursor,
|
|
1270
|
+
isLastPage: artifact.isLastPage,
|
|
1271
|
+
});
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (activeBundle.pageCount === 0) {
|
|
1277
|
+
const pagesRemaining = Math.max(
|
|
1278
|
+
1,
|
|
1279
|
+
maxSnapshotPages - pageIndex
|
|
1280
|
+
);
|
|
1281
|
+
const cachedRowLimit =
|
|
1282
|
+
resolveBinarySnapshotBundleRowLimit({
|
|
1283
|
+
limitSnapshotRows,
|
|
1284
|
+
pagesRemaining,
|
|
1285
|
+
});
|
|
1286
|
+
activeBundle.cacheRowLimit = cachedRowLimit;
|
|
1287
|
+
const cacheLookupStartedAt = Date.now();
|
|
1288
|
+
const cached: SnapshotChunkRefWithContinuation | null =
|
|
1289
|
+
await readSnapshotChunkRefByPageKey(trx, {
|
|
1290
|
+
partitionId,
|
|
1291
|
+
scopeKey: cacheKey,
|
|
1292
|
+
scope: nextTableName,
|
|
1293
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
1294
|
+
rowCursor: nextState.rowCursor,
|
|
1295
|
+
rowLimit: cachedRowLimit,
|
|
1296
|
+
encoding: snapshotChunkEncoding,
|
|
1297
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1298
|
+
});
|
|
1299
|
+
bootstrapTimings.chunkCacheLookupMs += Math.max(
|
|
1300
|
+
0,
|
|
1301
|
+
Date.now() - cacheLookupStartedAt
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
if (
|
|
1305
|
+
cached &&
|
|
1306
|
+
(cached.isLastPage || cached.nextRowCursor !== null)
|
|
1307
|
+
) {
|
|
1308
|
+
const chunk = toResponseChunkRef(cached);
|
|
1309
|
+
snapshots.push({
|
|
1310
|
+
table: nextTableName,
|
|
1311
|
+
rows: [],
|
|
1312
|
+
chunks: [chunk],
|
|
1313
|
+
manifest: await createChunkedSnapshotManifest({
|
|
1314
|
+
table: nextTableName,
|
|
1315
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
1316
|
+
scopeKey: cacheKey,
|
|
1317
|
+
rowCursor: nextState.rowCursor,
|
|
1318
|
+
rowLimit: cachedRowLimit,
|
|
1319
|
+
nextRowCursor: cached.nextRowCursor,
|
|
1320
|
+
isFirstPage: nextState.rowCursor == null,
|
|
1321
|
+
isLastPage: cached.isLastPage,
|
|
1322
|
+
chunks: [chunk],
|
|
1323
|
+
}),
|
|
1324
|
+
isFirstPage: nextState.rowCursor == null,
|
|
1325
|
+
isLastPage: cached.isLastPage,
|
|
1326
|
+
bootstrapStateAfter: snapshotBootstrapStateAfter({
|
|
1327
|
+
tableIndex: nextState.tableIndex,
|
|
1328
|
+
nextRowCursor: cached.nextRowCursor,
|
|
1329
|
+
isLastPage: cached.isLastPage,
|
|
1330
|
+
}),
|
|
1331
|
+
});
|
|
1332
|
+
activeBundle = null;
|
|
1333
|
+
pageIndex +=
|
|
1334
|
+
Math.max(
|
|
1335
|
+
1,
|
|
1336
|
+
Math.ceil(cachedRowLimit / limitSnapshotRows)
|
|
1337
|
+
) - 1;
|
|
1338
|
+
|
|
1339
|
+
nextState = snapshotBootstrapStateAfter({
|
|
1340
|
+
tableIndex: nextState.tableIndex,
|
|
1341
|
+
nextRowCursor: cached.nextRowCursor,
|
|
1342
|
+
isLastPage: cached.isLastPage,
|
|
1343
|
+
});
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
const snapshotQueryStartedAt = Date.now();
|
|
1349
|
+
const page: {
|
|
1350
|
+
rows: unknown[];
|
|
1351
|
+
nextCursor: string | null;
|
|
1352
|
+
} = await tableHandler.snapshot(
|
|
1353
|
+
{
|
|
1354
|
+
db: trx,
|
|
1355
|
+
actorId: args.auth.actorId,
|
|
1356
|
+
auth: args.auth,
|
|
1357
|
+
scopeValues: effectiveScopes,
|
|
1358
|
+
cursor: nextState.rowCursor,
|
|
1359
|
+
limit: limitSnapshotRows,
|
|
1360
|
+
schemaVersion: clientSchemaVersion,
|
|
1361
|
+
},
|
|
1362
|
+
sub.params
|
|
1363
|
+
);
|
|
1364
|
+
bootstrapTimings.snapshotQueryMs += Math.max(
|
|
1365
|
+
0,
|
|
1366
|
+
Date.now() - snapshotQueryStartedAt
|
|
1367
|
+
);
|
|
1368
|
+
|
|
1369
|
+
const pageRows = page.rows ?? [];
|
|
1370
|
+
activeBundle.nextRowCursor = page.nextCursor;
|
|
1371
|
+
activeBundle.binaryRows.push(...pageRows);
|
|
1372
|
+
activeBundle.pageCount += 1;
|
|
1373
|
+
|
|
1374
|
+
if (page.nextCursor != null) {
|
|
1375
|
+
const shouldFlushBinaryBundle =
|
|
1376
|
+
activeBundle.binaryRows.length >=
|
|
1377
|
+
DEFAULT_MAX_BINARY_SNAPSHOT_BUNDLE_ROWS;
|
|
1378
|
+
if (shouldFlushBinaryBundle) {
|
|
1379
|
+
await flushSnapshotBundle(activeBundle);
|
|
1380
|
+
activeBundle = null;
|
|
1381
|
+
}
|
|
1382
|
+
nextState = {
|
|
1383
|
+
...nextState,
|
|
1384
|
+
rowCursor: page.nextCursor,
|
|
1385
|
+
};
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
activeBundle.isLastPage = true;
|
|
1390
|
+
await flushSnapshotBundle(activeBundle);
|
|
1391
|
+
activeBundle = null;
|
|
1392
|
+
|
|
1393
|
+
if (nextState.tableIndex + 1 < nextState.tables.length) {
|
|
1394
|
+
nextState = {
|
|
1395
|
+
...nextState,
|
|
1396
|
+
tableIndex: nextState.tableIndex + 1,
|
|
1397
|
+
rowCursor: null,
|
|
1398
|
+
};
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
nextState = null;
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
if (activeBundle) {
|
|
1407
|
+
await flushSnapshotBundle(activeBundle);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
subResponses.push({
|
|
1411
|
+
id: sub.id,
|
|
1412
|
+
status: 'active',
|
|
1413
|
+
scopes: effectiveScopes,
|
|
1414
|
+
bootstrap: true,
|
|
1415
|
+
bootstrapState: nextState,
|
|
1416
|
+
nextCursor: effectiveState.asOfCommitSeq,
|
|
1417
|
+
commits: [],
|
|
1418
|
+
snapshots,
|
|
1419
|
+
});
|
|
1420
|
+
nextCursors.push(effectiveState.asOfCommitSeq);
|
|
1421
|
+
continue;
|
|
573
1422
|
}
|
|
574
|
-
nextState = null;
|
|
575
|
-
break;
|
|
576
|
-
}
|
|
577
1423
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const bundleHeader = encodeSnapshotRows([]);
|
|
585
|
-
activeBundle = {
|
|
586
|
-
table: nextTableName,
|
|
587
|
-
startCursor: nextState.rowCursor,
|
|
588
|
-
isFirstPage: nextState.rowCursor == null,
|
|
589
|
-
isLastPage: false,
|
|
590
|
-
pageCount: 0,
|
|
591
|
-
ttlMs:
|
|
592
|
-
tableHandler.snapshotChunkTtlMs ?? 24 * 60 * 60 * 1000,
|
|
593
|
-
rowFrameParts: [bundleHeader],
|
|
594
|
-
};
|
|
595
|
-
}
|
|
1424
|
+
// Incremental pull for this subscription. The dialect row query
|
|
1425
|
+
// carries the scanned commit-window max when matching rows exist,
|
|
1426
|
+
// so we only need a separate commit-window scan when the row query
|
|
1427
|
+
// returns no matches at all.
|
|
1428
|
+
const incrementalRows: IncrementalPullRow[] = [];
|
|
1429
|
+
let maxScannedCommitSeq = cursor;
|
|
596
1430
|
|
|
597
|
-
|
|
598
|
-
|
|
1431
|
+
for await (const row of dialect.iterateIncrementalPullRows(
|
|
1432
|
+
trx,
|
|
599
1433
|
{
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if (page.nextCursor != null) {
|
|
615
|
-
nextState = { ...nextState, rowCursor: page.nextCursor };
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
activeBundle.isLastPage = true;
|
|
620
|
-
await flushSnapshotBundle(activeBundle);
|
|
621
|
-
activeBundle = null;
|
|
622
|
-
|
|
623
|
-
if (nextState.tableIndex + 1 < nextState.tables.length) {
|
|
624
|
-
nextState = {
|
|
625
|
-
...nextState,
|
|
626
|
-
tableIndex: nextState.tableIndex + 1,
|
|
627
|
-
rowCursor: null,
|
|
628
|
-
};
|
|
629
|
-
continue;
|
|
630
|
-
}
|
|
1434
|
+
partitionId,
|
|
1435
|
+
table: sub.table,
|
|
1436
|
+
scopes: effectiveScopes,
|
|
1437
|
+
cursor,
|
|
1438
|
+
limitCommits,
|
|
1439
|
+
}
|
|
1440
|
+
)) {
|
|
1441
|
+
incrementalRows.push(row);
|
|
1442
|
+
maxScannedCommitSeq = Math.max(
|
|
1443
|
+
maxScannedCommitSeq,
|
|
1444
|
+
row.scanned_max_commit_seq ?? row.commit_seq
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
631
1447
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1448
|
+
if (incrementalRows.length === 0) {
|
|
1449
|
+
const scannedCommitSeqs =
|
|
1450
|
+
await dialect.readCommitSeqsForPull(trx, {
|
|
1451
|
+
partitionId,
|
|
1452
|
+
cursor,
|
|
1453
|
+
limitCommits,
|
|
1454
|
+
tables: [sub.table],
|
|
1455
|
+
});
|
|
1456
|
+
maxScannedCommitSeq =
|
|
1457
|
+
scannedCommitSeqs.length > 0
|
|
1458
|
+
? scannedCommitSeqs[scannedCommitSeqs.length - 1]!
|
|
1459
|
+
: cursor;
|
|
1460
|
+
|
|
1461
|
+
if (scannedCommitSeqs.length === 0) {
|
|
1462
|
+
subResponses.push({
|
|
1463
|
+
id: sub.id,
|
|
1464
|
+
status: 'active',
|
|
1465
|
+
scopes: effectiveScopes,
|
|
1466
|
+
bootstrap: false,
|
|
1467
|
+
nextCursor: cursor,
|
|
1468
|
+
commits: [],
|
|
1469
|
+
});
|
|
1470
|
+
nextCursors.push(cursor);
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
635
1474
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1475
|
+
const tableHandler = args.handlers.byTable.get(sub.table);
|
|
1476
|
+
if (!tableHandler) {
|
|
1477
|
+
throw new Error(`Unknown table: ${sub.table}`);
|
|
1478
|
+
}
|
|
639
1479
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1480
|
+
const incrementalItems = incrementalRows.map((r) => ({
|
|
1481
|
+
commitSeq: r.commit_seq,
|
|
1482
|
+
createdAt: r.created_at,
|
|
1483
|
+
actorId: r.actor_id,
|
|
1484
|
+
change: projectPullChangeForClientSchema({
|
|
1485
|
+
handler: tableHandler,
|
|
1486
|
+
schemaVersion: clientSchemaVersion,
|
|
1487
|
+
change: {
|
|
1488
|
+
table: r.table,
|
|
1489
|
+
row_id: r.row_id,
|
|
1490
|
+
op: r.op,
|
|
1491
|
+
row_json: r.row_json,
|
|
1492
|
+
row_version: r.row_version,
|
|
1493
|
+
scopes: r.scopes,
|
|
1494
|
+
} satisfies SyncChange,
|
|
1495
|
+
}),
|
|
1496
|
+
}));
|
|
1497
|
+
|
|
1498
|
+
if (pullPlugins.length > 0 && incrementalItems.length > 0) {
|
|
1499
|
+
const transformedChanges = await transformPullChanges({
|
|
1500
|
+
plugins: pullPlugins,
|
|
1501
|
+
ctx: {
|
|
1502
|
+
db: trx,
|
|
1503
|
+
actorId: args.auth.actorId,
|
|
1504
|
+
auth: args.auth,
|
|
1505
|
+
},
|
|
1506
|
+
tableHandler,
|
|
1507
|
+
subscription: {
|
|
1508
|
+
id: sub.id,
|
|
1509
|
+
table: sub.table,
|
|
1510
|
+
scopes: effectiveScopes,
|
|
1511
|
+
params: sub.params,
|
|
1512
|
+
cursor,
|
|
1513
|
+
crdtStateVectors: sub.crdtStateVectors,
|
|
1514
|
+
},
|
|
1515
|
+
changes: incrementalItems.map((item) => item.change),
|
|
1516
|
+
});
|
|
1517
|
+
for (let i = 0; i < incrementalItems.length; i += 1) {
|
|
1518
|
+
incrementalItems[i]!.change = transformedChanges[i]!;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
653
1521
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1522
|
+
let nextCursor = cursor;
|
|
1523
|
+
|
|
1524
|
+
if (dedupeRows) {
|
|
1525
|
+
const latestByRowKey = new Map<
|
|
1526
|
+
string,
|
|
1527
|
+
{
|
|
1528
|
+
commitSeq: number;
|
|
1529
|
+
createdAt: string;
|
|
1530
|
+
actorId: string;
|
|
1531
|
+
change: SyncChange;
|
|
1532
|
+
}
|
|
1533
|
+
>();
|
|
1534
|
+
|
|
1535
|
+
for (const item of incrementalItems) {
|
|
1536
|
+
nextCursor = Math.max(nextCursor, item.commitSeq);
|
|
1537
|
+
const rowKey = `${item.change.table}\u0000${item.change.row_id}`;
|
|
1538
|
+
|
|
1539
|
+
// Move row keys to insertion tail so Map iteration yields
|
|
1540
|
+
// "latest change wins" order without a full array sort.
|
|
1541
|
+
if (latestByRowKey.has(rowKey)) {
|
|
1542
|
+
latestByRowKey.delete(rowKey);
|
|
1543
|
+
}
|
|
1544
|
+
latestByRowKey.set(rowKey, {
|
|
1545
|
+
commitSeq: item.commitSeq,
|
|
1546
|
+
createdAt: item.createdAt,
|
|
1547
|
+
actorId: item.actorId,
|
|
1548
|
+
change: item.change,
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
nextCursor = Math.max(nextCursor, maxScannedCommitSeq);
|
|
1553
|
+
|
|
1554
|
+
if (latestByRowKey.size === 0) {
|
|
1555
|
+
subResponses.push({
|
|
1556
|
+
id: sub.id,
|
|
1557
|
+
status: 'active',
|
|
1558
|
+
scopes: effectiveScopes,
|
|
1559
|
+
bootstrap: false,
|
|
1560
|
+
nextCursor,
|
|
1561
|
+
commits: [],
|
|
1562
|
+
});
|
|
1563
|
+
nextCursors.push(nextCursor);
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
const commits: SyncCommit[] = [];
|
|
1568
|
+
for (const item of latestByRowKey.values()) {
|
|
1569
|
+
const lastCommit = commits[commits.length - 1];
|
|
1570
|
+
if (
|
|
1571
|
+
!lastCommit ||
|
|
1572
|
+
lastCommit.commitSeq !== item.commitSeq
|
|
1573
|
+
) {
|
|
1574
|
+
commits.push({
|
|
1575
|
+
commitSeq: item.commitSeq,
|
|
1576
|
+
createdAt: item.createdAt,
|
|
1577
|
+
actorId: item.actorId,
|
|
1578
|
+
changes: [item.change],
|
|
1579
|
+
});
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1582
|
+
lastCommit.changes.push(item.change);
|
|
1583
|
+
}
|
|
1584
|
+
const integrity = await createWireSubscriptionIntegrity({
|
|
1585
|
+
partitionId,
|
|
1586
|
+
subscriptionId: sub.id,
|
|
1587
|
+
previousRoot:
|
|
1588
|
+
typeof sub.verifiedRoot === 'string'
|
|
1589
|
+
? sub.verifiedRoot
|
|
1590
|
+
: SYNCULAR_COMMIT_GENESIS_ROOT,
|
|
1591
|
+
commits,
|
|
1592
|
+
});
|
|
680
1593
|
|
|
681
|
-
|
|
1594
|
+
subResponses.push({
|
|
1595
|
+
id: sub.id,
|
|
1596
|
+
status: 'active',
|
|
1597
|
+
scopes: effectiveScopes,
|
|
1598
|
+
bootstrap: false,
|
|
1599
|
+
nextCursor,
|
|
1600
|
+
...(integrity ? { integrity } : {}),
|
|
1601
|
+
commits,
|
|
1602
|
+
});
|
|
1603
|
+
nextCursors.push(nextCursor);
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
682
1606
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
nextCursor = Math.max(nextCursor, r.commit_seq);
|
|
702
|
-
const rowKey = `${r.table}\u0000${r.row_id}`;
|
|
703
|
-
const change: SyncChange = {
|
|
704
|
-
table: r.table,
|
|
705
|
-
row_id: r.row_id,
|
|
706
|
-
op: r.op,
|
|
707
|
-
row_json: r.row_json,
|
|
708
|
-
row_version: r.row_version,
|
|
709
|
-
scopes: r.scopes,
|
|
710
|
-
};
|
|
1607
|
+
const commits: SyncCommit[] = [];
|
|
1608
|
+
|
|
1609
|
+
for (const item of incrementalItems) {
|
|
1610
|
+
nextCursor = Math.max(nextCursor, item.commitSeq);
|
|
1611
|
+
const seq = item.commitSeq;
|
|
1612
|
+
let commit = commits[commits.length - 1];
|
|
1613
|
+
if (!commit || commit.commitSeq !== seq) {
|
|
1614
|
+
commit = {
|
|
1615
|
+
commitSeq: seq,
|
|
1616
|
+
createdAt: item.createdAt,
|
|
1617
|
+
actorId: item.actorId,
|
|
1618
|
+
changes: [],
|
|
1619
|
+
};
|
|
1620
|
+
commits.push(commit);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
commit.changes.push(item.change);
|
|
1624
|
+
}
|
|
711
1625
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
change,
|
|
722
|
-
});
|
|
723
|
-
}
|
|
1626
|
+
const integrity = await createWireSubscriptionIntegrity({
|
|
1627
|
+
partitionId,
|
|
1628
|
+
subscriptionId: sub.id,
|
|
1629
|
+
previousRoot:
|
|
1630
|
+
typeof sub.verifiedRoot === 'string'
|
|
1631
|
+
? sub.verifiedRoot
|
|
1632
|
+
: SYNCULAR_COMMIT_GENESIS_ROOT,
|
|
1633
|
+
commits,
|
|
1634
|
+
});
|
|
724
1635
|
|
|
725
|
-
|
|
1636
|
+
nextCursor = Math.max(nextCursor, maxScannedCommitSeq);
|
|
726
1637
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1638
|
+
if (commits.length === 0) {
|
|
1639
|
+
subResponses.push({
|
|
1640
|
+
id: sub.id,
|
|
1641
|
+
status: 'active',
|
|
1642
|
+
scopes: effectiveScopes,
|
|
1643
|
+
bootstrap: false,
|
|
1644
|
+
nextCursor,
|
|
1645
|
+
commits: [],
|
|
1646
|
+
});
|
|
1647
|
+
nextCursors.push(nextCursor);
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
739
1650
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
changes: [item.change],
|
|
1651
|
+
subResponses.push({
|
|
1652
|
+
id: sub.id,
|
|
1653
|
+
status: 'active',
|
|
1654
|
+
scopes: effectiveScopes,
|
|
1655
|
+
bootstrap: false,
|
|
1656
|
+
nextCursor,
|
|
1657
|
+
...(integrity ? { integrity } : {}),
|
|
1658
|
+
commits,
|
|
749
1659
|
});
|
|
750
|
-
|
|
1660
|
+
nextCursors.push(nextCursor);
|
|
751
1661
|
}
|
|
752
|
-
lastCommit.changes.push(item.change);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
subResponses.push({
|
|
756
|
-
id: sub.id,
|
|
757
|
-
status: 'active',
|
|
758
|
-
scopes: effectiveScopes,
|
|
759
|
-
bootstrap: false,
|
|
760
|
-
nextCursor,
|
|
761
|
-
commits,
|
|
762
|
-
});
|
|
763
|
-
nextCursors.push(nextCursor);
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
1662
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (!commit) {
|
|
781
|
-
commit = {
|
|
782
|
-
commitSeq: seq,
|
|
783
|
-
createdAt: r.created_at,
|
|
784
|
-
actorId: r.actor_id,
|
|
785
|
-
changes: [],
|
|
1663
|
+
const effectiveScopes = mergeScopes(activeSubscriptions);
|
|
1664
|
+
const clientCursor =
|
|
1665
|
+
nextCursors.length > 0
|
|
1666
|
+
? Math.min(...nextCursors)
|
|
1667
|
+
: maxCommitSeq;
|
|
1668
|
+
|
|
1669
|
+
return {
|
|
1670
|
+
response: {
|
|
1671
|
+
ok: true as const,
|
|
1672
|
+
subscriptions: subResponses,
|
|
1673
|
+
},
|
|
1674
|
+
effectiveScopes,
|
|
1675
|
+
clientCursor,
|
|
786
1676
|
};
|
|
787
|
-
commitsBySeq.set(seq, commit);
|
|
788
|
-
commitSeqs.push(seq);
|
|
789
1677
|
}
|
|
1678
|
+
);
|
|
790
1679
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1680
|
+
const chunkStorage = args.chunkStorage;
|
|
1681
|
+
if (chunkStorage && pendingExternalChunkWrites.length > 0) {
|
|
1682
|
+
await runWithConcurrency(
|
|
1683
|
+
pendingExternalChunkWrites,
|
|
1684
|
+
4,
|
|
1685
|
+
async (pending) => {
|
|
1686
|
+
const cacheLookupStartedAt = Date.now();
|
|
1687
|
+
let chunkRef: SyncSnapshotChunkRef | null =
|
|
1688
|
+
await readSnapshotChunkRefByPageKey(db, {
|
|
1689
|
+
partitionId: pending.cacheLookup.partitionId,
|
|
1690
|
+
scopeKey: pending.cacheLookup.scopeKey,
|
|
1691
|
+
scope: pending.cacheLookup.scope,
|
|
1692
|
+
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
1693
|
+
rowCursor: pending.cacheLookup.rowCursor,
|
|
1694
|
+
rowLimit: pending.cacheLookup.rowLimit,
|
|
1695
|
+
encoding: snapshotChunkEncoding,
|
|
1696
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1697
|
+
});
|
|
1698
|
+
bootstrapTimings.chunkCacheLookupMs += Math.max(
|
|
1699
|
+
0,
|
|
1700
|
+
Date.now() - cacheLookupStartedAt
|
|
1701
|
+
);
|
|
801
1702
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1703
|
+
if (!chunkRef) {
|
|
1704
|
+
if (chunkStorage.storeChunkStream) {
|
|
1705
|
+
const {
|
|
1706
|
+
stream: bodyStream,
|
|
1707
|
+
byteLength,
|
|
1708
|
+
sha256,
|
|
1709
|
+
gzipMs,
|
|
1710
|
+
hashMs,
|
|
1711
|
+
} = await encodeCompressedSnapshotChunkToStream(
|
|
1712
|
+
pending.payloadParts,
|
|
1713
|
+
snapshotChunkGzipLevel
|
|
1714
|
+
);
|
|
1715
|
+
bootstrapTimings.chunkGzipMs += gzipMs;
|
|
1716
|
+
bootstrapTimings.chunkHashMs += hashMs;
|
|
1717
|
+
const chunkPersistStartedAt = Date.now();
|
|
1718
|
+
chunkRef = await chunkStorage.storeChunkStream({
|
|
1719
|
+
partitionId: pending.cacheLookup.partitionId,
|
|
1720
|
+
scopeKey: pending.cacheLookup.scopeKey,
|
|
1721
|
+
scope: pending.cacheLookup.scope,
|
|
1722
|
+
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
1723
|
+
rowCursor: pending.cacheLookup.rowCursor,
|
|
1724
|
+
rowLimit: pending.cacheLookup.rowLimit,
|
|
1725
|
+
nextRowCursor: pending.cacheLookup.nextRowCursor,
|
|
1726
|
+
isLastPage: pending.cacheLookup.isLastPage,
|
|
1727
|
+
encoding: snapshotChunkEncoding,
|
|
1728
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1729
|
+
sha256,
|
|
1730
|
+
byteLength,
|
|
1731
|
+
bodyStream,
|
|
1732
|
+
expiresAt: pending.expiresAt,
|
|
1733
|
+
});
|
|
1734
|
+
bootstrapTimings.chunkPersistMs += Math.max(
|
|
1735
|
+
0,
|
|
1736
|
+
Date.now() - chunkPersistStartedAt
|
|
1737
|
+
);
|
|
1738
|
+
} else {
|
|
1739
|
+
const encodedChunk = await encodeCompressedSnapshotChunk(
|
|
1740
|
+
pending.payloadParts,
|
|
1741
|
+
snapshotChunkGzipLevel
|
|
1742
|
+
);
|
|
1743
|
+
bootstrapTimings.chunkGzipMs += encodedChunk.gzipMs;
|
|
1744
|
+
bootstrapTimings.chunkHashMs += encodedChunk.hashMs;
|
|
1745
|
+
const chunkPersistStartedAt = Date.now();
|
|
1746
|
+
chunkRef = await chunkStorage.storeChunk({
|
|
1747
|
+
partitionId: pending.cacheLookup.partitionId,
|
|
1748
|
+
scopeKey: pending.cacheLookup.scopeKey,
|
|
1749
|
+
scope: pending.cacheLookup.scope,
|
|
1750
|
+
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
1751
|
+
rowCursor: pending.cacheLookup.rowCursor,
|
|
1752
|
+
rowLimit: pending.cacheLookup.rowLimit,
|
|
1753
|
+
nextRowCursor: pending.cacheLookup.nextRowCursor,
|
|
1754
|
+
isLastPage: pending.cacheLookup.isLastPage,
|
|
1755
|
+
encoding: snapshotChunkEncoding,
|
|
1756
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1757
|
+
sha256: encodedChunk.sha256,
|
|
1758
|
+
body: encodedChunk.body,
|
|
1759
|
+
expiresAt: pending.expiresAt,
|
|
1760
|
+
});
|
|
1761
|
+
bootstrapTimings.chunkPersistMs += Math.max(
|
|
1762
|
+
0,
|
|
1763
|
+
Date.now() - chunkPersistStartedAt
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
const chunk = toResponseChunkRef(chunkRef);
|
|
1769
|
+
pending.snapshot.chunks = [chunk];
|
|
1770
|
+
pending.snapshot.manifest =
|
|
1771
|
+
await createChunkedSnapshotManifest({
|
|
1772
|
+
table: pending.snapshot.table,
|
|
1773
|
+
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
1774
|
+
scopeKey: pending.cacheLookup.scopeKey,
|
|
1775
|
+
rowCursor: pending.cacheLookup.rowCursor,
|
|
1776
|
+
rowLimit: pending.cacheLookup.rowLimit,
|
|
1777
|
+
nextRowCursor: pending.cacheLookup.nextRowCursor,
|
|
1778
|
+
isFirstPage: pending.snapshot.isFirstPage,
|
|
1779
|
+
isLastPage: pending.cacheLookup.isLastPage,
|
|
1780
|
+
chunks: [chunk],
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
);
|
|
815
1784
|
}
|
|
816
1785
|
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1786
|
+
const durationMs = Math.max(0, Date.now() - startedAtMs);
|
|
1787
|
+
const stats = summarizePullResponse(result.response);
|
|
1788
|
+
|
|
1789
|
+
span.setAttribute('status', 'ok');
|
|
1790
|
+
span.setAttribute('duration_ms', durationMs);
|
|
1791
|
+
span.setAttribute('subscription_count', stats.subscriptionCount);
|
|
1792
|
+
span.setAttribute('commit_count', stats.commitCount);
|
|
1793
|
+
span.setAttribute('change_count', stats.changeCount);
|
|
1794
|
+
span.setAttribute('snapshot_page_count', stats.snapshotPageCount);
|
|
1795
|
+
span.setAttributes({
|
|
1796
|
+
bootstrap_snapshot_query_ms: bootstrapTimings.snapshotQueryMs,
|
|
1797
|
+
bootstrap_snapshot_binary_encode_ms:
|
|
1798
|
+
bootstrapTimings.binaryEncodeMs,
|
|
1799
|
+
bootstrap_chunk_cache_lookup_ms:
|
|
1800
|
+
bootstrapTimings.chunkCacheLookupMs,
|
|
1801
|
+
bootstrap_artifact_cache_lookup_ms:
|
|
1802
|
+
bootstrapTimings.artifactCacheLookupMs,
|
|
1803
|
+
bootstrap_chunk_gzip_ms: bootstrapTimings.chunkGzipMs,
|
|
1804
|
+
bootstrap_chunk_hash_ms: bootstrapTimings.chunkHashMs,
|
|
1805
|
+
bootstrap_chunk_persist_ms: bootstrapTimings.chunkPersistMs,
|
|
829
1806
|
});
|
|
830
|
-
|
|
831
|
-
}
|
|
1807
|
+
span.setStatus('ok');
|
|
832
1808
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
response: {
|
|
839
|
-
ok: true as const,
|
|
840
|
-
subscriptions: subResponses,
|
|
841
|
-
},
|
|
842
|
-
effectiveScopes,
|
|
843
|
-
clientCursor,
|
|
844
|
-
};
|
|
845
|
-
});
|
|
846
|
-
|
|
847
|
-
const chunkStorage = args.chunkStorage;
|
|
848
|
-
if (chunkStorage && pendingExternalChunkWrites.length > 0) {
|
|
849
|
-
for (const pending of pendingExternalChunkWrites) {
|
|
850
|
-
let chunkRef = await readSnapshotChunkRefByPageKey(db, {
|
|
851
|
-
partitionId: pending.cacheLookup.partitionId,
|
|
852
|
-
scopeKey: pending.cacheLookup.scopeKey,
|
|
853
|
-
scope: pending.cacheLookup.scope,
|
|
854
|
-
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
855
|
-
rowCursor: pending.cacheLookup.rowCursor,
|
|
856
|
-
rowLimit: pending.cacheLookup.rowLimit,
|
|
857
|
-
encoding: SYNC_SNAPSHOT_CHUNK_ENCODING,
|
|
858
|
-
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1809
|
+
recordPullMetrics({
|
|
1810
|
+
status: 'ok',
|
|
1811
|
+
dedupeRows,
|
|
1812
|
+
durationMs,
|
|
1813
|
+
stats,
|
|
859
1814
|
});
|
|
860
1815
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
encoding: SYNC_SNAPSHOT_CHUNK_ENCODING,
|
|
874
|
-
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
875
|
-
sha256,
|
|
876
|
-
byteLength,
|
|
877
|
-
bodyStream,
|
|
878
|
-
expiresAt: pending.expiresAt,
|
|
879
|
-
});
|
|
880
|
-
} else {
|
|
881
|
-
const compressedBody = await gzipBytes(pending.rowFramePayload);
|
|
882
|
-
chunkRef = await chunkStorage.storeChunk({
|
|
883
|
-
partitionId: pending.cacheLookup.partitionId,
|
|
884
|
-
scopeKey: pending.cacheLookup.scopeKey,
|
|
885
|
-
scope: pending.cacheLookup.scope,
|
|
886
|
-
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
887
|
-
rowCursor: pending.cacheLookup.rowCursor,
|
|
888
|
-
rowLimit: pending.cacheLookup.rowLimit,
|
|
889
|
-
encoding: SYNC_SNAPSHOT_CHUNK_ENCODING,
|
|
890
|
-
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
891
|
-
sha256,
|
|
892
|
-
body: compressedBody,
|
|
893
|
-
expiresAt: pending.expiresAt,
|
|
894
|
-
});
|
|
895
|
-
}
|
|
1816
|
+
return {
|
|
1817
|
+
...result,
|
|
1818
|
+
bootstrapTimings,
|
|
1819
|
+
};
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
if (
|
|
1822
|
+
error instanceof Error &&
|
|
1823
|
+
attemptIndex < MAX_PULL_TRANSACTION_RETRIES - 1 &&
|
|
1824
|
+
isSerializablePullError(error)
|
|
1825
|
+
) {
|
|
1826
|
+
await delay(PULL_TRANSACTION_RETRY_DELAY_MS * (attemptIndex + 1));
|
|
1827
|
+
continue;
|
|
896
1828
|
}
|
|
897
|
-
|
|
898
|
-
pending.snapshot.chunks = [chunkRef];
|
|
1829
|
+
throw error;
|
|
899
1830
|
}
|
|
900
1831
|
}
|
|
901
1832
|
|
|
902
|
-
|
|
903
|
-
const stats = summarizePullResponse(result.response);
|
|
904
|
-
|
|
905
|
-
span.setAttribute('status', 'ok');
|
|
906
|
-
span.setAttribute('duration_ms', durationMs);
|
|
907
|
-
span.setAttribute('subscription_count', stats.subscriptionCount);
|
|
908
|
-
span.setAttribute('commit_count', stats.commitCount);
|
|
909
|
-
span.setAttribute('change_count', stats.changeCount);
|
|
910
|
-
span.setAttribute('snapshot_page_count', stats.snapshotPageCount);
|
|
911
|
-
span.setStatus('ok');
|
|
912
|
-
|
|
913
|
-
recordPullMetrics({
|
|
914
|
-
status: 'ok',
|
|
915
|
-
dedupeRows,
|
|
916
|
-
durationMs,
|
|
917
|
-
stats,
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
return result;
|
|
1833
|
+
throw new Error('Pull transaction retry loop exhausted unexpectedly');
|
|
921
1834
|
} catch (error) {
|
|
922
1835
|
const durationMs = Math.max(0, Date.now() - startedAtMs);
|
|
923
1836
|
|