@syncular/server 0.0.6-95 → 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/dist/pull.js
CHANGED
|
@@ -1,38 +1,200 @@
|
|
|
1
|
-
import { captureSyncException, countSyncMetric,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { bytesToReadableStream, captureSyncException, concatByteChunks, countSyncMetric, createSnapshotManifest, distributionSyncMetric, encodeBinarySnapshotTable, gzipBytes, randomId, SYNC_SCOPED_SNAPSHOT_ARTIFACT_KIND_SQLITE_V1, SYNC_SNAPSHOT_CHUNK_COMPRESSION, SYNC_SNAPSHOT_CHUNK_ENCODING, sha256Hex, snapshotScopeDigestFromCacheKey, startSyncSpan, } from '@syncular/core';
|
|
2
|
+
import { createWireSubscriptionIntegrity, SYNCULAR_COMMIT_GENESIS_ROOT, } from './commit-integrity';
|
|
3
|
+
import { getServerBootstrapOrderFor, } from './handlers/collection';
|
|
4
|
+
import { EXTERNAL_CLIENT_ID } from './notify';
|
|
5
|
+
import { sortServerPullPlugins, } from './plugins/types';
|
|
6
|
+
import { createScopedSnapshotArtifactScopeCacheKey, readBestScopedSnapshotArtifactRefForPageCapacity, } from './snapshot-artifacts';
|
|
7
|
+
import { createSnapshotChunkScopeCacheKey, insertSnapshotChunk, readSnapshotChunkRefByPageKey, } from './snapshot-chunks';
|
|
8
|
+
import { createMemoryScopeCache, } from './subscriptions/cache';
|
|
9
|
+
import { resolveEffectiveScopesForSubscriptions } from './subscriptions/resolve';
|
|
7
10
|
const defaultScopeCache = createMemoryScopeCache();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
const DEFAULT_MAX_BINARY_SNAPSHOT_BUNDLE_ROWS = 50_000;
|
|
12
|
+
const DEFAULT_SNAPSHOT_CHUNK_GZIP_LEVEL = 1;
|
|
13
|
+
const MAX_PULL_TRANSACTION_RETRIES = 2;
|
|
14
|
+
const PULL_TRANSACTION_RETRY_DELAY_MS = 15;
|
|
15
|
+
function toResponseChunkRef(ref) {
|
|
16
|
+
return {
|
|
17
|
+
id: ref.id,
|
|
18
|
+
byteLength: ref.byteLength,
|
|
19
|
+
sha256: ref.sha256,
|
|
20
|
+
encoding: ref.encoding,
|
|
21
|
+
compression: ref.compression,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function createChunkedSnapshotManifest(args) {
|
|
25
|
+
return createSnapshotManifest({
|
|
26
|
+
version: 1,
|
|
27
|
+
table: args.table,
|
|
28
|
+
asOfCommitSeq: args.asOfCommitSeq,
|
|
29
|
+
scopeDigest: snapshotScopeDigestFromCacheKey(args.scopeKey),
|
|
30
|
+
rowCursor: args.rowCursor,
|
|
31
|
+
rowLimit: args.rowLimit,
|
|
32
|
+
nextRowCursor: args.nextRowCursor,
|
|
33
|
+
isFirstPage: args.isFirstPage,
|
|
34
|
+
isLastPage: args.isLastPage,
|
|
35
|
+
chunks: args.chunks.map(toResponseChunkRef),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function normalizeFeatureSet(features) {
|
|
39
|
+
return Array.from(new Set(features ?? [])).sort();
|
|
40
|
+
}
|
|
41
|
+
function resolveSnapshotArtifactSelection(request, clientSchemaVersion) {
|
|
42
|
+
if (!request)
|
|
43
|
+
return null;
|
|
44
|
+
if (!request.artifactKinds.includes(SYNC_SCOPED_SNAPSHOT_ARTIFACT_KIND_SQLITE_V1)) {
|
|
45
|
+
return null;
|
|
11
46
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
total += chunk.length;
|
|
47
|
+
if (!request.compressions?.includes(SYNC_SNAPSHOT_CHUNK_COMPRESSION)) {
|
|
48
|
+
return null;
|
|
15
49
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
50
|
+
return {
|
|
51
|
+
artifactKind: SYNC_SCOPED_SNAPSHOT_ARTIFACT_KIND_SQLITE_V1,
|
|
52
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
53
|
+
schemaVersion: String(clientSchemaVersion),
|
|
54
|
+
featureSet: normalizeFeatureSet(request.featureSet),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function resolveSnapshotBinaryColumns(handler, schemaVersion) {
|
|
58
|
+
const versioned = handler.snapshotBinaryColumnsForVersion?.(schemaVersion);
|
|
59
|
+
return versioned === undefined
|
|
60
|
+
? handler.snapshotBinaryColumns
|
|
61
|
+
: (versioned ?? undefined);
|
|
62
|
+
}
|
|
63
|
+
function resolveSnapshotBinaryEncoder(handler, schemaVersion) {
|
|
64
|
+
const versioned = handler.snapshotBinaryEncoderForVersion?.(schemaVersion);
|
|
65
|
+
return versioned === undefined
|
|
66
|
+
? handler.snapshotBinaryEncoder
|
|
67
|
+
: (versioned ?? undefined);
|
|
68
|
+
}
|
|
69
|
+
function createPullBootstrapTimings() {
|
|
70
|
+
return {
|
|
71
|
+
snapshotQueryMs: 0,
|
|
72
|
+
binaryEncodeMs: 0,
|
|
73
|
+
chunkCacheLookupMs: 0,
|
|
74
|
+
artifactCacheLookupMs: 0,
|
|
75
|
+
chunkGzipMs: 0,
|
|
76
|
+
chunkHashMs: 0,
|
|
77
|
+
chunkPersistMs: 0,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function resolveBinarySnapshotBundleRowLimit(args) {
|
|
81
|
+
const pageSize = Math.max(1, args.limitSnapshotRows);
|
|
82
|
+
const pagesRemaining = Math.max(1, args.pagesRemaining);
|
|
83
|
+
const maxBundlePages = Math.max(1, Math.ceil(DEFAULT_MAX_BINARY_SNAPSHOT_BUNDLE_ROWS / pageSize));
|
|
84
|
+
return pageSize * Math.min(pagesRemaining, maxBundlePages);
|
|
85
|
+
}
|
|
86
|
+
async function gzipByteChunks(chunks, gzipLevel) {
|
|
87
|
+
return gzipBytes(concatByteChunks(chunks), {
|
|
88
|
+
level: gzipLevel,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async function encodeCompressedSnapshotChunk(chunks, gzipLevel) {
|
|
92
|
+
const gzipStartedAt = Date.now();
|
|
93
|
+
const body = await gzipByteChunks(chunks, gzipLevel);
|
|
94
|
+
const gzipMs = Math.max(0, Date.now() - gzipStartedAt);
|
|
95
|
+
const hashStartedAt = Date.now();
|
|
96
|
+
const sha256 = await sha256Hex(body);
|
|
97
|
+
const hashMs = Math.max(0, Date.now() - hashStartedAt);
|
|
98
|
+
return { body, sha256, gzipMs, hashMs };
|
|
99
|
+
}
|
|
100
|
+
async function encodeCompressedSnapshotChunkToStream(chunks, gzipLevel) {
|
|
101
|
+
const encoded = await encodeCompressedSnapshotChunk(chunks, gzipLevel);
|
|
102
|
+
return {
|
|
103
|
+
stream: bytesToReadableStream(encoded.body),
|
|
104
|
+
byteLength: encoded.body.length,
|
|
105
|
+
sha256: encoded.sha256,
|
|
106
|
+
gzipMs: encoded.gzipMs,
|
|
107
|
+
hashMs: encoded.hashMs,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function encodeBinarySnapshotRows(table, rows, columns) {
|
|
111
|
+
const recordRows = rows.map((row) => toSnapshotRecordRow(table, row));
|
|
112
|
+
return encodeBinarySnapshotTable({
|
|
113
|
+
table,
|
|
114
|
+
columns: columns ?? inferBinarySnapshotColumns(recordRows),
|
|
115
|
+
rows: recordRows,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function toSnapshotRecordRow(table, row) {
|
|
119
|
+
if (row == null ||
|
|
120
|
+
typeof row !== 'object' ||
|
|
121
|
+
Array.isArray(row) ||
|
|
122
|
+
row instanceof Uint8Array ||
|
|
123
|
+
row instanceof ArrayBuffer) {
|
|
124
|
+
throw new Error(`Cannot encode binary snapshot for table ${table}: snapshot rows must be objects`);
|
|
21
125
|
}
|
|
22
|
-
return
|
|
126
|
+
return row;
|
|
23
127
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
128
|
+
function inferBinarySnapshotColumns(rows) {
|
|
129
|
+
const columns = [];
|
|
130
|
+
const columnsByName = new Map();
|
|
131
|
+
for (const row of rows) {
|
|
132
|
+
for (const name in row) {
|
|
133
|
+
if (!Object.hasOwn(row, name))
|
|
134
|
+
continue;
|
|
135
|
+
const value = row[name];
|
|
136
|
+
let column = columnsByName.get(name);
|
|
137
|
+
if (!column) {
|
|
138
|
+
column = { name, type: null, nullable: false, presentCount: 0 };
|
|
139
|
+
columnsByName.set(name, column);
|
|
140
|
+
columns.push(column);
|
|
141
|
+
}
|
|
142
|
+
column.presentCount += 1;
|
|
143
|
+
if (value == null) {
|
|
144
|
+
column.nullable = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
column.type = mergeBinarySnapshotColumnTypes(column.type, inferBinarySnapshotColumnType(value));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return columns.map((column) => ({
|
|
151
|
+
name: column.name,
|
|
152
|
+
type: column.type ?? 'json',
|
|
153
|
+
...(column.nullable || column.presentCount < rows.length
|
|
154
|
+
? { nullable: true }
|
|
155
|
+
: {}),
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
function inferBinarySnapshotColumnType(value) {
|
|
159
|
+
if (typeof value === 'string')
|
|
160
|
+
return 'string';
|
|
161
|
+
if (typeof value === 'boolean')
|
|
162
|
+
return 'boolean';
|
|
163
|
+
if (typeof value === 'bigint')
|
|
164
|
+
return 'integer';
|
|
165
|
+
if (typeof value === 'number') {
|
|
166
|
+
return Number.isSafeInteger(value) ? 'integer' : 'float';
|
|
167
|
+
}
|
|
168
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
169
|
+
return 'bytes';
|
|
170
|
+
}
|
|
171
|
+
return 'json';
|
|
172
|
+
}
|
|
173
|
+
function mergeBinarySnapshotColumnTypes(current, next) {
|
|
174
|
+
if (!current || current === next)
|
|
175
|
+
return next;
|
|
176
|
+
if ((current === 'integer' && next === 'float') ||
|
|
177
|
+
(current === 'float' && next === 'integer')) {
|
|
178
|
+
return 'float';
|
|
179
|
+
}
|
|
180
|
+
return 'json';
|
|
181
|
+
}
|
|
182
|
+
async function runWithConcurrency(items, concurrency, worker) {
|
|
183
|
+
if (items.length === 0)
|
|
184
|
+
return;
|
|
185
|
+
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
186
|
+
let nextIndex = 0;
|
|
187
|
+
async function runWorker() {
|
|
188
|
+
while (nextIndex < items.length) {
|
|
189
|
+
const index = nextIndex;
|
|
190
|
+
nextIndex += 1;
|
|
191
|
+
const item = items[index];
|
|
192
|
+
if (item === undefined)
|
|
193
|
+
continue;
|
|
194
|
+
await worker(item);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
|
|
36
198
|
}
|
|
37
199
|
/**
|
|
38
200
|
* Sanitize a numeric limit parameter with bounds checking.
|
|
@@ -45,6 +207,22 @@ function sanitizeLimit(value, defaultValue, min, max) {
|
|
|
45
207
|
return defaultValue;
|
|
46
208
|
return Math.max(min, Math.min(max, value));
|
|
47
209
|
}
|
|
210
|
+
function sanitizeGzipLevel(value) {
|
|
211
|
+
if (value === undefined || value === null || !Number.isFinite(value)) {
|
|
212
|
+
return DEFAULT_SNAPSHOT_CHUNK_GZIP_LEVEL;
|
|
213
|
+
}
|
|
214
|
+
return Math.max(0, Math.min(9, Math.trunc(value)));
|
|
215
|
+
}
|
|
216
|
+
function isSerializablePullError(error) {
|
|
217
|
+
const withCode = error;
|
|
218
|
+
return (withCode.code === '40001' ||
|
|
219
|
+
error.message.toLowerCase().includes('could not serialize access'));
|
|
220
|
+
}
|
|
221
|
+
async function delay(ms) {
|
|
222
|
+
await new Promise((resolve) => {
|
|
223
|
+
setTimeout(resolve, ms);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
48
226
|
/**
|
|
49
227
|
* Merge all scope values into a flat ScopeValues for cursor tracking.
|
|
50
228
|
*/
|
|
@@ -68,6 +246,45 @@ function mergeScopes(subscriptions) {
|
|
|
68
246
|
}
|
|
69
247
|
return merged;
|
|
70
248
|
}
|
|
249
|
+
function assertPullChangeIdentityUnchanged(source, before, after) {
|
|
250
|
+
if (before.table !== after.table) {
|
|
251
|
+
throw new Error(`${source} cannot change change.table (${before.table} -> ${after.table})`);
|
|
252
|
+
}
|
|
253
|
+
if (before.row_id !== after.row_id) {
|
|
254
|
+
throw new Error(`${source} cannot change change.row_id (${before.row_id} -> ${after.row_id})`);
|
|
255
|
+
}
|
|
256
|
+
if (before.op !== after.op) {
|
|
257
|
+
throw new Error(`${source} cannot change change.op (${before.op} -> ${after.op})`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function projectPullChangeForClientSchema(args) {
|
|
261
|
+
const projected = args.handler.projectChangeForVersion
|
|
262
|
+
? args.handler.projectChangeForVersion(args.change, args.schemaVersion)
|
|
263
|
+
: args.change;
|
|
264
|
+
assertPullChangeIdentityUnchanged('Server table handler projectChangeForVersion', args.change, projected);
|
|
265
|
+
return projected;
|
|
266
|
+
}
|
|
267
|
+
async function transformPullChanges(args) {
|
|
268
|
+
let changes = [...args.changes];
|
|
269
|
+
for (const plugin of args.plugins) {
|
|
270
|
+
if (!plugin.transformPullChanges)
|
|
271
|
+
continue;
|
|
272
|
+
const nextChanges = await plugin.transformPullChanges({
|
|
273
|
+
ctx: args.ctx,
|
|
274
|
+
tableHandler: args.tableHandler,
|
|
275
|
+
subscription: args.subscription,
|
|
276
|
+
changes,
|
|
277
|
+
});
|
|
278
|
+
if (nextChanges.length !== changes.length) {
|
|
279
|
+
throw new Error(`Server pull plugin "${plugin.name}" cannot change pull change count (${changes.length} -> ${nextChanges.length})`);
|
|
280
|
+
}
|
|
281
|
+
for (let i = 0; i < changes.length; i += 1) {
|
|
282
|
+
assertPullChangeIdentityUnchanged(`Server pull plugin "${plugin.name}"`, changes[i], nextChanges[i]);
|
|
283
|
+
}
|
|
284
|
+
changes = [...nextChanges];
|
|
285
|
+
}
|
|
286
|
+
return changes;
|
|
287
|
+
}
|
|
71
288
|
function summarizePullResponse(response) {
|
|
72
289
|
const subscriptions = response.subscriptions ?? [];
|
|
73
290
|
let activeSubscriptionCount = 0;
|
|
@@ -126,29 +343,49 @@ function recordPullMetrics(args) {
|
|
|
126
343
|
});
|
|
127
344
|
distributionSyncMetric('sync.server.pull.snapshot_pages', stats.snapshotPageCount, { attributes });
|
|
128
345
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
346
|
+
async function readLatestExternalCommitByTable(trx, args) {
|
|
347
|
+
const tableNames = Array.from(new Set(args.tables.filter((table) => typeof table === 'string')));
|
|
348
|
+
const latestByTable = new Map();
|
|
349
|
+
if (tableNames.length === 0) {
|
|
350
|
+
return latestByTable;
|
|
351
|
+
}
|
|
134
352
|
const executor = trx;
|
|
135
353
|
const rows = await executor
|
|
136
|
-
.selectFrom('
|
|
137
|
-
.
|
|
138
|
-
.
|
|
139
|
-
.
|
|
140
|
-
.
|
|
141
|
-
.
|
|
354
|
+
.selectFrom('sync_table_commits as tc')
|
|
355
|
+
.innerJoin('sync_commits as cm', (join) => join
|
|
356
|
+
.onRef('cm.commit_seq', '=', 'tc.commit_seq')
|
|
357
|
+
.onRef('cm.partition_id', '=', 'tc.partition_id'))
|
|
358
|
+
.select(['tc.table as table'])
|
|
359
|
+
.select((eb) => eb.fn.max('tc.commit_seq').as('latest_commit_seq'))
|
|
360
|
+
.where('tc.partition_id', '=', args.partitionId)
|
|
361
|
+
.where('cm.client_id', '=', EXTERNAL_CLIENT_ID)
|
|
362
|
+
.where('cm.change_count', '=', 0)
|
|
363
|
+
.where('tc.commit_seq', '>', args.afterCursor)
|
|
364
|
+
.where('tc.table', 'in', tableNames)
|
|
365
|
+
.groupBy('tc.table')
|
|
142
366
|
.execute();
|
|
143
|
-
|
|
144
|
-
commitSeq
|
|
145
|
-
|
|
146
|
-
|
|
367
|
+
for (const row of rows) {
|
|
368
|
+
const commitSeq = Number(row.latest_commit_seq ?? -1);
|
|
369
|
+
if (!Number.isFinite(commitSeq) || commitSeq < 0)
|
|
370
|
+
continue;
|
|
371
|
+
latestByTable.set(row.table, commitSeq);
|
|
372
|
+
}
|
|
373
|
+
return latestByTable;
|
|
147
374
|
}
|
|
148
375
|
export async function pull(args) {
|
|
149
376
|
const { request, dialect } = args;
|
|
150
377
|
const db = args.db;
|
|
378
|
+
const pullPlugins = sortServerPullPlugins(args.plugins);
|
|
151
379
|
const partitionId = args.auth.partitionId ?? 'default';
|
|
380
|
+
const snapshotChunkGzipLevel = sanitizeGzipLevel(args.snapshotChunkGzipLevel);
|
|
381
|
+
const clientSchemaVersion = request.schemaVersion;
|
|
382
|
+
if (!Number.isInteger(clientSchemaVersion) || clientSchemaVersion < 1) {
|
|
383
|
+
throw new Error('Pull request schemaVersion must be a positive integer');
|
|
384
|
+
}
|
|
385
|
+
const snapshotChunkCacheSchemaVersion = args.snapshotChunkCacheSchemaVersion === null ||
|
|
386
|
+
args.snapshotChunkCacheSchemaVersion === undefined
|
|
387
|
+
? clientSchemaVersion
|
|
388
|
+
: `${clientSchemaVersion}:${args.snapshotChunkCacheSchemaVersion}`;
|
|
152
389
|
const requestedSubscriptionCount = Array.isArray(request.subscriptions)
|
|
153
390
|
? request.subscriptions.length
|
|
154
391
|
: 0;
|
|
@@ -163,11 +400,13 @@ export async function pull(args) {
|
|
|
163
400
|
}, async (span) => {
|
|
164
401
|
try {
|
|
165
402
|
// Validate and sanitize request limits
|
|
166
|
-
const limitCommits = sanitizeLimit(request.limitCommits,
|
|
167
|
-
const limitSnapshotRows = sanitizeLimit(request.limitSnapshotRows, 1000, 1,
|
|
403
|
+
const limitCommits = sanitizeLimit(request.limitCommits, 1000, 1, 1000);
|
|
404
|
+
const limitSnapshotRows = sanitizeLimit(request.limitSnapshotRows, 1000, 1, 50000);
|
|
168
405
|
const maxSnapshotPages = sanitizeLimit(request.maxSnapshotPages, 4, 1, 50);
|
|
169
406
|
const dedupeRows = request.dedupeRows === true;
|
|
170
|
-
const
|
|
407
|
+
const snapshotChunkEncoding = SYNC_SNAPSHOT_CHUNK_ENCODING;
|
|
408
|
+
const snapshotArtifactSelection = resolveSnapshotArtifactSelection(request.snapshotArtifacts, clientSchemaVersion);
|
|
409
|
+
const snapshotArtifactSchemaVersion = snapshotArtifactSelection?.schemaVersion ?? null;
|
|
171
410
|
// Resolve effective scopes for each subscription
|
|
172
411
|
const resolved = await resolveEffectiveScopesForSubscriptions({
|
|
173
412
|
db,
|
|
@@ -176,480 +415,781 @@ export async function pull(args) {
|
|
|
176
415
|
handlers: args.handlers,
|
|
177
416
|
scopeCache: args.scopeCache ?? defaultScopeCache,
|
|
178
417
|
});
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const subResponses = [];
|
|
188
|
-
const activeSubscriptions = [];
|
|
189
|
-
const nextCursors = [];
|
|
190
|
-
// Detect external data changes (synthetic commits from notifyExternalDataChange)
|
|
191
|
-
// Compute minimum cursor across all active subscriptions to scope the query.
|
|
192
|
-
let minSubCursor = Number.MAX_SAFE_INTEGER;
|
|
193
|
-
for (const sub of resolved) {
|
|
194
|
-
if (sub.status === 'revoked' ||
|
|
195
|
-
Object.keys(sub.scopes).length === 0)
|
|
196
|
-
continue;
|
|
197
|
-
const cursor = Math.max(-1, sub.cursor ?? -1);
|
|
198
|
-
if (cursor >= 0 && cursor < minSubCursor) {
|
|
199
|
-
minSubCursor = cursor;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
const externalDataChanges = minSubCursor < Number.MAX_SAFE_INTEGER && minSubCursor >= 0
|
|
203
|
-
? await readExternalDataChanges(trx, dialect, {
|
|
204
|
-
partitionId,
|
|
205
|
-
afterCursor: minSubCursor,
|
|
206
|
-
})
|
|
207
|
-
: [];
|
|
208
|
-
const maxExternalCommitByTable = new Map();
|
|
209
|
-
for (const change of externalDataChanges) {
|
|
210
|
-
for (const table of change.tables) {
|
|
211
|
-
const previous = maxExternalCommitByTable.get(table) ?? -1;
|
|
212
|
-
if (change.commitSeq > previous) {
|
|
213
|
-
maxExternalCommitByTable.set(table, change.commitSeq);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
for (const sub of resolved) {
|
|
218
|
-
const cursor = Math.max(-1, sub.cursor ?? -1);
|
|
219
|
-
// Validate table handler exists (throws if not registered)
|
|
220
|
-
getServerHandlerOrThrow(args.handlers, sub.table);
|
|
221
|
-
if (sub.status === 'revoked' ||
|
|
222
|
-
Object.keys(sub.scopes).length === 0) {
|
|
223
|
-
subResponses.push({
|
|
224
|
-
id: sub.id,
|
|
225
|
-
status: 'revoked',
|
|
226
|
-
scopes: {},
|
|
227
|
-
bootstrap: false,
|
|
228
|
-
nextCursor: cursor,
|
|
229
|
-
commits: [],
|
|
418
|
+
for (let attemptIndex = 0; attemptIndex < MAX_PULL_TRANSACTION_RETRIES; attemptIndex += 1) {
|
|
419
|
+
const pendingExternalChunkWrites = [];
|
|
420
|
+
const bootstrapTimings = createPullBootstrapTimings();
|
|
421
|
+
try {
|
|
422
|
+
const result = await dialect.executeInTransaction(db, async (trx) => {
|
|
423
|
+
await dialect.setRepeatableRead(trx);
|
|
424
|
+
const maxCommitSeq = await dialect.readMaxCommitSeq(trx, {
|
|
425
|
+
partitionId,
|
|
230
426
|
});
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
cursor
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const requestedState = sub.bootstrapState ?? null;
|
|
251
|
-
const state = requestedState &&
|
|
252
|
-
typeof requestedState.asOfCommitSeq === 'number' &&
|
|
253
|
-
Array.isArray(requestedState.tables) &&
|
|
254
|
-
typeof requestedState.tableIndex === 'number'
|
|
255
|
-
? requestedState
|
|
256
|
-
: initState;
|
|
257
|
-
// If the bootstrap state's asOfCommitSeq is no longer catch-up-able, restart bootstrap.
|
|
258
|
-
const effectiveState = state.asOfCommitSeq < minCommitSeq - 1 ? initState : state;
|
|
259
|
-
const tableName = effectiveState.tables[effectiveState.tableIndex];
|
|
260
|
-
// No tables (or ran past the end): treat bootstrap as complete.
|
|
261
|
-
if (!tableName) {
|
|
262
|
-
subResponses.push({
|
|
263
|
-
id: sub.id,
|
|
264
|
-
status: 'active',
|
|
265
|
-
scopes: effectiveScopes,
|
|
266
|
-
bootstrap: true,
|
|
267
|
-
bootstrapState: null,
|
|
268
|
-
nextCursor: effectiveState.asOfCommitSeq,
|
|
269
|
-
commits: [],
|
|
270
|
-
snapshots: [],
|
|
271
|
-
});
|
|
272
|
-
nextCursors.push(effectiveState.asOfCommitSeq);
|
|
273
|
-
continue;
|
|
427
|
+
const minCommitSeq = await dialect.readMinCommitSeq(trx, {
|
|
428
|
+
partitionId,
|
|
429
|
+
});
|
|
430
|
+
const subResponses = [];
|
|
431
|
+
const activeSubscriptions = [];
|
|
432
|
+
const nextCursors = [];
|
|
433
|
+
// Detect external data changes (synthetic commits from notifyExternalDataChange)
|
|
434
|
+
// Compute minimum cursor across all active subscriptions to scope the query.
|
|
435
|
+
let minSubCursor = Number.MAX_SAFE_INTEGER;
|
|
436
|
+
const activeTables = new Set();
|
|
437
|
+
for (const sub of resolved) {
|
|
438
|
+
if (sub.status === 'revoked' ||
|
|
439
|
+
Object.keys(sub.scopes).length === 0)
|
|
440
|
+
continue;
|
|
441
|
+
activeTables.add(sub.table);
|
|
442
|
+
const cursor = Math.max(-1, sub.cursor ?? -1);
|
|
443
|
+
if (cursor >= 0 && cursor < minSubCursor) {
|
|
444
|
+
minSubCursor = cursor;
|
|
445
|
+
}
|
|
274
446
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
const cacheKey = `${partitionId}:${await scopesToCacheKey(effectiveScopes)}`;
|
|
278
|
-
const flushSnapshotBundle = async (bundle) => {
|
|
279
|
-
const nowIso = new Date().toISOString();
|
|
280
|
-
const bundleRowLimit = Math.max(1, limitSnapshotRows * bundle.pageCount);
|
|
281
|
-
const cached = await readSnapshotChunkRefByPageKey(trx, {
|
|
447
|
+
const maxExternalCommitByTable = minSubCursor < Number.MAX_SAFE_INTEGER && minSubCursor >= 0
|
|
448
|
+
? await readLatestExternalCommitByTable(trx, {
|
|
282
449
|
partitionId,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
450
|
+
afterCursor: minSubCursor,
|
|
451
|
+
tables: Array.from(activeTables),
|
|
452
|
+
})
|
|
453
|
+
: new Map();
|
|
454
|
+
for (const sub of resolved) {
|
|
455
|
+
const cursor = Math.max(-1, sub.cursor ?? -1);
|
|
456
|
+
// Validate table handler exists (throws if not registered)
|
|
457
|
+
if (!args.handlers.byTable.has(sub.table)) {
|
|
458
|
+
throw new Error(`Unknown table: ${sub.table}`);
|
|
459
|
+
}
|
|
460
|
+
if (sub.status === 'revoked' ||
|
|
461
|
+
Object.keys(sub.scopes).length === 0) {
|
|
462
|
+
subResponses.push({
|
|
463
|
+
id: sub.id,
|
|
464
|
+
status: 'revoked',
|
|
465
|
+
scopes: {},
|
|
466
|
+
bootstrap: false,
|
|
467
|
+
nextCursor: cursor,
|
|
468
|
+
commits: [],
|
|
469
|
+
});
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const effectiveScopes = sub.scopes;
|
|
473
|
+
activeSubscriptions.push({ scopes: effectiveScopes });
|
|
474
|
+
const latestExternalCommitForTable = maxExternalCommitByTable.get(sub.table);
|
|
475
|
+
const needsBootstrap = sub.bootstrapState != null ||
|
|
476
|
+
cursor < 0 ||
|
|
477
|
+
cursor > maxCommitSeq ||
|
|
478
|
+
(minCommitSeq > 0 && cursor < minCommitSeq - 1) ||
|
|
479
|
+
(latestExternalCommitForTable !== undefined &&
|
|
480
|
+
latestExternalCommitForTable > cursor);
|
|
481
|
+
if (needsBootstrap) {
|
|
482
|
+
const tables = getServerBootstrapOrderFor(args.handlers, sub.table).map((handler) => handler.table);
|
|
483
|
+
const initState = {
|
|
484
|
+
asOfCommitSeq: maxCommitSeq,
|
|
485
|
+
tables,
|
|
486
|
+
tableIndex: 0,
|
|
487
|
+
rowCursor: null,
|
|
488
|
+
};
|
|
489
|
+
const requestedState = sub.bootstrapState ?? null;
|
|
490
|
+
const state = requestedState &&
|
|
491
|
+
typeof requestedState.asOfCommitSeq === 'number' &&
|
|
492
|
+
Array.isArray(requestedState.tables) &&
|
|
493
|
+
typeof requestedState.tableIndex === 'number'
|
|
494
|
+
? requestedState
|
|
495
|
+
: initState;
|
|
496
|
+
// If the bootstrap state's asOfCommitSeq is no longer catch-up-able, restart bootstrap.
|
|
497
|
+
const effectiveState = state.asOfCommitSeq < minCommitSeq - 1
|
|
498
|
+
? initState
|
|
499
|
+
: state;
|
|
500
|
+
const tableName = effectiveState.tables[effectiveState.tableIndex];
|
|
501
|
+
// No tables (or ran past the end): treat bootstrap as complete.
|
|
502
|
+
if (!tableName) {
|
|
503
|
+
subResponses.push({
|
|
504
|
+
id: sub.id,
|
|
505
|
+
status: 'active',
|
|
506
|
+
scopes: effectiveScopes,
|
|
507
|
+
bootstrap: true,
|
|
508
|
+
bootstrapState: null,
|
|
509
|
+
nextCursor: effectiveState.asOfCommitSeq,
|
|
510
|
+
commits: [],
|
|
511
|
+
snapshots: [],
|
|
512
|
+
});
|
|
513
|
+
nextCursors.push(effectiveState.asOfCommitSeq);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const snapshots = [];
|
|
517
|
+
let nextState = effectiveState;
|
|
518
|
+
const cacheKey = await createSnapshotChunkScopeCacheKey({
|
|
519
|
+
partitionId,
|
|
520
|
+
scopes: effectiveScopes,
|
|
521
|
+
schemaVersion: snapshotChunkCacheSchemaVersion,
|
|
522
|
+
encoding: snapshotChunkEncoding,
|
|
523
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
524
|
+
gzipLevel: snapshotChunkGzipLevel,
|
|
525
|
+
});
|
|
526
|
+
const artifactScopeKey = snapshotArtifactSelection && snapshotArtifactSchemaVersion
|
|
527
|
+
? await createScopedSnapshotArtifactScopeCacheKey({
|
|
528
|
+
partitionId,
|
|
529
|
+
subscriptionId: sub.id,
|
|
530
|
+
scopes: effectiveScopes,
|
|
531
|
+
schemaVersion: snapshotArtifactSchemaVersion,
|
|
532
|
+
artifactKind: snapshotArtifactSelection.artifactKind,
|
|
533
|
+
compression: snapshotArtifactSelection.compression,
|
|
534
|
+
features: snapshotArtifactSelection.featureSet,
|
|
535
|
+
})
|
|
536
|
+
: null;
|
|
537
|
+
const createSnapshotBundle = (table, tableIndex, rowCursor, ttlMs, binaryColumns, binaryEncoder) => {
|
|
538
|
+
return {
|
|
539
|
+
table,
|
|
540
|
+
tableIndex,
|
|
541
|
+
startCursor: rowCursor,
|
|
542
|
+
nextRowCursor: null,
|
|
543
|
+
isFirstPage: rowCursor == null,
|
|
544
|
+
isLastPage: false,
|
|
545
|
+
pageCount: 0,
|
|
546
|
+
cacheRowLimit: null,
|
|
547
|
+
ttlMs,
|
|
548
|
+
binaryColumns,
|
|
549
|
+
binaryEncoder,
|
|
550
|
+
binaryRows: [],
|
|
303
551
|
};
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
552
|
+
};
|
|
553
|
+
const snapshotBootstrapStateAfter = (args) => {
|
|
554
|
+
if (!args.isLastPage) {
|
|
555
|
+
return {
|
|
556
|
+
...effectiveState,
|
|
557
|
+
tableIndex: args.tableIndex,
|
|
558
|
+
rowCursor: args.nextRowCursor,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
if (args.tableIndex + 1 < effectiveState.tables.length) {
|
|
562
|
+
return {
|
|
563
|
+
...effectiveState,
|
|
564
|
+
tableIndex: args.tableIndex + 1,
|
|
565
|
+
rowCursor: null,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return null;
|
|
569
|
+
};
|
|
570
|
+
const encodeSnapshotBundlePayload = (bundle) => {
|
|
571
|
+
const encodeStartedAt = Date.now();
|
|
572
|
+
const payload = bundle.binaryEncoder
|
|
573
|
+
? bundle.binaryEncoder(bundle.binaryRows)
|
|
574
|
+
: encodeBinarySnapshotRows(bundle.table, bundle.binaryRows, bundle.binaryColumns);
|
|
575
|
+
bootstrapTimings.binaryEncodeMs += Math.max(0, Date.now() - encodeStartedAt);
|
|
576
|
+
return [payload];
|
|
577
|
+
};
|
|
578
|
+
const flushSnapshotBundle = async (bundle) => {
|
|
579
|
+
const nowIso = new Date().toISOString();
|
|
580
|
+
const bundleRowLimit = Math.max(1, bundle.cacheRowLimit ??
|
|
581
|
+
limitSnapshotRows * bundle.pageCount);
|
|
582
|
+
const cacheLookupStartedAt = Date.now();
|
|
583
|
+
const cached = await readSnapshotChunkRefByPageKey(trx, {
|
|
584
|
+
partitionId,
|
|
585
|
+
scopeKey: cacheKey,
|
|
586
|
+
scope: bundle.table,
|
|
587
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
588
|
+
rowCursor: bundle.startCursor,
|
|
589
|
+
rowLimit: bundleRowLimit,
|
|
590
|
+
encoding: snapshotChunkEncoding,
|
|
591
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
592
|
+
nowIso,
|
|
593
|
+
});
|
|
594
|
+
bootstrapTimings.chunkCacheLookupMs += Math.max(0, Date.now() - cacheLookupStartedAt);
|
|
595
|
+
let chunkRef = cached;
|
|
596
|
+
if (!chunkRef) {
|
|
597
|
+
const expiresAt = new Date(Date.now() + Math.max(1000, bundle.ttlMs)).toISOString();
|
|
598
|
+
if (args.chunkStorage) {
|
|
599
|
+
const snapshot = {
|
|
600
|
+
table: bundle.table,
|
|
601
|
+
rows: [],
|
|
602
|
+
chunks: [],
|
|
603
|
+
isFirstPage: bundle.isFirstPage,
|
|
604
|
+
isLastPage: bundle.isLastPage,
|
|
605
|
+
bootstrapStateAfter: snapshotBootstrapStateAfter({
|
|
606
|
+
tableIndex: bundle.tableIndex,
|
|
607
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
608
|
+
isLastPage: bundle.isLastPage,
|
|
609
|
+
}),
|
|
610
|
+
};
|
|
611
|
+
snapshots.push(snapshot);
|
|
612
|
+
pendingExternalChunkWrites.push({
|
|
613
|
+
snapshot,
|
|
614
|
+
cacheLookup: {
|
|
615
|
+
partitionId,
|
|
616
|
+
scopeKey: cacheKey,
|
|
617
|
+
scope: bundle.table,
|
|
618
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
619
|
+
rowCursor: bundle.startCursor,
|
|
620
|
+
rowLimit: bundleRowLimit,
|
|
621
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
622
|
+
isLastPage: bundle.isLastPage,
|
|
623
|
+
},
|
|
624
|
+
payloadParts: encodeSnapshotBundlePayload(bundle),
|
|
625
|
+
expiresAt,
|
|
626
|
+
});
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const encodedChunk = await encodeCompressedSnapshotChunk(encodeSnapshotBundlePayload(bundle), snapshotChunkGzipLevel);
|
|
630
|
+
bootstrapTimings.chunkGzipMs += encodedChunk.gzipMs;
|
|
631
|
+
bootstrapTimings.chunkHashMs += encodedChunk.hashMs;
|
|
632
|
+
const chunkId = randomId();
|
|
633
|
+
const chunkPersistStartedAt = Date.now();
|
|
634
|
+
chunkRef = await insertSnapshotChunk(trx, {
|
|
635
|
+
chunkId,
|
|
308
636
|
partitionId,
|
|
309
637
|
scopeKey: cacheKey,
|
|
310
638
|
scope: bundle.table,
|
|
311
639
|
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
312
640
|
rowCursor: bundle.startCursor,
|
|
313
641
|
rowLimit: bundleRowLimit,
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
642
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
643
|
+
isLastPage: bundle.isLastPage,
|
|
644
|
+
encoding: snapshotChunkEncoding,
|
|
645
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
646
|
+
sha256: encodedChunk.sha256,
|
|
647
|
+
body: encodedChunk.body,
|
|
648
|
+
expiresAt,
|
|
649
|
+
});
|
|
650
|
+
bootstrapTimings.chunkPersistMs += Math.max(0, Date.now() - chunkPersistStartedAt);
|
|
651
|
+
}
|
|
652
|
+
const chunk = toResponseChunkRef(chunkRef);
|
|
653
|
+
snapshots.push({
|
|
654
|
+
table: bundle.table,
|
|
655
|
+
rows: [],
|
|
656
|
+
chunks: [chunk],
|
|
657
|
+
manifest: await createChunkedSnapshotManifest({
|
|
658
|
+
table: bundle.table,
|
|
659
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
660
|
+
scopeKey: cacheKey,
|
|
661
|
+
rowCursor: bundle.startCursor,
|
|
662
|
+
rowLimit: bundleRowLimit,
|
|
663
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
664
|
+
isFirstPage: bundle.isFirstPage,
|
|
665
|
+
isLastPage: bundle.isLastPage,
|
|
666
|
+
chunks: [chunk],
|
|
667
|
+
}),
|
|
668
|
+
isFirstPage: bundle.isFirstPage,
|
|
669
|
+
isLastPage: bundle.isLastPage,
|
|
670
|
+
bootstrapStateAfter: snapshotBootstrapStateAfter({
|
|
671
|
+
tableIndex: bundle.tableIndex,
|
|
672
|
+
nextRowCursor: bundle.nextRowCursor,
|
|
673
|
+
isLastPage: bundle.isLastPage,
|
|
674
|
+
}),
|
|
317
675
|
});
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
676
|
+
};
|
|
677
|
+
let activeBundle = null;
|
|
678
|
+
for (let pageIndex = 0; pageIndex < maxSnapshotPages; pageIndex++) {
|
|
679
|
+
if (!nextState)
|
|
680
|
+
break;
|
|
681
|
+
const nextTableName = nextState.tables[nextState.tableIndex];
|
|
682
|
+
if (!nextTableName) {
|
|
683
|
+
if (activeBundle) {
|
|
684
|
+
activeBundle.isLastPage = true;
|
|
685
|
+
await flushSnapshotBundle(activeBundle);
|
|
686
|
+
activeBundle = null;
|
|
687
|
+
}
|
|
688
|
+
nextState = null;
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
const tableHandler = args.handlers.byTable.get(nextTableName);
|
|
692
|
+
if (!tableHandler) {
|
|
693
|
+
throw new Error(`Unknown table: ${nextTableName}`);
|
|
694
|
+
}
|
|
695
|
+
if (!activeBundle ||
|
|
696
|
+
activeBundle.table !== nextTableName) {
|
|
697
|
+
if (activeBundle) {
|
|
698
|
+
await flushSnapshotBundle(activeBundle);
|
|
699
|
+
}
|
|
700
|
+
activeBundle = createSnapshotBundle(nextTableName, nextState.tableIndex, nextState.rowCursor, tableHandler.snapshotChunkTtlMs ??
|
|
701
|
+
24 * 60 * 60 * 1000, resolveSnapshotBinaryColumns(tableHandler, clientSchemaVersion), resolveSnapshotBinaryEncoder(tableHandler, clientSchemaVersion));
|
|
702
|
+
}
|
|
703
|
+
if (artifactScopeKey && activeBundle.pageCount === 0) {
|
|
704
|
+
const pagesRemaining = Math.max(1, maxSnapshotPages - pageIndex);
|
|
705
|
+
const artifactRowLimit = resolveBinarySnapshotBundleRowLimit({
|
|
706
|
+
limitSnapshotRows,
|
|
707
|
+
pagesRemaining,
|
|
708
|
+
});
|
|
709
|
+
const artifactLookupStartedAt = Date.now();
|
|
710
|
+
const artifact = await readBestScopedSnapshotArtifactRefForPageCapacity(trx, {
|
|
711
|
+
partitionId,
|
|
712
|
+
scopeKey: artifactScopeKey,
|
|
713
|
+
subscriptionId: sub.id,
|
|
714
|
+
table: nextTableName,
|
|
715
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
716
|
+
rowCursor: nextState.rowCursor,
|
|
717
|
+
maxRowLimit: artifactRowLimit,
|
|
718
|
+
artifactKind: snapshotArtifactSelection.artifactKind,
|
|
719
|
+
schemaVersion: snapshotArtifactSchemaVersion,
|
|
720
|
+
compression: snapshotArtifactSelection.compression,
|
|
721
|
+
});
|
|
722
|
+
bootstrapTimings.artifactCacheLookupMs += Math.max(0, Date.now() - artifactLookupStartedAt);
|
|
723
|
+
if (artifact &&
|
|
724
|
+
(artifact.isLastPage ||
|
|
725
|
+
artifact.nextRowCursor !== null)) {
|
|
726
|
+
snapshots.push({
|
|
727
|
+
table: nextTableName,
|
|
728
|
+
rows: [],
|
|
729
|
+
artifacts: [artifact],
|
|
730
|
+
isFirstPage: artifact.isFirstPage,
|
|
731
|
+
isLastPage: artifact.isLastPage,
|
|
732
|
+
bootstrapStateAfter: snapshotBootstrapStateAfter({
|
|
733
|
+
tableIndex: nextState.tableIndex,
|
|
734
|
+
nextRowCursor: artifact.nextRowCursor,
|
|
735
|
+
isLastPage: artifact.isLastPage,
|
|
736
|
+
}),
|
|
737
|
+
});
|
|
738
|
+
activeBundle = null;
|
|
739
|
+
const selectedArtifactRowLimit = artifact.manifest.rowLimit;
|
|
740
|
+
pageIndex +=
|
|
741
|
+
Math.max(1, Math.ceil(selectedArtifactRowLimit / limitSnapshotRows)) - 1;
|
|
742
|
+
nextState = snapshotBootstrapStateAfter({
|
|
743
|
+
tableIndex: nextState.tableIndex,
|
|
744
|
+
nextRowCursor: artifact.nextRowCursor,
|
|
745
|
+
isLastPage: artifact.isLastPage,
|
|
746
|
+
});
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (activeBundle.pageCount === 0) {
|
|
751
|
+
const pagesRemaining = Math.max(1, maxSnapshotPages - pageIndex);
|
|
752
|
+
const cachedRowLimit = resolveBinarySnapshotBundleRowLimit({
|
|
753
|
+
limitSnapshotRows,
|
|
754
|
+
pagesRemaining,
|
|
755
|
+
});
|
|
756
|
+
activeBundle.cacheRowLimit = cachedRowLimit;
|
|
757
|
+
const cacheLookupStartedAt = Date.now();
|
|
758
|
+
const cached = await readSnapshotChunkRefByPageKey(trx, {
|
|
759
|
+
partitionId,
|
|
760
|
+
scopeKey: cacheKey,
|
|
761
|
+
scope: nextTableName,
|
|
762
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
763
|
+
rowCursor: nextState.rowCursor,
|
|
764
|
+
rowLimit: cachedRowLimit,
|
|
765
|
+
encoding: snapshotChunkEncoding,
|
|
766
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
767
|
+
});
|
|
768
|
+
bootstrapTimings.chunkCacheLookupMs += Math.max(0, Date.now() - cacheLookupStartedAt);
|
|
769
|
+
if (cached &&
|
|
770
|
+
(cached.isLastPage || cached.nextRowCursor !== null)) {
|
|
771
|
+
const chunk = toResponseChunkRef(cached);
|
|
772
|
+
snapshots.push({
|
|
773
|
+
table: nextTableName,
|
|
774
|
+
rows: [],
|
|
775
|
+
chunks: [chunk],
|
|
776
|
+
manifest: await createChunkedSnapshotManifest({
|
|
777
|
+
table: nextTableName,
|
|
778
|
+
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
779
|
+
scopeKey: cacheKey,
|
|
780
|
+
rowCursor: nextState.rowCursor,
|
|
781
|
+
rowLimit: cachedRowLimit,
|
|
782
|
+
nextRowCursor: cached.nextRowCursor,
|
|
783
|
+
isFirstPage: nextState.rowCursor == null,
|
|
784
|
+
isLastPage: cached.isLastPage,
|
|
785
|
+
chunks: [chunk],
|
|
786
|
+
}),
|
|
787
|
+
isFirstPage: nextState.rowCursor == null,
|
|
788
|
+
isLastPage: cached.isLastPage,
|
|
789
|
+
bootstrapStateAfter: snapshotBootstrapStateAfter({
|
|
790
|
+
tableIndex: nextState.tableIndex,
|
|
791
|
+
nextRowCursor: cached.nextRowCursor,
|
|
792
|
+
isLastPage: cached.isLastPage,
|
|
793
|
+
}),
|
|
794
|
+
});
|
|
795
|
+
activeBundle = null;
|
|
796
|
+
pageIndex +=
|
|
797
|
+
Math.max(1, Math.ceil(cachedRowLimit / limitSnapshotRows)) - 1;
|
|
798
|
+
nextState = snapshotBootstrapStateAfter({
|
|
799
|
+
tableIndex: nextState.tableIndex,
|
|
800
|
+
nextRowCursor: cached.nextRowCursor,
|
|
801
|
+
isLastPage: cached.isLastPage,
|
|
802
|
+
});
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const snapshotQueryStartedAt = Date.now();
|
|
807
|
+
const page = await tableHandler.snapshot({
|
|
808
|
+
db: trx,
|
|
809
|
+
actorId: args.auth.actorId,
|
|
810
|
+
auth: args.auth,
|
|
811
|
+
scopeValues: effectiveScopes,
|
|
812
|
+
cursor: nextState.rowCursor,
|
|
813
|
+
limit: limitSnapshotRows,
|
|
814
|
+
schemaVersion: clientSchemaVersion,
|
|
815
|
+
}, sub.params);
|
|
816
|
+
bootstrapTimings.snapshotQueryMs += Math.max(0, Date.now() - snapshotQueryStartedAt);
|
|
817
|
+
const pageRows = page.rows ?? [];
|
|
818
|
+
activeBundle.nextRowCursor = page.nextCursor;
|
|
819
|
+
activeBundle.binaryRows.push(...pageRows);
|
|
820
|
+
activeBundle.pageCount += 1;
|
|
821
|
+
if (page.nextCursor != null) {
|
|
822
|
+
const shouldFlushBinaryBundle = activeBundle.binaryRows.length >=
|
|
823
|
+
DEFAULT_MAX_BINARY_SNAPSHOT_BUNDLE_ROWS;
|
|
824
|
+
if (shouldFlushBinaryBundle) {
|
|
825
|
+
await flushSnapshotBundle(activeBundle);
|
|
826
|
+
activeBundle = null;
|
|
827
|
+
}
|
|
828
|
+
nextState = {
|
|
829
|
+
...nextState,
|
|
830
|
+
rowCursor: page.nextCursor,
|
|
831
|
+
};
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
353
834
|
activeBundle.isLastPage = true;
|
|
354
835
|
await flushSnapshotBundle(activeBundle);
|
|
355
836
|
activeBundle = null;
|
|
837
|
+
if (nextState.tableIndex + 1 < nextState.tables.length) {
|
|
838
|
+
nextState = {
|
|
839
|
+
...nextState,
|
|
840
|
+
tableIndex: nextState.tableIndex + 1,
|
|
841
|
+
rowCursor: null,
|
|
842
|
+
};
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
nextState = null;
|
|
846
|
+
break;
|
|
356
847
|
}
|
|
357
|
-
nextState = null;
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
const tableHandler = getServerHandlerOrThrow(args.handlers, nextTableName);
|
|
361
|
-
if (!activeBundle || activeBundle.table !== nextTableName) {
|
|
362
848
|
if (activeBundle) {
|
|
363
849
|
await flushSnapshotBundle(activeBundle);
|
|
364
850
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
const page = await tableHandler.snapshot({
|
|
377
|
-
db: trx,
|
|
378
|
-
actorId: args.auth.actorId,
|
|
379
|
-
auth: args.auth,
|
|
380
|
-
scopeValues: effectiveScopes,
|
|
381
|
-
cursor: nextState.rowCursor,
|
|
382
|
-
limit: limitSnapshotRows,
|
|
383
|
-
}, sub.params);
|
|
384
|
-
const rowFrames = encodeSnapshotRowFrames(page.rows ?? []);
|
|
385
|
-
activeBundle.rowFrameParts.push(rowFrames);
|
|
386
|
-
activeBundle.pageCount += 1;
|
|
387
|
-
if (page.nextCursor != null) {
|
|
388
|
-
nextState = { ...nextState, rowCursor: page.nextCursor };
|
|
851
|
+
subResponses.push({
|
|
852
|
+
id: sub.id,
|
|
853
|
+
status: 'active',
|
|
854
|
+
scopes: effectiveScopes,
|
|
855
|
+
bootstrap: true,
|
|
856
|
+
bootstrapState: nextState,
|
|
857
|
+
nextCursor: effectiveState.asOfCommitSeq,
|
|
858
|
+
commits: [],
|
|
859
|
+
snapshots,
|
|
860
|
+
});
|
|
861
|
+
nextCursors.push(effectiveState.asOfCommitSeq);
|
|
389
862
|
continue;
|
|
390
863
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
864
|
+
// Incremental pull for this subscription. The dialect row query
|
|
865
|
+
// carries the scanned commit-window max when matching rows exist,
|
|
866
|
+
// so we only need a separate commit-window scan when the row query
|
|
867
|
+
// returns no matches at all.
|
|
868
|
+
const incrementalRows = [];
|
|
869
|
+
let maxScannedCommitSeq = cursor;
|
|
870
|
+
for await (const row of dialect.iterateIncrementalPullRows(trx, {
|
|
871
|
+
partitionId,
|
|
872
|
+
table: sub.table,
|
|
873
|
+
scopes: effectiveScopes,
|
|
874
|
+
cursor,
|
|
875
|
+
limitCommits,
|
|
876
|
+
})) {
|
|
877
|
+
incrementalRows.push(row);
|
|
878
|
+
maxScannedCommitSeq = Math.max(maxScannedCommitSeq, row.scanned_max_commit_seq ?? row.commit_seq);
|
|
401
879
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
});
|
|
430
|
-
const maxScannedCommitSeq = scannedCommitSeqs.length > 0
|
|
431
|
-
? scannedCommitSeqs[scannedCommitSeqs.length - 1]
|
|
432
|
-
: cursor;
|
|
433
|
-
if (scannedCommitSeqs.length === 0) {
|
|
434
|
-
subResponses.push({
|
|
435
|
-
id: sub.id,
|
|
436
|
-
status: 'active',
|
|
437
|
-
scopes: effectiveScopes,
|
|
438
|
-
bootstrap: false,
|
|
439
|
-
nextCursor: cursor,
|
|
440
|
-
commits: [],
|
|
441
|
-
});
|
|
442
|
-
nextCursors.push(cursor);
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
let nextCursor = cursor;
|
|
446
|
-
if (dedupeRows) {
|
|
447
|
-
const latestByRowKey = new Map();
|
|
448
|
-
for await (const r of dialect.iterateIncrementalPullRows(trx, {
|
|
449
|
-
partitionId,
|
|
450
|
-
table: sub.table,
|
|
451
|
-
scopes: effectiveScopes,
|
|
452
|
-
cursor,
|
|
453
|
-
limitCommits,
|
|
454
|
-
})) {
|
|
455
|
-
nextCursor = Math.max(nextCursor, r.commit_seq);
|
|
456
|
-
const rowKey = `${r.table}\u0000${r.row_id}`;
|
|
457
|
-
const change = {
|
|
458
|
-
table: r.table,
|
|
459
|
-
row_id: r.row_id,
|
|
460
|
-
op: r.op,
|
|
461
|
-
row_json: r.row_json,
|
|
462
|
-
row_version: r.row_version,
|
|
463
|
-
scopes: r.scopes,
|
|
464
|
-
};
|
|
465
|
-
// Move row keys to insertion tail so Map iteration yields
|
|
466
|
-
// "latest change wins" order without a full array sort.
|
|
467
|
-
if (latestByRowKey.has(rowKey)) {
|
|
468
|
-
latestByRowKey.delete(rowKey);
|
|
880
|
+
if (incrementalRows.length === 0) {
|
|
881
|
+
const scannedCommitSeqs = await dialect.readCommitSeqsForPull(trx, {
|
|
882
|
+
partitionId,
|
|
883
|
+
cursor,
|
|
884
|
+
limitCommits,
|
|
885
|
+
tables: [sub.table],
|
|
886
|
+
});
|
|
887
|
+
maxScannedCommitSeq =
|
|
888
|
+
scannedCommitSeqs.length > 0
|
|
889
|
+
? scannedCommitSeqs[scannedCommitSeqs.length - 1]
|
|
890
|
+
: cursor;
|
|
891
|
+
if (scannedCommitSeqs.length === 0) {
|
|
892
|
+
subResponses.push({
|
|
893
|
+
id: sub.id,
|
|
894
|
+
status: 'active',
|
|
895
|
+
scopes: effectiveScopes,
|
|
896
|
+
bootstrap: false,
|
|
897
|
+
nextCursor: cursor,
|
|
898
|
+
commits: [],
|
|
899
|
+
});
|
|
900
|
+
nextCursors.push(cursor);
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const tableHandler = args.handlers.byTable.get(sub.table);
|
|
905
|
+
if (!tableHandler) {
|
|
906
|
+
throw new Error(`Unknown table: ${sub.table}`);
|
|
469
907
|
}
|
|
470
|
-
|
|
908
|
+
const incrementalItems = incrementalRows.map((r) => ({
|
|
471
909
|
commitSeq: r.commit_seq,
|
|
472
910
|
createdAt: r.created_at,
|
|
473
911
|
actorId: r.actor_id,
|
|
474
|
-
change
|
|
912
|
+
change: projectPullChangeForClientSchema({
|
|
913
|
+
handler: tableHandler,
|
|
914
|
+
schemaVersion: clientSchemaVersion,
|
|
915
|
+
change: {
|
|
916
|
+
table: r.table,
|
|
917
|
+
row_id: r.row_id,
|
|
918
|
+
op: r.op,
|
|
919
|
+
row_json: r.row_json,
|
|
920
|
+
row_version: r.row_version,
|
|
921
|
+
scopes: r.scopes,
|
|
922
|
+
},
|
|
923
|
+
}),
|
|
924
|
+
}));
|
|
925
|
+
if (pullPlugins.length > 0 && incrementalItems.length > 0) {
|
|
926
|
+
const transformedChanges = await transformPullChanges({
|
|
927
|
+
plugins: pullPlugins,
|
|
928
|
+
ctx: {
|
|
929
|
+
db: trx,
|
|
930
|
+
actorId: args.auth.actorId,
|
|
931
|
+
auth: args.auth,
|
|
932
|
+
},
|
|
933
|
+
tableHandler,
|
|
934
|
+
subscription: {
|
|
935
|
+
id: sub.id,
|
|
936
|
+
table: sub.table,
|
|
937
|
+
scopes: effectiveScopes,
|
|
938
|
+
params: sub.params,
|
|
939
|
+
cursor,
|
|
940
|
+
crdtStateVectors: sub.crdtStateVectors,
|
|
941
|
+
},
|
|
942
|
+
changes: incrementalItems.map((item) => item.change),
|
|
943
|
+
});
|
|
944
|
+
for (let i = 0; i < incrementalItems.length; i += 1) {
|
|
945
|
+
incrementalItems[i].change = transformedChanges[i];
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
let nextCursor = cursor;
|
|
949
|
+
if (dedupeRows) {
|
|
950
|
+
const latestByRowKey = new Map();
|
|
951
|
+
for (const item of incrementalItems) {
|
|
952
|
+
nextCursor = Math.max(nextCursor, item.commitSeq);
|
|
953
|
+
const rowKey = `${item.change.table}\u0000${item.change.row_id}`;
|
|
954
|
+
// Move row keys to insertion tail so Map iteration yields
|
|
955
|
+
// "latest change wins" order without a full array sort.
|
|
956
|
+
if (latestByRowKey.has(rowKey)) {
|
|
957
|
+
latestByRowKey.delete(rowKey);
|
|
958
|
+
}
|
|
959
|
+
latestByRowKey.set(rowKey, {
|
|
960
|
+
commitSeq: item.commitSeq,
|
|
961
|
+
createdAt: item.createdAt,
|
|
962
|
+
actorId: item.actorId,
|
|
963
|
+
change: item.change,
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
nextCursor = Math.max(nextCursor, maxScannedCommitSeq);
|
|
967
|
+
if (latestByRowKey.size === 0) {
|
|
968
|
+
subResponses.push({
|
|
969
|
+
id: sub.id,
|
|
970
|
+
status: 'active',
|
|
971
|
+
scopes: effectiveScopes,
|
|
972
|
+
bootstrap: false,
|
|
973
|
+
nextCursor,
|
|
974
|
+
commits: [],
|
|
975
|
+
});
|
|
976
|
+
nextCursors.push(nextCursor);
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
const commits = [];
|
|
980
|
+
for (const item of latestByRowKey.values()) {
|
|
981
|
+
const lastCommit = commits[commits.length - 1];
|
|
982
|
+
if (!lastCommit ||
|
|
983
|
+
lastCommit.commitSeq !== item.commitSeq) {
|
|
984
|
+
commits.push({
|
|
985
|
+
commitSeq: item.commitSeq,
|
|
986
|
+
createdAt: item.createdAt,
|
|
987
|
+
actorId: item.actorId,
|
|
988
|
+
changes: [item.change],
|
|
989
|
+
});
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
lastCommit.changes.push(item.change);
|
|
993
|
+
}
|
|
994
|
+
const integrity = await createWireSubscriptionIntegrity({
|
|
995
|
+
partitionId,
|
|
996
|
+
subscriptionId: sub.id,
|
|
997
|
+
previousRoot: typeof sub.verifiedRoot === 'string'
|
|
998
|
+
? sub.verifiedRoot
|
|
999
|
+
: SYNCULAR_COMMIT_GENESIS_ROOT,
|
|
1000
|
+
commits,
|
|
1001
|
+
});
|
|
1002
|
+
subResponses.push({
|
|
1003
|
+
id: sub.id,
|
|
1004
|
+
status: 'active',
|
|
1005
|
+
scopes: effectiveScopes,
|
|
1006
|
+
bootstrap: false,
|
|
1007
|
+
nextCursor,
|
|
1008
|
+
...(integrity ? { integrity } : {}),
|
|
1009
|
+
commits,
|
|
1010
|
+
});
|
|
1011
|
+
nextCursors.push(nextCursor);
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
const commits = [];
|
|
1015
|
+
for (const item of incrementalItems) {
|
|
1016
|
+
nextCursor = Math.max(nextCursor, item.commitSeq);
|
|
1017
|
+
const seq = item.commitSeq;
|
|
1018
|
+
let commit = commits[commits.length - 1];
|
|
1019
|
+
if (!commit || commit.commitSeq !== seq) {
|
|
1020
|
+
commit = {
|
|
1021
|
+
commitSeq: seq,
|
|
1022
|
+
createdAt: item.createdAt,
|
|
1023
|
+
actorId: item.actorId,
|
|
1024
|
+
changes: [],
|
|
1025
|
+
};
|
|
1026
|
+
commits.push(commit);
|
|
1027
|
+
}
|
|
1028
|
+
commit.changes.push(item.change);
|
|
1029
|
+
}
|
|
1030
|
+
const integrity = await createWireSubscriptionIntegrity({
|
|
1031
|
+
partitionId,
|
|
1032
|
+
subscriptionId: sub.id,
|
|
1033
|
+
previousRoot: typeof sub.verifiedRoot === 'string'
|
|
1034
|
+
? sub.verifiedRoot
|
|
1035
|
+
: SYNCULAR_COMMIT_GENESIS_ROOT,
|
|
1036
|
+
commits,
|
|
475
1037
|
});
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
1038
|
+
nextCursor = Math.max(nextCursor, maxScannedCommitSeq);
|
|
1039
|
+
if (commits.length === 0) {
|
|
1040
|
+
subResponses.push({
|
|
1041
|
+
id: sub.id,
|
|
1042
|
+
status: 'active',
|
|
1043
|
+
scopes: effectiveScopes,
|
|
1044
|
+
bootstrap: false,
|
|
1045
|
+
nextCursor,
|
|
1046
|
+
commits: [],
|
|
1047
|
+
});
|
|
1048
|
+
nextCursors.push(nextCursor);
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
479
1051
|
subResponses.push({
|
|
480
1052
|
id: sub.id,
|
|
481
1053
|
status: 'active',
|
|
482
1054
|
scopes: effectiveScopes,
|
|
483
1055
|
bootstrap: false,
|
|
484
1056
|
nextCursor,
|
|
485
|
-
|
|
1057
|
+
...(integrity ? { integrity } : {}),
|
|
1058
|
+
commits,
|
|
486
1059
|
});
|
|
487
1060
|
nextCursors.push(nextCursor);
|
|
488
|
-
continue;
|
|
489
1061
|
}
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
lastCommit.changes.push(item.change);
|
|
503
|
-
}
|
|
504
|
-
subResponses.push({
|
|
505
|
-
id: sub.id,
|
|
506
|
-
status: 'active',
|
|
507
|
-
scopes: effectiveScopes,
|
|
508
|
-
bootstrap: false,
|
|
509
|
-
nextCursor,
|
|
510
|
-
commits,
|
|
511
|
-
});
|
|
512
|
-
nextCursors.push(nextCursor);
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
const commitsBySeq = new Map();
|
|
516
|
-
const commitSeqs = [];
|
|
517
|
-
for await (const r of dialect.iterateIncrementalPullRows(trx, {
|
|
518
|
-
partitionId,
|
|
519
|
-
table: sub.table,
|
|
520
|
-
scopes: effectiveScopes,
|
|
521
|
-
cursor,
|
|
522
|
-
limitCommits,
|
|
523
|
-
})) {
|
|
524
|
-
nextCursor = Math.max(nextCursor, r.commit_seq);
|
|
525
|
-
const seq = r.commit_seq;
|
|
526
|
-
let commit = commitsBySeq.get(seq);
|
|
527
|
-
if (!commit) {
|
|
528
|
-
commit = {
|
|
529
|
-
commitSeq: seq,
|
|
530
|
-
createdAt: r.created_at,
|
|
531
|
-
actorId: r.actor_id,
|
|
532
|
-
changes: [],
|
|
533
|
-
};
|
|
534
|
-
commitsBySeq.set(seq, commit);
|
|
535
|
-
commitSeqs.push(seq);
|
|
536
|
-
}
|
|
537
|
-
const change = {
|
|
538
|
-
table: r.table,
|
|
539
|
-
row_id: r.row_id,
|
|
540
|
-
op: r.op,
|
|
541
|
-
row_json: r.row_json,
|
|
542
|
-
row_version: r.row_version,
|
|
543
|
-
scopes: r.scopes,
|
|
1062
|
+
const effectiveScopes = mergeScopes(activeSubscriptions);
|
|
1063
|
+
const clientCursor = nextCursors.length > 0
|
|
1064
|
+
? Math.min(...nextCursors)
|
|
1065
|
+
: maxCommitSeq;
|
|
1066
|
+
return {
|
|
1067
|
+
response: {
|
|
1068
|
+
ok: true,
|
|
1069
|
+
subscriptions: subResponses,
|
|
1070
|
+
},
|
|
1071
|
+
effectiveScopes,
|
|
1072
|
+
clientCursor,
|
|
544
1073
|
};
|
|
545
|
-
commit.changes.push(change);
|
|
546
|
-
}
|
|
547
|
-
nextCursor = Math.max(nextCursor, maxScannedCommitSeq);
|
|
548
|
-
if (commitSeqs.length === 0) {
|
|
549
|
-
subResponses.push({
|
|
550
|
-
id: sub.id,
|
|
551
|
-
status: 'active',
|
|
552
|
-
scopes: effectiveScopes,
|
|
553
|
-
bootstrap: false,
|
|
554
|
-
nextCursor,
|
|
555
|
-
commits: [],
|
|
556
|
-
});
|
|
557
|
-
nextCursors.push(nextCursor);
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
const commits = commitSeqs
|
|
561
|
-
.map((seq) => commitsBySeq.get(seq))
|
|
562
|
-
.filter((c) => !!c)
|
|
563
|
-
.filter((c) => c.changes.length > 0);
|
|
564
|
-
subResponses.push({
|
|
565
|
-
id: sub.id,
|
|
566
|
-
status: 'active',
|
|
567
|
-
scopes: effectiveScopes,
|
|
568
|
-
bootstrap: false,
|
|
569
|
-
nextCursor,
|
|
570
|
-
commits,
|
|
571
|
-
});
|
|
572
|
-
nextCursors.push(nextCursor);
|
|
573
|
-
}
|
|
574
|
-
const effectiveScopes = mergeScopes(activeSubscriptions);
|
|
575
|
-
const clientCursor = nextCursors.length > 0 ? Math.min(...nextCursors) : maxCommitSeq;
|
|
576
|
-
return {
|
|
577
|
-
response: {
|
|
578
|
-
ok: true,
|
|
579
|
-
subscriptions: subResponses,
|
|
580
|
-
},
|
|
581
|
-
effectiveScopes,
|
|
582
|
-
clientCursor,
|
|
583
|
-
};
|
|
584
|
-
});
|
|
585
|
-
const chunkStorage = args.chunkStorage;
|
|
586
|
-
if (chunkStorage && pendingExternalChunkWrites.length > 0) {
|
|
587
|
-
for (const pending of pendingExternalChunkWrites) {
|
|
588
|
-
let chunkRef = await readSnapshotChunkRefByPageKey(db, {
|
|
589
|
-
partitionId: pending.cacheLookup.partitionId,
|
|
590
|
-
scopeKey: pending.cacheLookup.scopeKey,
|
|
591
|
-
scope: pending.cacheLookup.scope,
|
|
592
|
-
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
593
|
-
rowCursor: pending.cacheLookup.rowCursor,
|
|
594
|
-
rowLimit: pending.cacheLookup.rowLimit,
|
|
595
|
-
encoding: SYNC_SNAPSHOT_CHUNK_ENCODING,
|
|
596
|
-
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
597
1074
|
});
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
chunkRef = await
|
|
1075
|
+
const chunkStorage = args.chunkStorage;
|
|
1076
|
+
if (chunkStorage && pendingExternalChunkWrites.length > 0) {
|
|
1077
|
+
await runWithConcurrency(pendingExternalChunkWrites, 4, async (pending) => {
|
|
1078
|
+
const cacheLookupStartedAt = Date.now();
|
|
1079
|
+
let chunkRef = await readSnapshotChunkRefByPageKey(db, {
|
|
603
1080
|
partitionId: pending.cacheLookup.partitionId,
|
|
604
1081
|
scopeKey: pending.cacheLookup.scopeKey,
|
|
605
1082
|
scope: pending.cacheLookup.scope,
|
|
606
1083
|
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
607
1084
|
rowCursor: pending.cacheLookup.rowCursor,
|
|
608
1085
|
rowLimit: pending.cacheLookup.rowLimit,
|
|
609
|
-
encoding:
|
|
1086
|
+
encoding: snapshotChunkEncoding,
|
|
610
1087
|
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
611
|
-
sha256,
|
|
612
|
-
byteLength,
|
|
613
|
-
bodyStream,
|
|
614
|
-
expiresAt: pending.expiresAt,
|
|
615
1088
|
});
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1089
|
+
bootstrapTimings.chunkCacheLookupMs += Math.max(0, Date.now() - cacheLookupStartedAt);
|
|
1090
|
+
if (!chunkRef) {
|
|
1091
|
+
if (chunkStorage.storeChunkStream) {
|
|
1092
|
+
const { stream: bodyStream, byteLength, sha256, gzipMs, hashMs, } = await encodeCompressedSnapshotChunkToStream(pending.payloadParts, snapshotChunkGzipLevel);
|
|
1093
|
+
bootstrapTimings.chunkGzipMs += gzipMs;
|
|
1094
|
+
bootstrapTimings.chunkHashMs += hashMs;
|
|
1095
|
+
const chunkPersistStartedAt = Date.now();
|
|
1096
|
+
chunkRef = await chunkStorage.storeChunkStream({
|
|
1097
|
+
partitionId: pending.cacheLookup.partitionId,
|
|
1098
|
+
scopeKey: pending.cacheLookup.scopeKey,
|
|
1099
|
+
scope: pending.cacheLookup.scope,
|
|
1100
|
+
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
1101
|
+
rowCursor: pending.cacheLookup.rowCursor,
|
|
1102
|
+
rowLimit: pending.cacheLookup.rowLimit,
|
|
1103
|
+
nextRowCursor: pending.cacheLookup.nextRowCursor,
|
|
1104
|
+
isLastPage: pending.cacheLookup.isLastPage,
|
|
1105
|
+
encoding: snapshotChunkEncoding,
|
|
1106
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1107
|
+
sha256,
|
|
1108
|
+
byteLength,
|
|
1109
|
+
bodyStream,
|
|
1110
|
+
expiresAt: pending.expiresAt,
|
|
1111
|
+
});
|
|
1112
|
+
bootstrapTimings.chunkPersistMs += Math.max(0, Date.now() - chunkPersistStartedAt);
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
const encodedChunk = await encodeCompressedSnapshotChunk(pending.payloadParts, snapshotChunkGzipLevel);
|
|
1116
|
+
bootstrapTimings.chunkGzipMs += encodedChunk.gzipMs;
|
|
1117
|
+
bootstrapTimings.chunkHashMs += encodedChunk.hashMs;
|
|
1118
|
+
const chunkPersistStartedAt = Date.now();
|
|
1119
|
+
chunkRef = await chunkStorage.storeChunk({
|
|
1120
|
+
partitionId: pending.cacheLookup.partitionId,
|
|
1121
|
+
scopeKey: pending.cacheLookup.scopeKey,
|
|
1122
|
+
scope: pending.cacheLookup.scope,
|
|
1123
|
+
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
1124
|
+
rowCursor: pending.cacheLookup.rowCursor,
|
|
1125
|
+
rowLimit: pending.cacheLookup.rowLimit,
|
|
1126
|
+
nextRowCursor: pending.cacheLookup.nextRowCursor,
|
|
1127
|
+
isLastPage: pending.cacheLookup.isLastPage,
|
|
1128
|
+
encoding: snapshotChunkEncoding,
|
|
1129
|
+
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1130
|
+
sha256: encodedChunk.sha256,
|
|
1131
|
+
body: encodedChunk.body,
|
|
1132
|
+
expiresAt: pending.expiresAt,
|
|
1133
|
+
});
|
|
1134
|
+
bootstrapTimings.chunkPersistMs += Math.max(0, Date.now() - chunkPersistStartedAt);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
const chunk = toResponseChunkRef(chunkRef);
|
|
1138
|
+
pending.snapshot.chunks = [chunk];
|
|
1139
|
+
pending.snapshot.manifest =
|
|
1140
|
+
await createChunkedSnapshotManifest({
|
|
1141
|
+
table: pending.snapshot.table,
|
|
1142
|
+
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
1143
|
+
scopeKey: pending.cacheLookup.scopeKey,
|
|
1144
|
+
rowCursor: pending.cacheLookup.rowCursor,
|
|
1145
|
+
rowLimit: pending.cacheLookup.rowLimit,
|
|
1146
|
+
nextRowCursor: pending.cacheLookup.nextRowCursor,
|
|
1147
|
+
isFirstPage: pending.snapshot.isFirstPage,
|
|
1148
|
+
isLastPage: pending.cacheLookup.isLastPage,
|
|
1149
|
+
chunks: [chunk],
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
633
1152
|
}
|
|
634
|
-
|
|
1153
|
+
const durationMs = Math.max(0, Date.now() - startedAtMs);
|
|
1154
|
+
const stats = summarizePullResponse(result.response);
|
|
1155
|
+
span.setAttribute('status', 'ok');
|
|
1156
|
+
span.setAttribute('duration_ms', durationMs);
|
|
1157
|
+
span.setAttribute('subscription_count', stats.subscriptionCount);
|
|
1158
|
+
span.setAttribute('commit_count', stats.commitCount);
|
|
1159
|
+
span.setAttribute('change_count', stats.changeCount);
|
|
1160
|
+
span.setAttribute('snapshot_page_count', stats.snapshotPageCount);
|
|
1161
|
+
span.setAttributes({
|
|
1162
|
+
bootstrap_snapshot_query_ms: bootstrapTimings.snapshotQueryMs,
|
|
1163
|
+
bootstrap_snapshot_binary_encode_ms: bootstrapTimings.binaryEncodeMs,
|
|
1164
|
+
bootstrap_chunk_cache_lookup_ms: bootstrapTimings.chunkCacheLookupMs,
|
|
1165
|
+
bootstrap_artifact_cache_lookup_ms: bootstrapTimings.artifactCacheLookupMs,
|
|
1166
|
+
bootstrap_chunk_gzip_ms: bootstrapTimings.chunkGzipMs,
|
|
1167
|
+
bootstrap_chunk_hash_ms: bootstrapTimings.chunkHashMs,
|
|
1168
|
+
bootstrap_chunk_persist_ms: bootstrapTimings.chunkPersistMs,
|
|
1169
|
+
});
|
|
1170
|
+
span.setStatus('ok');
|
|
1171
|
+
recordPullMetrics({
|
|
1172
|
+
status: 'ok',
|
|
1173
|
+
dedupeRows,
|
|
1174
|
+
durationMs,
|
|
1175
|
+
stats,
|
|
1176
|
+
});
|
|
1177
|
+
return {
|
|
1178
|
+
...result,
|
|
1179
|
+
bootstrapTimings,
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
catch (error) {
|
|
1183
|
+
if (error instanceof Error &&
|
|
1184
|
+
attemptIndex < MAX_PULL_TRANSACTION_RETRIES - 1 &&
|
|
1185
|
+
isSerializablePullError(error)) {
|
|
1186
|
+
await delay(PULL_TRANSACTION_RETRY_DELAY_MS * (attemptIndex + 1));
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
throw error;
|
|
635
1190
|
}
|
|
636
1191
|
}
|
|
637
|
-
|
|
638
|
-
const stats = summarizePullResponse(result.response);
|
|
639
|
-
span.setAttribute('status', 'ok');
|
|
640
|
-
span.setAttribute('duration_ms', durationMs);
|
|
641
|
-
span.setAttribute('subscription_count', stats.subscriptionCount);
|
|
642
|
-
span.setAttribute('commit_count', stats.commitCount);
|
|
643
|
-
span.setAttribute('change_count', stats.changeCount);
|
|
644
|
-
span.setAttribute('snapshot_page_count', stats.snapshotPageCount);
|
|
645
|
-
span.setStatus('ok');
|
|
646
|
-
recordPullMetrics({
|
|
647
|
-
status: 'ok',
|
|
648
|
-
dedupeRows,
|
|
649
|
-
durationMs,
|
|
650
|
-
stats,
|
|
651
|
-
});
|
|
652
|
-
return result;
|
|
1192
|
+
throw new Error('Pull transaction retry loop exhausted unexpectedly');
|
|
653
1193
|
}
|
|
654
1194
|
catch (error) {
|
|
655
1195
|
const durationMs = Math.max(0, Date.now() - startedAtMs);
|