@syncular/server 0.1.3 → 0.2.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 +773 -13
- package/dist/admin.d.ts +136 -0
- package/dist/admin.js +168 -0
- package/dist/blob-handlers.d.ts +69 -0
- package/dist/blob-handlers.js +245 -0
- package/dist/blob-store.d.ts +111 -0
- package/dist/blob-store.js +0 -0
- package/dist/content-encoding.d.ts +27 -0
- package/dist/content-encoding.js +69 -0
- package/dist/context.d.ts +151 -0
- package/dist/context.js +21 -0
- package/dist/crdt-merger.d.ts +28 -0
- package/dist/crdt-merger.js +11 -0
- package/dist/d1-storage.d.ts +50 -0
- package/dist/d1-storage.js +0 -0
- package/dist/errors.d.ts +35 -9
- package/dist/errors.js +220 -11
- package/dist/events-ring.d.ts +55 -0
- package/dist/events-ring.js +96 -0
- package/dist/events.d.ts +231 -0
- package/dist/events.js +22 -0
- package/dist/frame-bytes.d.ts +20 -0
- package/dist/frame-bytes.js +75 -0
- package/dist/handler.d.ts +9 -0
- package/dist/handler.js +490 -0
- package/dist/index.d.ts +42 -33
- package/dist/index.js +45 -27
- package/dist/lease-store.d.ts +50 -0
- package/dist/lease-store.js +0 -0
- package/dist/pg-executor-pglite.d.ts +27 -0
- package/dist/pg-executor-pglite.js +33 -0
- package/dist/pg-executor.d.ts +67 -0
- package/dist/pg-executor.js +56 -0
- package/dist/postgres-fanout.d.ts +83 -0
- package/dist/postgres-fanout.js +71 -0
- package/dist/postgres-storage.d.ts +69 -0
- package/dist/postgres-storage.js +766 -0
- package/dist/prune.d.ts +24 -39
- package/dist/prune.js +44 -146
- package/dist/pull.d.ts +42 -67
- package/dist/pull.js +387 -1187
- package/dist/push.d.ts +31 -66
- package/dist/push.js +406 -655
- package/dist/realtime.d.ts +197 -0
- package/dist/realtime.js +848 -0
- package/dist/relational-rows.d.ts +229 -0
- package/dist/relational-rows.js +514 -0
- package/dist/s3-blob-store.d.ts +142 -0
- package/dist/s3-blob-store.js +458 -0
- package/dist/s3-segment-store.d.ts +95 -0
- package/dist/s3-segment-store.js +380 -0
- package/dist/schema.d.ts +82 -286
- package/dist/schema.js +133 -9
- package/dist/scopes.d.ts +48 -0
- package/dist/scopes.js +98 -0
- package/dist/segment-download.d.ts +14 -0
- package/dist/segment-download.js +125 -0
- package/dist/segment-store.d.ts +81 -0
- package/dist/segment-store.js +63 -0
- package/dist/signed-url.d.ts +139 -0
- package/dist/signed-url.js +143 -0
- package/dist/sigv4.d.ts +97 -0
- package/dist/sigv4.js +161 -0
- package/dist/sqlite-blob-store.d.ts +22 -0
- package/dist/sqlite-blob-store.js +86 -0
- package/dist/sqlite-dialect.d.ts +101 -0
- package/dist/sqlite-dialect.js +250 -0
- package/dist/sqlite-image.d.ts +27 -0
- package/dist/sqlite-image.js +83 -0
- package/dist/sqlite-lease-store.d.ts +21 -0
- package/dist/sqlite-lease-store.js +94 -0
- package/dist/sqlite-segment-store.d.ts +25 -0
- package/dist/sqlite-segment-store.js +115 -0
- package/dist/sqlite-storage.d.ts +47 -0
- package/dist/sqlite-storage.js +477 -0
- package/dist/storage.d.ts +252 -0
- package/dist/storage.js +1 -0
- package/dist/validate.d.ts +83 -0
- package/dist/validate.js +44 -0
- package/package.json +17 -325
- package/src/admin.ts +312 -0
- package/src/blob-handlers.ts +340 -0
- package/src/blob-store.ts +0 -0
- package/src/content-encoding.ts +94 -0
- package/src/context.ts +176 -0
- package/src/crdt-merger.ts +33 -0
- package/src/d1-storage.ts +0 -0
- package/src/errors.ts +241 -19
- package/src/events-ring.ts +116 -0
- package/src/events.ts +291 -0
- package/src/frame-bytes.ts +94 -0
- package/src/handler.ts +678 -0
- package/src/index.ts +44 -27
- package/src/lease-store.ts +0 -0
- package/src/pg-executor-pglite.ts +63 -0
- package/src/pg-executor.ts +87 -0
- package/src/postgres-fanout.ts +143 -0
- package/src/postgres-storage.ts +1182 -0
- package/src/prune.ts +63 -289
- package/src/pull.ts +505 -1805
- package/src/push.ts +623 -1006
- package/src/realtime.ts +1082 -0
- package/src/relational-rows.ts +633 -0
- package/src/s3-blob-store.ts +616 -0
- package/src/s3-segment-store.ts +519 -0
- package/src/schema.ts +242 -281
- package/src/scopes.ts +127 -0
- package/src/segment-download.ts +169 -0
- package/src/segment-store.ts +154 -0
- package/src/signed-url.ts +308 -0
- package/src/sigv4.ts +267 -0
- package/src/sqlite-blob-store.ts +125 -0
- package/src/sqlite-dialect.ts +337 -0
- package/src/sqlite-image.ts +124 -0
- package/src/sqlite-lease-store.ts +144 -0
- package/src/sqlite-segment-store.ts +204 -0
- package/src/sqlite-storage.ts +792 -0
- package/src/storage.ts +316 -0
- package/src/validate.ts +123 -0
- package/dist/auth-leases.d.ts +0 -85
- package/dist/auth-leases.d.ts.map +0 -1
- package/dist/auth-leases.js +0 -368
- package/dist/auth-leases.js.map +0 -1
- package/dist/better-sqlite3.d.ts +0 -22
- package/dist/better-sqlite3.d.ts.map +0 -1
- package/dist/better-sqlite3.js +0 -16
- package/dist/better-sqlite3.js.map +0 -1
- package/dist/blobs/access.d.ts +0 -35
- package/dist/blobs/access.d.ts.map +0 -1
- package/dist/blobs/access.js +0 -149
- package/dist/blobs/access.js.map +0 -1
- package/dist/blobs/adapters/database.d.ts +0 -100
- package/dist/blobs/adapters/database.d.ts.map +0 -1
- package/dist/blobs/adapters/database.js +0 -247
- package/dist/blobs/adapters/database.js.map +0 -1
- package/dist/blobs/index.d.ts +0 -9
- package/dist/blobs/index.d.ts.map +0 -1
- package/dist/blobs/index.js +0 -9
- package/dist/blobs/index.js.map +0 -1
- package/dist/blobs/manager.d.ts +0 -220
- package/dist/blobs/manager.d.ts.map +0 -1
- package/dist/blobs/manager.js +0 -625
- package/dist/blobs/manager.js.map +0 -1
- package/dist/blobs/migrate.d.ts +0 -27
- package/dist/blobs/migrate.d.ts.map +0 -1
- package/dist/blobs/migrate.js +0 -127
- package/dist/blobs/migrate.js.map +0 -1
- package/dist/blobs/types.d.ts +0 -58
- package/dist/blobs/types.d.ts.map +0 -1
- package/dist/blobs/types.js +0 -5
- package/dist/blobs/types.js.map +0 -1
- package/dist/bun-sqlite.d.ts +0 -19
- package/dist/bun-sqlite.d.ts.map +0 -1
- package/dist/bun-sqlite.js +0 -19
- package/dist/bun-sqlite.js.map +0 -1
- package/dist/clients.d.ts +0 -15
- package/dist/clients.d.ts.map +0 -1
- package/dist/clients.js +0 -7
- package/dist/clients.js.map +0 -1
- package/dist/cloudflare/durable-object.d.ts +0 -93
- package/dist/cloudflare/durable-object.d.ts.map +0 -1
- package/dist/cloudflare/durable-object.js +0 -210
- package/dist/cloudflare/durable-object.js.map +0 -1
- package/dist/cloudflare/index.d.ts +0 -22
- package/dist/cloudflare/index.d.ts.map +0 -1
- package/dist/cloudflare/index.js +0 -22
- package/dist/cloudflare/index.js.map +0 -1
- package/dist/cloudflare/r2.d.ts +0 -180
- package/dist/cloudflare/r2.d.ts.map +0 -1
- package/dist/cloudflare/r2.js +0 -258
- package/dist/cloudflare/r2.js.map +0 -1
- package/dist/cloudflare/scope-cache.d.ts +0 -54
- package/dist/cloudflare/scope-cache.d.ts.map +0 -1
- package/dist/cloudflare/scope-cache.js +0 -223
- package/dist/cloudflare/scope-cache.js.map +0 -1
- package/dist/cloudflare/sentry.d.ts +0 -47
- package/dist/cloudflare/sentry.d.ts.map +0 -1
- package/dist/cloudflare/sentry.js +0 -163
- package/dist/cloudflare/sentry.js.map +0 -1
- package/dist/cloudflare/worker.d.ts +0 -46
- package/dist/cloudflare/worker.d.ts.map +0 -1
- package/dist/cloudflare/worker.js +0 -63
- package/dist/cloudflare/worker.js.map +0 -1
- package/dist/commit-integrity.d.ts +0 -38
- package/dist/commit-integrity.d.ts.map +0 -1
- package/dist/commit-integrity.js +0 -260
- package/dist/commit-integrity.js.map +0 -1
- package/dist/compaction.d.ts +0 -27
- package/dist/compaction.d.ts.map +0 -1
- package/dist/compaction.js +0 -49
- package/dist/compaction.js.map +0 -1
- package/dist/crdt-yjs/index.d.ts +0 -99
- package/dist/crdt-yjs/index.d.ts.map +0 -1
- package/dist/crdt-yjs/index.js +0 -629
- package/dist/crdt-yjs/index.js.map +0 -1
- package/dist/d1.d.ts +0 -13
- package/dist/d1.d.ts.map +0 -1
- package/dist/d1.js +0 -14
- package/dist/d1.js.map +0 -1
- package/dist/dialect/base.d.ts +0 -93
- package/dist/dialect/base.d.ts.map +0 -1
- package/dist/dialect/base.js +0 -181
- package/dist/dialect/base.js.map +0 -1
- package/dist/dialect/helpers.d.ts +0 -16
- package/dist/dialect/helpers.d.ts.map +0 -1
- package/dist/dialect/helpers.js +0 -83
- package/dist/dialect/helpers.js.map +0 -1
- package/dist/dialect/index.d.ts +0 -7
- package/dist/dialect/index.d.ts.map +0 -1
- package/dist/dialect/index.js +0 -7
- package/dist/dialect/index.js.map +0 -1
- package/dist/dialect/types.d.ts +0 -187
- package/dist/dialect/types.d.ts.map +0 -1
- package/dist/dialect/types.js +0 -8
- package/dist/dialect/types.js.map +0 -1
- package/dist/encrypted-crdt.d.ts +0 -67
- package/dist/encrypted-crdt.d.ts.map +0 -1
- package/dist/encrypted-crdt.js +0 -426
- package/dist/encrypted-crdt.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/filesystem/index.d.ts +0 -44
- package/dist/filesystem/index.d.ts.map +0 -1
- package/dist/filesystem/index.js +0 -164
- package/dist/filesystem/index.js.map +0 -1
- package/dist/handlers/collection.d.ts +0 -20
- package/dist/handlers/collection.d.ts.map +0 -1
- package/dist/handlers/collection.js +0 -42
- package/dist/handlers/collection.js.map +0 -1
- package/dist/handlers/create-handler.d.ts +0 -156
- package/dist/handlers/create-handler.d.ts.map +0 -1
- package/dist/handlers/create-handler.js +0 -626
- package/dist/handlers/create-handler.js.map +0 -1
- package/dist/handlers/index.d.ts +0 -4
- package/dist/handlers/index.d.ts.map +0 -1
- package/dist/handlers/index.js +0 -4
- package/dist/handlers/index.js.map +0 -1
- package/dist/handlers/types.d.ts +0 -295
- package/dist/handlers/types.d.ts.map +0 -1
- package/dist/handlers/types.js +0 -2
- package/dist/handlers/types.js.map +0 -1
- package/dist/helpers/conflict.d.ts +0 -52
- package/dist/helpers/conflict.d.ts.map +0 -1
- package/dist/helpers/conflict.js +0 -49
- package/dist/helpers/conflict.js.map +0 -1
- package/dist/helpers/emitted-change.d.ts +0 -56
- package/dist/helpers/emitted-change.d.ts.map +0 -1
- package/dist/helpers/emitted-change.js +0 -46
- package/dist/helpers/emitted-change.js.map +0 -1
- package/dist/helpers/index.d.ts +0 -12
- package/dist/helpers/index.d.ts.map +0 -1
- package/dist/helpers/index.js +0 -12
- package/dist/helpers/index.js.map +0 -1
- package/dist/helpers/paginate.d.ts +0 -49
- package/dist/helpers/paginate.d.ts.map +0 -1
- package/dist/helpers/paginate.js +0 -54
- package/dist/helpers/paginate.js.map +0 -1
- package/dist/helpers/scope-authorization.d.ts +0 -7
- package/dist/helpers/scope-authorization.d.ts.map +0 -1
- package/dist/helpers/scope-authorization.js +0 -19
- package/dist/helpers/scope-authorization.js.map +0 -1
- package/dist/helpers/scope-commit-index.d.ts +0 -12
- package/dist/helpers/scope-commit-index.d.ts.map +0 -1
- package/dist/helpers/scope-commit-index.js +0 -35
- package/dist/helpers/scope-commit-index.js.map +0 -1
- package/dist/helpers/scope-strings.d.ts +0 -74
- package/dist/helpers/scope-strings.d.ts.map +0 -1
- package/dist/helpers/scope-strings.js +0 -82
- package/dist/helpers/scope-strings.js.map +0 -1
- package/dist/hono/api-key-auth.d.ts +0 -49
- package/dist/hono/api-key-auth.d.ts.map +0 -1
- package/dist/hono/api-key-auth.js +0 -108
- package/dist/hono/api-key-auth.js.map +0 -1
- package/dist/hono/audit-redaction.d.ts +0 -20
- package/dist/hono/audit-redaction.d.ts.map +0 -1
- package/dist/hono/audit-redaction.js +0 -85
- package/dist/hono/audit-redaction.js.map +0 -1
- package/dist/hono/blobs.d.ts +0 -75
- package/dist/hono/blobs.d.ts.map +0 -1
- package/dist/hono/blobs.js +0 -586
- package/dist/hono/blobs.js.map +0 -1
- package/dist/hono/console/gateway.d.ts +0 -42
- package/dist/hono/console/gateway.d.ts.map +0 -1
- package/dist/hono/console/gateway.js +0 -2158
- package/dist/hono/console/gateway.js.map +0 -1
- package/dist/hono/console/live-auth.d.ts +0 -7
- package/dist/hono/console/live-auth.d.ts.map +0 -1
- package/dist/hono/console/live-auth.js +0 -39
- package/dist/hono/console/live-auth.js.map +0 -1
- package/dist/hono/console/route-descriptor.d.ts +0 -6
- package/dist/hono/console/route-descriptor.d.ts.map +0 -1
- package/dist/hono/console/route-descriptor.js +0 -16
- package/dist/hono/console/route-descriptor.js.map +0 -1
- package/dist/hono/console/routes/api-keys.d.ts +0 -8
- package/dist/hono/console/routes/api-keys.d.ts.map +0 -1
- package/dist/hono/console/routes/api-keys.js +0 -575
- package/dist/hono/console/routes/api-keys.js.map +0 -1
- package/dist/hono/console/routes/clients.d.ts +0 -8
- package/dist/hono/console/routes/clients.d.ts.map +0 -1
- package/dist/hono/console/routes/clients.js +0 -322
- package/dist/hono/console/routes/clients.js.map +0 -1
- package/dist/hono/console/routes/commits.d.ts +0 -8
- package/dist/hono/console/routes/commits.d.ts.map +0 -1
- package/dist/hono/console/routes/commits.js +0 -882
- package/dist/hono/console/routes/commits.js.map +0 -1
- package/dist/hono/console/routes/context.d.ts +0 -188
- package/dist/hono/console/routes/context.d.ts.map +0 -1
- package/dist/hono/console/routes/context.js +0 -633
- package/dist/hono/console/routes/context.js.map +0 -1
- package/dist/hono/console/routes/events.d.ts +0 -8
- package/dist/hono/console/routes/events.d.ts.map +0 -1
- package/dist/hono/console/routes/events.js +0 -501
- package/dist/hono/console/routes/events.js.map +0 -1
- package/dist/hono/console/routes/maintenance.d.ts +0 -8
- package/dist/hono/console/routes/maintenance.d.ts.map +0 -1
- package/dist/hono/console/routes/maintenance.js +0 -341
- package/dist/hono/console/routes/maintenance.js.map +0 -1
- package/dist/hono/console/routes/shared.d.ts +0 -174
- package/dist/hono/console/routes/shared.d.ts.map +0 -1
- package/dist/hono/console/routes/shared.js +0 -583
- package/dist/hono/console/routes/shared.js.map +0 -1
- package/dist/hono/console/routes/stats.d.ts +0 -8
- package/dist/hono/console/routes/stats.d.ts.map +0 -1
- package/dist/hono/console/routes/stats.js +0 -295
- package/dist/hono/console/routes/stats.js.map +0 -1
- package/dist/hono/console/routes/storage.d.ts +0 -8
- package/dist/hono/console/routes/storage.d.ts.map +0 -1
- package/dist/hono/console/routes/storage.js +0 -121
- package/dist/hono/console/routes/storage.js.map +0 -1
- package/dist/hono/console/routes.d.ts +0 -36
- package/dist/hono/console/routes.d.ts.map +0 -1
- package/dist/hono/console/routes.js +0 -112
- package/dist/hono/console/routes.js.map +0 -1
- package/dist/hono/console/schema-errors.d.ts +0 -2
- package/dist/hono/console/schema-errors.d.ts.map +0 -1
- package/dist/hono/console/schema-errors.js +0 -17
- package/dist/hono/console/schema-errors.js.map +0 -1
- package/dist/hono/console/schemas.d.ts +0 -1515
- package/dist/hono/console/schemas.d.ts.map +0 -1
- package/dist/hono/console/schemas.js +0 -661
- package/dist/hono/console/schemas.js.map +0 -1
- package/dist/hono/console/types.d.ts +0 -213
- package/dist/hono/console/types.d.ts.map +0 -1
- package/dist/hono/console/types.js +0 -2
- package/dist/hono/console/types.js.map +0 -1
- package/dist/hono/console/ui.d.ts +0 -38
- package/dist/hono/console/ui.d.ts.map +0 -1
- package/dist/hono/console/ui.js +0 -43
- package/dist/hono/console/ui.js.map +0 -1
- package/dist/hono/create-server.d.ts +0 -71
- package/dist/hono/create-server.d.ts.map +0 -1
- package/dist/hono/create-server.js +0 -121
- package/dist/hono/create-server.js.map +0 -1
- package/dist/hono/errors.d.ts +0 -13
- package/dist/hono/errors.d.ts.map +0 -1
- package/dist/hono/errors.js +0 -21
- package/dist/hono/errors.js.map +0 -1
- package/dist/hono/index.d.ts +0 -20
- package/dist/hono/index.d.ts.map +0 -1
- package/dist/hono/index.js +0 -31
- package/dist/hono/index.js.map +0 -1
- package/dist/hono/openapi.d.ts +0 -72
- package/dist/hono/openapi.d.ts.map +0 -1
- package/dist/hono/openapi.js +0 -99
- package/dist/hono/openapi.js.map +0 -1
- package/dist/hono/proxy/connection-manager.d.ts +0 -78
- package/dist/hono/proxy/connection-manager.d.ts.map +0 -1
- package/dist/hono/proxy/connection-manager.js +0 -251
- package/dist/hono/proxy/connection-manager.js.map +0 -1
- package/dist/hono/proxy/index.d.ts +0 -8
- package/dist/hono/proxy/index.d.ts.map +0 -1
- package/dist/hono/proxy/index.js +0 -8
- package/dist/hono/proxy/index.js.map +0 -1
- package/dist/hono/proxy/routes.d.ts +0 -86
- package/dist/hono/proxy/routes.d.ts.map +0 -1
- package/dist/hono/proxy/routes.js +0 -183
- package/dist/hono/proxy/routes.js.map +0 -1
- package/dist/hono/rate-limit.d.ts +0 -101
- package/dist/hono/rate-limit.d.ts.map +0 -1
- package/dist/hono/rate-limit.js +0 -184
- package/dist/hono/rate-limit.js.map +0 -1
- package/dist/hono/realtime-sync-packs.d.ts +0 -43
- package/dist/hono/realtime-sync-packs.d.ts.map +0 -1
- package/dist/hono/realtime-sync-packs.js +0 -219
- package/dist/hono/realtime-sync-packs.js.map +0 -1
- package/dist/hono/routes/audit.d.ts +0 -12
- package/dist/hono/routes/audit.d.ts.map +0 -1
- package/dist/hono/routes/audit.js +0 -385
- package/dist/hono/routes/audit.js.map +0 -1
- package/dist/hono/routes/auth-leases.d.ts +0 -8
- package/dist/hono/routes/auth-leases.d.ts.map +0 -1
- package/dist/hono/routes/auth-leases.js +0 -88
- package/dist/hono/routes/auth-leases.js.map +0 -1
- package/dist/hono/routes/combined.d.ts +0 -8
- package/dist/hono/routes/combined.d.ts.map +0 -1
- package/dist/hono/routes/combined.js +0 -392
- package/dist/hono/routes/combined.js.map +0 -1
- package/dist/hono/routes/context.d.ts +0 -314
- package/dist/hono/routes/context.d.ts.map +0 -1
- package/dist/hono/routes/context.js +0 -1147
- package/dist/hono/routes/context.js.map +0 -1
- package/dist/hono/routes/health.d.ts +0 -8
- package/dist/hono/routes/health.d.ts.map +0 -1
- package/dist/hono/routes/health.js +0 -16
- package/dist/hono/routes/health.js.map +0 -1
- package/dist/hono/routes/realtime.d.ts +0 -9
- package/dist/hono/routes/realtime.d.ts.map +0 -1
- package/dist/hono/routes/realtime.js +0 -626
- package/dist/hono/routes/realtime.js.map +0 -1
- package/dist/hono/routes/shared.d.ts +0 -689
- package/dist/hono/routes/shared.d.ts.map +0 -1
- package/dist/hono/routes/shared.js +0 -972
- package/dist/hono/routes/shared.js.map +0 -1
- package/dist/hono/routes/snapshots.d.ts +0 -10
- package/dist/hono/routes/snapshots.d.ts.map +0 -1
- package/dist/hono/routes/snapshots.js +0 -281
- package/dist/hono/routes/snapshots.js.map +0 -1
- package/dist/hono/routes.d.ts +0 -19
- package/dist/hono/routes.d.ts.map +0 -1
- package/dist/hono/routes.js +0 -37
- package/dist/hono/routes.js.map +0 -1
- package/dist/hono/validation.d.ts +0 -5
- package/dist/hono/validation.d.ts.map +0 -1
- package/dist/hono/validation.js +0 -47
- package/dist/hono/validation.js.map +0 -1
- package/dist/hono/websocket-origin.d.ts +0 -9
- package/dist/hono/websocket-origin.d.ts.map +0 -1
- package/dist/hono/websocket-origin.js +0 -96
- package/dist/hono/websocket-origin.js.map +0 -1
- package/dist/hono/ws.d.ts +0 -341
- package/dist/hono/ws.d.ts.map +0 -1
- package/dist/hono/ws.js +0 -711
- package/dist/hono/ws.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/libsql.d.ts +0 -29
- package/dist/libsql.d.ts.map +0 -1
- package/dist/libsql.js +0 -25
- package/dist/libsql.js.map +0 -1
- package/dist/migrate.d.ts +0 -14
- package/dist/migrate.d.ts.map +0 -1
- package/dist/migrate.js +0 -13
- package/dist/migrate.js.map +0 -1
- package/dist/neon.d.ts +0 -21
- package/dist/neon.d.ts.map +0 -1
- package/dist/neon.js +0 -22
- package/dist/neon.js.map +0 -1
- package/dist/notify.d.ts +0 -77
- package/dist/notify.d.ts.map +0 -1
- package/dist/notify.js +0 -233
- package/dist/notify.js.map +0 -1
- package/dist/pglite.d.ts +0 -37
- package/dist/pglite.d.ts.map +0 -1
- package/dist/pglite.js +0 -37
- package/dist/pglite.js.map +0 -1
- package/dist/plugins/index.d.ts +0 -2
- package/dist/plugins/index.d.ts.map +0 -1
- package/dist/plugins/index.js +0 -2
- package/dist/plugins/index.js.map +0 -1
- package/dist/plugins/types.d.ts +0 -73
- package/dist/plugins/types.d.ts.map +0 -1
- package/dist/plugins/types.js +0 -30
- package/dist/plugins/types.js.map +0 -1
- package/dist/postgres/index.d.ts +0 -68
- package/dist/postgres/index.d.ts.map +0 -1
- package/dist/postgres/index.js +0 -924
- package/dist/postgres/index.js.map +0 -1
- package/dist/proxy/collection.d.ts +0 -7
- package/dist/proxy/collection.d.ts.map +0 -1
- package/dist/proxy/collection.js +0 -6
- package/dist/proxy/collection.js.map +0 -1
- package/dist/proxy/handler.d.ts +0 -42
- package/dist/proxy/handler.d.ts.map +0 -1
- package/dist/proxy/handler.js +0 -102
- package/dist/proxy/handler.js.map +0 -1
- package/dist/proxy/index.d.ts +0 -9
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js +0 -14
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/mutation-detector.d.ts +0 -35
- package/dist/proxy/mutation-detector.d.ts.map +0 -1
- package/dist/proxy/mutation-detector.js +0 -246
- package/dist/proxy/mutation-detector.js.map +0 -1
- package/dist/proxy/oplog.d.ts +0 -30
- package/dist/proxy/oplog.d.ts.map +0 -1
- package/dist/proxy/oplog.js +0 -137
- package/dist/proxy/oplog.js.map +0 -1
- package/dist/proxy/types.d.ts +0 -44
- package/dist/proxy/types.d.ts.map +0 -1
- package/dist/proxy/types.js +0 -7
- package/dist/proxy/types.js.map +0 -1
- package/dist/prune.d.ts.map +0 -1
- package/dist/prune.js.map +0 -1
- package/dist/pull.d.ts.map +0 -1
- package/dist/pull.js.map +0 -1
- package/dist/push.d.ts.map +0 -1
- package/dist/push.js.map +0 -1
- package/dist/realtime/in-memory.d.ts +0 -13
- package/dist/realtime/in-memory.d.ts.map +0 -1
- package/dist/realtime/in-memory.js +0 -28
- package/dist/realtime/in-memory.js.map +0 -1
- package/dist/realtime/index.d.ts +0 -4
- package/dist/realtime/index.d.ts.map +0 -1
- package/dist/realtime/index.js +0 -3
- package/dist/realtime/index.js.map +0 -1
- package/dist/realtime/types.d.ts +0 -62
- package/dist/realtime/types.d.ts.map +0 -1
- package/dist/realtime/types.js +0 -19
- package/dist/realtime/types.js.map +0 -1
- package/dist/relay/client-role/forward-engine.d.ts +0 -63
- package/dist/relay/client-role/forward-engine.d.ts.map +0 -1
- package/dist/relay/client-role/forward-engine.js +0 -267
- package/dist/relay/client-role/forward-engine.js.map +0 -1
- package/dist/relay/client-role/index.d.ts +0 -9
- package/dist/relay/client-role/index.d.ts.map +0 -1
- package/dist/relay/client-role/index.js +0 -9
- package/dist/relay/client-role/index.js.map +0 -1
- package/dist/relay/client-role/pull-engine.d.ts +0 -72
- package/dist/relay/client-role/pull-engine.d.ts.map +0 -1
- package/dist/relay/client-role/pull-engine.js +0 -249
- package/dist/relay/client-role/pull-engine.js.map +0 -1
- package/dist/relay/client-role/sequence-mapper.d.ts +0 -65
- package/dist/relay/client-role/sequence-mapper.d.ts.map +0 -1
- package/dist/relay/client-role/sequence-mapper.js +0 -161
- package/dist/relay/client-role/sequence-mapper.js.map +0 -1
- package/dist/relay/evaluation/relay-paths.d.ts +0 -41
- package/dist/relay/evaluation/relay-paths.d.ts.map +0 -1
- package/dist/relay/evaluation/relay-paths.js +0 -504
- package/dist/relay/evaluation/relay-paths.js.map +0 -1
- package/dist/relay/evaluation/rust-boundary.d.ts +0 -47
- package/dist/relay/evaluation/rust-boundary.d.ts.map +0 -1
- package/dist/relay/evaluation/rust-boundary.js +0 -220
- package/dist/relay/evaluation/rust-boundary.js.map +0 -1
- package/dist/relay/index.d.ts +0 -37
- package/dist/relay/index.d.ts.map +0 -1
- package/dist/relay/index.js +0 -44
- package/dist/relay/index.js.map +0 -1
- package/dist/relay/migrate.d.ts +0 -18
- package/dist/relay/migrate.d.ts.map +0 -1
- package/dist/relay/migrate.js +0 -99
- package/dist/relay/migrate.js.map +0 -1
- package/dist/relay/mode-manager.d.ts +0 -60
- package/dist/relay/mode-manager.d.ts.map +0 -1
- package/dist/relay/mode-manager.js +0 -114
- package/dist/relay/mode-manager.js.map +0 -1
- package/dist/relay/realtime.d.ts +0 -90
- package/dist/relay/realtime.d.ts.map +0 -1
- package/dist/relay/realtime.js +0 -147
- package/dist/relay/realtime.js.map +0 -1
- package/dist/relay/relay.d.ts +0 -190
- package/dist/relay/relay.d.ts.map +0 -1
- package/dist/relay/relay.js +0 -320
- package/dist/relay/relay.js.map +0 -1
- package/dist/relay/schema.d.ts +0 -158
- package/dist/relay/schema.d.ts.map +0 -1
- package/dist/relay/schema.js +0 -7
- package/dist/relay/schema.js.map +0 -1
- package/dist/relay/server-role/index.d.ts +0 -56
- package/dist/relay/server-role/index.d.ts.map +0 -1
- package/dist/relay/server-role/index.js +0 -193
- package/dist/relay/server-role/index.js.map +0 -1
- package/dist/relay/server-role/pull.d.ts +0 -27
- package/dist/relay/server-role/pull.d.ts.map +0 -1
- package/dist/relay/server-role/pull.js +0 -24
- package/dist/relay/server-role/pull.js.map +0 -1
- package/dist/relay/server-role/push.d.ts +0 -29
- package/dist/relay/server-role/push.d.ts.map +0 -1
- package/dist/relay/server-role/push.js +0 -122
- package/dist/relay/server-role/push.js.map +0 -1
- package/dist/s3/index.d.ts +0 -83
- package/dist/s3/index.d.ts.map +0 -1
- package/dist/s3/index.js +0 -223
- package/dist/s3/index.js.map +0 -1
- package/dist/schema.d.ts.map +0 -1
- package/dist/schema.js.map +0 -1
- package/dist/service-worker/index.d.ts +0 -114
- package/dist/service-worker/index.d.ts.map +0 -1
- package/dist/service-worker/index.js +0 -487
- package/dist/service-worker/index.js.map +0 -1
- package/dist/snapshot-artifacts/sqlite-bun.d.ts +0 -15
- package/dist/snapshot-artifacts/sqlite-bun.d.ts.map +0 -1
- package/dist/snapshot-artifacts/sqlite-bun.js +0 -128
- package/dist/snapshot-artifacts/sqlite-bun.js.map +0 -1
- package/dist/snapshot-artifacts.d.ts +0 -206
- package/dist/snapshot-artifacts.d.ts.map +0 -1
- package/dist/snapshot-artifacts.js +0 -545
- package/dist/snapshot-artifacts.js.map +0 -1
- package/dist/snapshot-chunks/db-metadata.d.ts +0 -56
- package/dist/snapshot-chunks/db-metadata.d.ts.map +0 -1
- package/dist/snapshot-chunks/db-metadata.js +0 -360
- package/dist/snapshot-chunks/db-metadata.js.map +0 -1
- package/dist/snapshot-chunks/index.d.ts +0 -8
- package/dist/snapshot-chunks/index.d.ts.map +0 -1
- package/dist/snapshot-chunks/index.js +0 -8
- package/dist/snapshot-chunks/index.js.map +0 -1
- package/dist/snapshot-chunks/types.d.ts +0 -80
- package/dist/snapshot-chunks/types.d.ts.map +0 -1
- package/dist/snapshot-chunks/types.js +0 -8
- package/dist/snapshot-chunks/types.js.map +0 -1
- package/dist/snapshot-chunks.d.ts +0 -90
- package/dist/snapshot-chunks.d.ts.map +0 -1
- package/dist/snapshot-chunks.js +0 -304
- package/dist/snapshot-chunks.js.map +0 -1
- package/dist/sqlite/index.d.ts +0 -53
- package/dist/sqlite/index.d.ts.map +0 -1
- package/dist/sqlite/index.js +0 -795
- package/dist/sqlite/index.js.map +0 -1
- package/dist/sqlite3.d.ts +0 -22
- package/dist/sqlite3.d.ts.map +0 -1
- package/dist/sqlite3.js +0 -99
- package/dist/sqlite3.js.map +0 -1
- package/dist/stats.d.ts +0 -28
- package/dist/stats.d.ts.map +0 -1
- package/dist/stats.js +0 -93
- package/dist/stats.js.map +0 -1
- package/dist/subscriptions/cache.d.ts +0 -58
- package/dist/subscriptions/cache.d.ts.map +0 -1
- package/dist/subscriptions/cache.js +0 -250
- package/dist/subscriptions/cache.js.map +0 -1
- package/dist/subscriptions/index.d.ts +0 -3
- package/dist/subscriptions/index.d.ts.map +0 -1
- package/dist/subscriptions/index.js +0 -3
- package/dist/subscriptions/index.js.map +0 -1
- package/dist/subscriptions/resolve.d.ts +0 -40
- package/dist/subscriptions/resolve.d.ts.map +0 -1
- package/dist/subscriptions/resolve.js +0 -275
- package/dist/subscriptions/resolve.js.map +0 -1
- package/dist/sync.d.ts +0 -24
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -27
- package/dist/sync.js.map +0 -1
- package/src/auth-leases.ts +0 -649
- package/src/better-sqlite3.ts +0 -35
- package/src/blobs/access.ts +0 -244
- package/src/blobs/adapters/database.ts +0 -397
- package/src/blobs/index.ts +0 -9
- package/src/blobs/manager.ts +0 -901
- package/src/blobs/migrate.ts +0 -158
- package/src/blobs/types.ts +0 -74
- package/src/bun-sqlite-ambient.d.ts +0 -19
- package/src/bun-sqlite.ts +0 -27
- package/src/clients.ts +0 -22
- package/src/cloudflare/durable-object.ts +0 -289
- package/src/cloudflare/index.ts +0 -22
- package/src/cloudflare/r2.ts +0 -526
- package/src/cloudflare/scope-cache.ts +0 -341
- package/src/cloudflare/sentry.ts +0 -230
- package/src/cloudflare/worker.ts +0 -77
- package/src/commit-integrity.ts +0 -371
- package/src/compaction.ts +0 -77
- package/src/crdt-yjs/index.ts +0 -931
- package/src/d1.ts +0 -16
- package/src/dialect/base.ts +0 -360
- package/src/dialect/helpers.ts +0 -92
- package/src/dialect/index.ts +0 -7
- package/src/dialect/types.ts +0 -247
- package/src/encrypted-crdt.ts +0 -786
- package/src/filesystem/index.ts +0 -262
- package/src/handlers/collection.ts +0 -121
- package/src/handlers/create-handler.ts +0 -1134
- package/src/handlers/index.ts +0 -3
- package/src/handlers/types.ts +0 -403
- package/src/helpers/conflict.ts +0 -64
- package/src/helpers/emitted-change.ts +0 -69
- package/src/helpers/index.ts +0 -12
- package/src/helpers/paginate.ts +0 -82
- package/src/helpers/scope-authorization.ts +0 -27
- package/src/helpers/scope-commit-index.ts +0 -52
- package/src/helpers/scope-strings.ts +0 -101
- package/src/hono/api-key-auth.ts +0 -177
- package/src/hono/audit-redaction.ts +0 -135
- package/src/hono/blobs.ts +0 -851
- package/src/hono/console/gateway.ts +0 -3046
- package/src/hono/console/live-auth.ts +0 -46
- package/src/hono/console/route-descriptor.ts +0 -22
- package/src/hono/console/routes/api-keys.ts +0 -721
- package/src/hono/console/routes/clients.ts +0 -447
- package/src/hono/console/routes/commits.ts +0 -1137
- package/src/hono/console/routes/context.ts +0 -956
- package/src/hono/console/routes/events.ts +0 -669
- package/src/hono/console/routes/maintenance.ts +0 -461
- package/src/hono/console/routes/shared.ts +0 -816
- package/src/hono/console/routes/stats.ts +0 -392
- package/src/hono/console/routes/storage.ts +0 -159
- package/src/hono/console/routes.ts +0 -146
- package/src/hono/console/schema-errors.ts +0 -23
- package/src/hono/console/schemas.ts +0 -914
- package/src/hono/console/types.ts +0 -223
- package/src/hono/console/ui.ts +0 -100
- package/src/hono/create-server.ts +0 -230
- package/src/hono/errors.ts +0 -50
- package/src/hono/index.ts +0 -54
- package/src/hono/openapi.ts +0 -139
- package/src/hono/proxy/connection-manager.ts +0 -340
- package/src/hono/proxy/index.ts +0 -8
- package/src/hono/proxy/routes.ts +0 -272
- package/src/hono/rate-limit.ts +0 -319
- package/src/hono/realtime-sync-packs.ts +0 -354
- package/src/hono/routes/audit.ts +0 -499
- package/src/hono/routes/auth-leases.ts +0 -119
- package/src/hono/routes/combined.ts +0 -583
- package/src/hono/routes/context.ts +0 -1629
- package/src/hono/routes/health.ts +0 -26
- package/src/hono/routes/realtime.ts +0 -808
- package/src/hono/routes/shared.ts +0 -1626
- package/src/hono/routes/snapshots.ts +0 -345
- package/src/hono/routes.ts +0 -68
- package/src/hono/validation.ts +0 -81
- package/src/hono/websocket-origin.ts +0 -131
- package/src/hono/ws.ts +0 -1134
- package/src/libsql.ts +0 -51
- package/src/migrate.ts +0 -20
- package/src/neon.ts +0 -28
- package/src/notify.ts +0 -341
- package/src/pglite.ts +0 -68
- package/src/plugins/index.ts +0 -1
- package/src/plugins/types.ts +0 -144
- package/src/postgres/index.ts +0 -1291
- package/src/proxy/collection.ts +0 -17
- package/src/proxy/handler.ts +0 -159
- package/src/proxy/index.ts +0 -21
- package/src/proxy/mutation-detector.ts +0 -281
- package/src/proxy/oplog.ts +0 -181
- package/src/proxy/types.ts +0 -46
- package/src/realtime/in-memory.ts +0 -33
- package/src/realtime/index.ts +0 -7
- package/src/realtime/types.ts +0 -90
- package/src/relay/bun-types.d.ts +0 -50
- package/src/relay/client-role/forward-engine.ts +0 -355
- package/src/relay/client-role/index.ts +0 -9
- package/src/relay/client-role/pull-engine.ts +0 -329
- package/src/relay/client-role/sequence-mapper.ts +0 -201
- package/src/relay/evaluation/relay-paths.ts +0 -699
- package/src/relay/evaluation/rust-boundary.ts +0 -464
- package/src/relay/index.ts +0 -50
- package/src/relay/migrate.ts +0 -113
- package/src/relay/mode-manager.ts +0 -142
- package/src/relay/realtime.ts +0 -207
- package/src/relay/relay.ts +0 -431
- package/src/relay/schema.ts +0 -171
- package/src/relay/server-role/index.ts +0 -338
- package/src/relay/server-role/pull.ts +0 -43
- package/src/relay/server-role/push.ts +0 -164
- package/src/s3/index.ts +0 -346
- package/src/service-worker/index.ts +0 -773
- package/src/snapshot-artifacts/sqlite-bun.ts +0 -168
- package/src/snapshot-artifacts.ts +0 -896
- package/src/snapshot-chunks/db-metadata.ts +0 -537
- package/src/snapshot-chunks/index.ts +0 -8
- package/src/snapshot-chunks/types.ts +0 -105
- package/src/snapshot-chunks.ts +0 -453
- package/src/sqlite/index.ts +0 -1064
- package/src/sqlite3.ts +0 -137
- package/src/stats.ts +0 -180
- package/src/subscriptions/cache.ts +0 -376
- package/src/subscriptions/index.ts +0 -2
- package/src/subscriptions/resolve.ts +0 -357
- package/src/sync.ts +0 -111
package/dist/pull.js
CHANGED
|
@@ -1,1222 +1,422 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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;
|
|
46
|
-
}
|
|
47
|
-
if (!request.compressions?.includes(SYNC_SNAPSHOT_CHUNK_COMPRESSION)) {
|
|
48
|
-
return null;
|
|
1
|
+
/**
|
|
2
|
+
* Pull: incremental commit delivery, cursors, bootstrap segments
|
|
3
|
+
* (SPEC.md §4, §5).
|
|
4
|
+
*/
|
|
5
|
+
import { decodeRow, encodeRowsSegment, } from '@syncular/core';
|
|
6
|
+
import { clockOf, limitsOf } from './context.js';
|
|
7
|
+
import { scopeDigest } from './scopes.js';
|
|
8
|
+
import { issueSegmentUrl } from './signed-url.js';
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the §5.3 image builder: the host-injected one if present, else the
|
|
11
|
+
* in-tree `buildSqliteImage` on a Bun runtime (dynamic import so `bun:sqlite`
|
|
12
|
+
* is never a static dep of the neutral core), else `undefined` (rows lane).
|
|
13
|
+
* Memoized so the dynamic import happens at most once per process.
|
|
14
|
+
*/
|
|
15
|
+
let cachedDefaultBuilder;
|
|
16
|
+
async function resolveImageBuilder(ctx) {
|
|
17
|
+
if (ctx.sqliteImageBuilder !== undefined)
|
|
18
|
+
return ctx.sqliteImageBuilder;
|
|
19
|
+
if (cachedDefaultBuilder === undefined) {
|
|
20
|
+
const hasBun = globalThis.Bun !== undefined;
|
|
21
|
+
cachedDefaultBuilder = hasBun
|
|
22
|
+
? (await import('./sqlite-image.js')).buildSqliteImage
|
|
23
|
+
: null;
|
|
49
24
|
}
|
|
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
|
-
});
|
|
25
|
+
return cachedDefaultBuilder ?? undefined;
|
|
90
26
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
27
|
+
/** §4.2 accept bitmask. */
|
|
28
|
+
export const ACCEPT_INLINE_ROWS = 1 << 0;
|
|
29
|
+
export const ACCEPT_EXTERNAL_ROWS = 1 << 1;
|
|
30
|
+
export const ACCEPT_SQLITE = 1 << 2;
|
|
31
|
+
export const ACCEPT_SIGNED_URLS = 1 << 3;
|
|
32
|
+
function clamp(value, min, max, dflt) {
|
|
33
|
+
if (value === 0)
|
|
34
|
+
return dflt;
|
|
35
|
+
return Math.min(max, Math.max(min, value));
|
|
99
36
|
}
|
|
100
|
-
|
|
101
|
-
|
|
37
|
+
/** §4.2 defaults and silent clamps (the v1 values). */
|
|
38
|
+
export function clampPullLimits(header) {
|
|
102
39
|
return {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
hashMs: encoded.hashMs,
|
|
40
|
+
limitCommits: clamp(header.limitCommits, 1, 1000, 1000),
|
|
41
|
+
limitSnapshotRows: clamp(header.limitSnapshotRows, 1, 50000, 1000),
|
|
42
|
+
maxSnapshotPages: clamp(header.maxSnapshotPages, 1, 50, 4),
|
|
43
|
+
accept: header.accept,
|
|
108
44
|
};
|
|
109
45
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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`);
|
|
125
|
-
}
|
|
126
|
-
return row;
|
|
127
|
-
}
|
|
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));
|
|
46
|
+
function parseBootstrapToken(raw, table) {
|
|
47
|
+
if (raw === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(raw);
|
|
51
|
+
if (typeof parsed.asOfCommitSeq !== 'number' ||
|
|
52
|
+
!Array.isArray(parsed.tables) ||
|
|
53
|
+
typeof parsed.tableIndex !== 'number' ||
|
|
54
|
+
(parsed.rowCursor !== null && typeof parsed.rowCursor !== 'string') ||
|
|
55
|
+
parsed.tables[0] !== table) {
|
|
56
|
+
return undefined;
|
|
148
57
|
}
|
|
58
|
+
return {
|
|
59
|
+
asOfCommitSeq: parsed.asOfCommitSeq,
|
|
60
|
+
tables: parsed.tables,
|
|
61
|
+
tableIndex: parsed.tableIndex,
|
|
62
|
+
rowCursor: parsed.rowCursor ?? null,
|
|
63
|
+
};
|
|
149
64
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function commitFrame(table, commit) {
|
|
70
|
+
const changes = commit.changes.map((change) => ({
|
|
71
|
+
tableIndex: 0,
|
|
72
|
+
rowId: change.rowId,
|
|
73
|
+
op: change.op,
|
|
74
|
+
...(change.rowVersion !== undefined
|
|
75
|
+
? { rowVersion: change.rowVersion }
|
|
155
76
|
: {}),
|
|
77
|
+
scopes: change.scopes,
|
|
78
|
+
...(change.payload !== undefined ? { row: change.payload } : {}),
|
|
156
79
|
}));
|
|
80
|
+
return {
|
|
81
|
+
type: 'COMMIT',
|
|
82
|
+
commitSeq: commit.commitSeq,
|
|
83
|
+
createdAtMs: commit.createdAtMs,
|
|
84
|
+
actorId: commit.actorId,
|
|
85
|
+
tables: [table],
|
|
86
|
+
changes,
|
|
87
|
+
};
|
|
157
88
|
}
|
|
158
|
-
function
|
|
159
|
-
if (
|
|
160
|
-
return
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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';
|
|
89
|
+
function chunkRows(rows, size) {
|
|
90
|
+
if (rows.length === 0)
|
|
91
|
+
return [];
|
|
92
|
+
const blocks = [];
|
|
93
|
+
for (let i = 0; i < rows.length; i += size) {
|
|
94
|
+
blocks.push(rows.slice(i, i + size));
|
|
170
95
|
}
|
|
171
|
-
return
|
|
96
|
+
return blocks;
|
|
172
97
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
98
|
+
/** §5.4 signed-URL fields for a descriptor, when the client asked (bit 3). */
|
|
99
|
+
async function signedUrlFields(ctx, limits, segmentId, digest, now) {
|
|
100
|
+
if ((limits.accept & ACCEPT_SIGNED_URLS) === 0 || !ctx.signedUrls)
|
|
101
|
+
return {};
|
|
102
|
+
return issueSegmentUrl(ctx.signedUrls, {
|
|
103
|
+
segmentId,
|
|
104
|
+
partition: ctx.partition,
|
|
105
|
+
scopeDigest: digest,
|
|
106
|
+
nowMs: now,
|
|
107
|
+
});
|
|
181
108
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
109
|
+
function segmentRefFrame(record, extra) {
|
|
110
|
+
return {
|
|
111
|
+
type: 'SEGMENT_REF',
|
|
112
|
+
segmentId: record.segmentId,
|
|
113
|
+
mediaType: record.mediaType,
|
|
114
|
+
table: record.table,
|
|
115
|
+
byteLength: record.byteLength,
|
|
116
|
+
rowCount: record.rowCount,
|
|
117
|
+
asOfCommitSeq: record.asOfCommitSeq,
|
|
118
|
+
scopeDigest: record.scopeDigest,
|
|
119
|
+
...(record.rowCursor !== null ? { rowCursor: record.rowCursor } : {}),
|
|
120
|
+
...(record.nextRowCursor !== null
|
|
121
|
+
? { nextRowCursor: record.nextRowCursor }
|
|
122
|
+
: {}),
|
|
123
|
+
...(extra.url !== undefined ? { url: extra.url } : {}),
|
|
124
|
+
...(extra.urlExpiresAtMs !== undefined
|
|
125
|
+
? { urlExpiresAtMs: extra.urlExpiresAtMs }
|
|
126
|
+
: {}),
|
|
127
|
+
};
|
|
198
128
|
}
|
|
199
129
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
130
|
+
* §5.3 sqlite-image lane: only at the start of a table, only when the
|
|
131
|
+
* client advertised bit 2, and only when the snapshot exceeds one rows
|
|
132
|
+
* page. Reuses an unexpired stored image for the same (partition, table,
|
|
133
|
+
* schemaVersion, scope digest, pin) instead of rebuilding — the
|
|
134
|
+
* bootstrap-storm rule. Returns false when the table is not eligible
|
|
135
|
+
* (the rows lane takes over).
|
|
202
136
|
*/
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
137
|
+
async function* sqliteImageSegment(ctx, schema, limits, plan, asOf, digest, trace) {
|
|
138
|
+
const { storage, segments, partition } = ctx;
|
|
139
|
+
const now = clockOf(ctx)();
|
|
140
|
+
const existing = await segments.find({
|
|
141
|
+
partition,
|
|
142
|
+
table: plan.table.name,
|
|
143
|
+
schemaVersion: schema.version,
|
|
144
|
+
mediaType: 'sqlite',
|
|
145
|
+
scopeDigest: digest,
|
|
146
|
+
asOfCommitSeq: asOf,
|
|
147
|
+
}, now);
|
|
148
|
+
if (existing !== undefined) {
|
|
149
|
+
trace?.segments.push({
|
|
150
|
+
mediaType: 'sqlite',
|
|
151
|
+
delivery: 'ref',
|
|
152
|
+
origin: 'reused',
|
|
153
|
+
bytes: existing.byteLength,
|
|
154
|
+
rows: existing.rowCount,
|
|
155
|
+
});
|
|
156
|
+
yield segmentRefFrame(existing, await signedUrlFields(ctx, limits, existing.segmentId, digest, now));
|
|
157
|
+
return true;
|
|
213
158
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
async function delay(ms) {
|
|
222
|
-
await new Promise((resolve) => {
|
|
223
|
-
setTimeout(resolve, ms);
|
|
159
|
+
// Eligibility probe (§5.3): image only when the snapshot exceeds one
|
|
160
|
+
// rows page — smaller tables stay on the (typically inline) rows lane.
|
|
161
|
+
const probe = await storage.scanRows(partition, {
|
|
162
|
+
table: plan.table.name,
|
|
163
|
+
scopeFilter: plan.effective,
|
|
164
|
+
afterRowId: null,
|
|
165
|
+
limit: limits.limitSnapshotRows + 1,
|
|
224
166
|
});
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
167
|
+
if (probe.length <= limits.limitSnapshotRows)
|
|
168
|
+
return false;
|
|
169
|
+
// §5.3: building an image needs a SQLite engine. The host injects the
|
|
170
|
+
// builder through `sqliteImageBuilder`; when omitted we default to the
|
|
171
|
+
// in-tree `buildSqliteImage` ONLY on a Bun runtime, reached by a *dynamic*
|
|
172
|
+
// import so `bun:sqlite` is never a static dependency of the pull path
|
|
173
|
+
// (TODO §4.2 neutrality — enforced by test/runtime-neutrality.test.ts). On
|
|
174
|
+
// Workers/edge (no `Bun`) this yields the rows lane for a bit-2 client — a
|
|
175
|
+
// support floor, not a fallback (§5.3: sqlite is an *accept*, not a demand).
|
|
176
|
+
const buildImage = await resolveImageBuilder(ctx);
|
|
177
|
+
if (buildImage === undefined)
|
|
178
|
+
return false;
|
|
179
|
+
// The probe rows are the snapshot's first page — keep them and scan on
|
|
180
|
+
// from the probe's cursor instead of re-reading the whole prefix (the
|
|
181
|
+
// scan is keyset-ordered by rowId, so the concatenation is exactly the
|
|
182
|
+
// rows a single full scan would return).
|
|
183
|
+
const rows = [...probe];
|
|
184
|
+
let afterRowId = probe[probe.length - 1]?.rowId ?? null;
|
|
185
|
+
for (;;) {
|
|
186
|
+
const scanned = await storage.scanRows(partition, {
|
|
187
|
+
table: plan.table.name,
|
|
188
|
+
scopeFilter: plan.effective,
|
|
189
|
+
afterRowId,
|
|
190
|
+
limit: 50_000,
|
|
191
|
+
});
|
|
192
|
+
rows.push(...scanned);
|
|
193
|
+
const last = scanned[scanned.length - 1];
|
|
194
|
+
if (scanned.length < 50_000 || last === undefined)
|
|
195
|
+
break;
|
|
196
|
+
afterRowId = last.rowId;
|
|
246
197
|
}
|
|
247
|
-
|
|
198
|
+
const bytes = buildImage({
|
|
199
|
+
table: plan.table,
|
|
200
|
+
schemaVersion: schema.version,
|
|
201
|
+
asOfCommitSeq: asOf,
|
|
202
|
+
scopeDigest: digest,
|
|
203
|
+
rows,
|
|
204
|
+
});
|
|
205
|
+
const record = await segments.put({
|
|
206
|
+
partition,
|
|
207
|
+
table: plan.table.name,
|
|
208
|
+
schemaVersion: schema.version,
|
|
209
|
+
mediaType: 'sqlite',
|
|
210
|
+
scopeDigest: digest,
|
|
211
|
+
asOfCommitSeq: asOf,
|
|
212
|
+
rowCount: rows.length,
|
|
213
|
+
rowCursor: null,
|
|
214
|
+
nextRowCursor: null,
|
|
215
|
+
}, bytes, now);
|
|
216
|
+
trace?.segments.push({
|
|
217
|
+
mediaType: 'sqlite',
|
|
218
|
+
delivery: 'ref',
|
|
219
|
+
origin: 'built',
|
|
220
|
+
bytes: record.byteLength,
|
|
221
|
+
rows: record.rowCount,
|
|
222
|
+
});
|
|
223
|
+
yield segmentRefFrame(record, await signedUrlFields(ctx, limits, record.segmentId, digest, now));
|
|
224
|
+
return true;
|
|
248
225
|
}
|
|
249
|
-
function
|
|
250
|
-
|
|
251
|
-
|
|
226
|
+
async function* bootstrapSegments(ctx, schema, limits, plan, asOf, startRowCursor, trace) {
|
|
227
|
+
const { storage, segments, partition } = ctx;
|
|
228
|
+
const serverLimits = limitsOf(ctx);
|
|
229
|
+
const digest = await scopeDigest(plan.effective);
|
|
230
|
+
const now = clockOf(ctx)();
|
|
231
|
+
// §5.3: the sqlite-image lane — whole-table, chosen only at the start
|
|
232
|
+
// of a table (a mid-table resume stays on the rows lane, never
|
|
233
|
+
// switching lanes), and only when the client advertised bit 2.
|
|
234
|
+
// §5.11: a table with any encrypted column is image-INELIGIBLE — an image
|
|
235
|
+
// copies ciphertext wholesale with no per-row decrypt pass, so it MUST be
|
|
236
|
+
// served on the rows lane (which decrypts per row on the client).
|
|
237
|
+
if ((limits.accept & ACCEPT_SQLITE) !== 0 &&
|
|
238
|
+
startRowCursor === null &&
|
|
239
|
+
plan.table.encryptedColumnIndices.length === 0) {
|
|
240
|
+
const imaged = yield* sqliteImageSegment(ctx, schema, limits, plan, asOf, digest, trace);
|
|
241
|
+
if (imaged)
|
|
242
|
+
return { complete: true, rowCursor: null };
|
|
252
243
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
:
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
ctx: args.ctx,
|
|
274
|
-
tableHandler: args.tableHandler,
|
|
275
|
-
subscription: args.subscription,
|
|
276
|
-
changes,
|
|
244
|
+
let rowCursor = startRowCursor;
|
|
245
|
+
for (let page = 0; page < limits.maxSnapshotPages; page++) {
|
|
246
|
+
const scanned = await storage.scanRows(partition, {
|
|
247
|
+
table: plan.table.name,
|
|
248
|
+
scopeFilter: plan.effective,
|
|
249
|
+
afterRowId: rowCursor,
|
|
250
|
+
limit: limits.limitSnapshotRows + 1,
|
|
251
|
+
});
|
|
252
|
+
const pageRows = scanned.slice(0, limits.limitSnapshotRows);
|
|
253
|
+
const hasMore = scanned.length > limits.limitSnapshotRows;
|
|
254
|
+
// §5.2: every row record carries the row's current server_version.
|
|
255
|
+
const decoded = pageRows.map((row) => ({
|
|
256
|
+
serverVersion: row.serverVersion,
|
|
257
|
+
values: decodeRow(plan.table.columns, row.payload),
|
|
258
|
+
}));
|
|
259
|
+
const bytes = encodeRowsSegment({
|
|
260
|
+
table: plan.table.name,
|
|
261
|
+
schemaVersion: schema.version,
|
|
262
|
+
columns: plan.table.columns,
|
|
263
|
+
blocks: chunkRows(decoded, 1000),
|
|
277
264
|
});
|
|
278
|
-
|
|
279
|
-
|
|
265
|
+
const lastRow = pageRows[pageRows.length - 1];
|
|
266
|
+
const nextRowCursor = hasMore && lastRow !== undefined ? lastRow.rowId : null;
|
|
267
|
+
const canInline = (limits.accept & ACCEPT_INLINE_ROWS) !== 0;
|
|
268
|
+
const canExternal = (limits.accept & ACCEPT_EXTERNAL_ROWS) !== 0;
|
|
269
|
+
const inline = canInline &&
|
|
270
|
+
(bytes.length <= serverLimits.inlineSegmentMaxBytes || !canExternal);
|
|
271
|
+
if (inline) {
|
|
272
|
+
trace?.segments.push({
|
|
273
|
+
mediaType: 'rows',
|
|
274
|
+
delivery: 'inline',
|
|
275
|
+
origin: 'built',
|
|
276
|
+
bytes: bytes.length,
|
|
277
|
+
rows: pageRows.length,
|
|
278
|
+
});
|
|
279
|
+
yield { type: 'SEGMENT_INLINE', payload: bytes };
|
|
280
280
|
}
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
else {
|
|
282
|
+
const record = await segments.put({
|
|
283
|
+
partition,
|
|
284
|
+
table: plan.table.name,
|
|
285
|
+
schemaVersion: schema.version,
|
|
286
|
+
mediaType: 'rows',
|
|
287
|
+
scopeDigest: digest,
|
|
288
|
+
asOfCommitSeq: asOf,
|
|
289
|
+
rowCount: pageRows.length,
|
|
290
|
+
rowCursor,
|
|
291
|
+
nextRowCursor,
|
|
292
|
+
}, bytes, now);
|
|
293
|
+
trace?.segments.push({
|
|
294
|
+
mediaType: 'rows',
|
|
295
|
+
delivery: 'ref',
|
|
296
|
+
origin: 'built',
|
|
297
|
+
bytes: record.byteLength,
|
|
298
|
+
rows: record.rowCount,
|
|
299
|
+
});
|
|
300
|
+
yield segmentRefFrame(record, await signedUrlFields(ctx, limits, record.segmentId, digest, now));
|
|
283
301
|
}
|
|
284
|
-
|
|
302
|
+
if (!hasMore)
|
|
303
|
+
return { complete: true, rowCursor: null };
|
|
304
|
+
rowCursor = nextRowCursor;
|
|
285
305
|
}
|
|
286
|
-
return
|
|
306
|
+
return { complete: false, rowCursor };
|
|
287
307
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Produce the `SUB_START … SUB_END` section for one subscription (§1.6),
|
|
310
|
+
* returning the cursor recorded for the retention watermark (§4.5).
|
|
311
|
+
*/
|
|
312
|
+
export async function* subscriptionSection(ctx, schema, limits, plan, maxSeq, horizonSeq, trace) {
|
|
313
|
+
const sub = plan.frame;
|
|
314
|
+
if (plan.status === 'revoked') {
|
|
315
|
+
yield {
|
|
316
|
+
type: 'SUB_START',
|
|
317
|
+
id: sub.id,
|
|
318
|
+
status: 'revoked',
|
|
319
|
+
reasonCode: 'sync.scope_revoked',
|
|
320
|
+
effectiveScopes: {},
|
|
321
|
+
bootstrap: false,
|
|
322
|
+
};
|
|
323
|
+
yield { type: 'SUB_END', nextCursor: sub.cursor };
|
|
324
|
+
return { nextCursor: sub.cursor, active: false };
|
|
325
|
+
}
|
|
326
|
+
const token = parseBootstrapToken(sub.bootstrapState, sub.table);
|
|
327
|
+
// §4.6: a cursor behind the horizon (and not resuming a bootstrap)
|
|
328
|
+
// cannot compute deltas — answer `reset` and echo the cursor.
|
|
329
|
+
if (token === undefined && sub.cursor >= 0 && sub.cursor < horizonSeq) {
|
|
330
|
+
yield {
|
|
331
|
+
type: 'SUB_START',
|
|
332
|
+
id: sub.id,
|
|
333
|
+
status: 'reset',
|
|
334
|
+
reasonCode: 'sync.cursor_expired',
|
|
335
|
+
effectiveScopes: {},
|
|
336
|
+
bootstrap: false,
|
|
337
|
+
};
|
|
338
|
+
yield { type: 'SUB_END', nextCursor: sub.cursor };
|
|
339
|
+
return { nextCursor: sub.cursor, active: false };
|
|
340
|
+
}
|
|
341
|
+
const bootstrapping = token !== undefined || sub.cursor < 0 || sub.cursor > maxSeq;
|
|
342
|
+
if (bootstrapping) {
|
|
343
|
+
// §4.7: resume at the pinned point unless the pin fell behind the
|
|
344
|
+
// horizon (or the token is unusable) — then restart from a fresh pin.
|
|
345
|
+
const resume = token !== undefined && token.asOfCommitSeq >= horizonSeq
|
|
346
|
+
? token
|
|
347
|
+
: undefined;
|
|
348
|
+
const asOf = resume?.asOfCommitSeq ?? maxSeq;
|
|
349
|
+
const startCursor = resume?.rowCursor ?? null;
|
|
350
|
+
yield {
|
|
351
|
+
type: 'SUB_START',
|
|
352
|
+
id: sub.id,
|
|
353
|
+
status: 'active',
|
|
354
|
+
reasonCode: '',
|
|
355
|
+
effectiveScopes: plan.effective,
|
|
356
|
+
bootstrap: true,
|
|
357
|
+
};
|
|
358
|
+
const outcome = yield* bootstrapSegments(ctx, schema, limits, plan, asOf, startCursor, trace);
|
|
359
|
+
if (outcome.complete) {
|
|
360
|
+
yield { type: 'SUB_END', nextCursor: asOf };
|
|
299
361
|
}
|
|
300
362
|
else {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
363
|
+
const nextToken = {
|
|
364
|
+
asOfCommitSeq: asOf,
|
|
365
|
+
tables: [sub.table],
|
|
366
|
+
tableIndex: 0,
|
|
367
|
+
rowCursor: outcome.rowCursor,
|
|
368
|
+
};
|
|
369
|
+
yield {
|
|
370
|
+
type: 'SUB_END',
|
|
371
|
+
nextCursor: asOf,
|
|
372
|
+
bootstrapState: JSON.stringify(nextToken),
|
|
373
|
+
};
|
|
305
374
|
}
|
|
306
|
-
|
|
307
|
-
commitCount += commits.length;
|
|
308
|
-
for (const commit of commits) {
|
|
309
|
-
changeCount += commit.changes?.length ?? 0;
|
|
310
|
-
}
|
|
311
|
-
snapshotPageCount += sub.snapshots?.length ?? 0;
|
|
375
|
+
return { nextCursor: asOf, active: true };
|
|
312
376
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
377
|
+
// Incremental (§4.5): window cursor < commitSeq <= maxSeq, oldest first,
|
|
378
|
+
// cut off at limitCommits total changes, never splitting a commit.
|
|
379
|
+
yield {
|
|
380
|
+
type: 'SUB_START',
|
|
381
|
+
id: sub.id,
|
|
382
|
+
status: 'active',
|
|
383
|
+
reasonCode: '',
|
|
384
|
+
effectiveScopes: plan.effective,
|
|
385
|
+
bootstrap: false,
|
|
321
386
|
};
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
};
|
|
329
|
-
countSyncMetric('sync.server.pull.requests', 1, { attributes });
|
|
330
|
-
distributionSyncMetric('sync.server.pull.duration_ms', durationMs, {
|
|
331
|
-
unit: 'millisecond',
|
|
332
|
-
attributes,
|
|
333
|
-
});
|
|
334
|
-
distributionSyncMetric('sync.server.pull.subscriptions', stats.subscriptionCount, { attributes });
|
|
335
|
-
distributionSyncMetric('sync.server.pull.active_subscriptions', stats.activeSubscriptionCount, { attributes });
|
|
336
|
-
distributionSyncMetric('sync.server.pull.revoked_subscriptions', stats.revokedSubscriptionCount, { attributes });
|
|
337
|
-
distributionSyncMetric('sync.server.pull.bootstrap_subscriptions', stats.bootstrapSubscriptionCount, { attributes });
|
|
338
|
-
distributionSyncMetric('sync.server.pull.commits', stats.commitCount, {
|
|
339
|
-
attributes,
|
|
387
|
+
const commits = await ctx.storage.readCommitWindow(ctx.partition, {
|
|
388
|
+
table: sub.table,
|
|
389
|
+
scopeFilter: plan.effective,
|
|
390
|
+
afterSeq: sub.cursor,
|
|
391
|
+
throughSeq: maxSeq,
|
|
392
|
+
limitChanges: limits.limitCommits + 1,
|
|
340
393
|
});
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const latestByTable = new Map();
|
|
349
|
-
if (tableNames.length === 0) {
|
|
350
|
-
return latestByTable;
|
|
351
|
-
}
|
|
352
|
-
const executor = trx;
|
|
353
|
-
const rows = await executor
|
|
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')
|
|
366
|
-
.execute();
|
|
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;
|
|
374
|
-
}
|
|
375
|
-
export async function pull(args) {
|
|
376
|
-
const { request, dialect } = args;
|
|
377
|
-
const db = args.db;
|
|
378
|
-
const pullPlugins = sortServerPullPlugins(args.plugins);
|
|
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}`;
|
|
389
|
-
const requestedSubscriptionCount = Array.isArray(request.subscriptions)
|
|
390
|
-
? request.subscriptions.length
|
|
391
|
-
: 0;
|
|
392
|
-
const startedAtMs = Date.now();
|
|
393
|
-
return startSyncSpan({
|
|
394
|
-
name: 'sync.server.pull',
|
|
395
|
-
op: 'sync.pull',
|
|
396
|
-
attributes: {
|
|
397
|
-
requested_subscription_count: requestedSubscriptionCount,
|
|
398
|
-
dedupe_rows: request.dedupeRows === true,
|
|
399
|
-
},
|
|
400
|
-
}, async (span) => {
|
|
401
|
-
try {
|
|
402
|
-
// Validate and sanitize request limits
|
|
403
|
-
const limitCommits = sanitizeLimit(request.limitCommits, 1000, 1, 1000);
|
|
404
|
-
const limitSnapshotRows = sanitizeLimit(request.limitSnapshotRows, 1000, 1, 50000);
|
|
405
|
-
const maxSnapshotPages = sanitizeLimit(request.maxSnapshotPages, 4, 1, 50);
|
|
406
|
-
const dedupeRows = request.dedupeRows === true;
|
|
407
|
-
const snapshotChunkEncoding = SYNC_SNAPSHOT_CHUNK_ENCODING;
|
|
408
|
-
const snapshotArtifactSelection = resolveSnapshotArtifactSelection(request.snapshotArtifacts, clientSchemaVersion);
|
|
409
|
-
const snapshotArtifactSchemaVersion = snapshotArtifactSelection?.schemaVersion ?? null;
|
|
410
|
-
// Resolve effective scopes for each subscription
|
|
411
|
-
const resolved = await resolveEffectiveScopesForSubscriptions({
|
|
412
|
-
db,
|
|
413
|
-
auth: args.auth,
|
|
414
|
-
subscriptions: request.subscriptions ?? [],
|
|
415
|
-
handlers: args.handlers,
|
|
416
|
-
scopeCache: args.scopeCache ?? defaultScopeCache,
|
|
417
|
-
});
|
|
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,
|
|
426
|
-
});
|
|
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
|
-
}
|
|
446
|
-
}
|
|
447
|
-
const maxExternalCommitByTable = minSubCursor < Number.MAX_SAFE_INTEGER && minSubCursor >= 0
|
|
448
|
-
? await readLatestExternalCommitByTable(trx, {
|
|
449
|
-
partitionId,
|
|
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: [],
|
|
551
|
-
};
|
|
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,
|
|
636
|
-
partitionId,
|
|
637
|
-
scopeKey: cacheKey,
|
|
638
|
-
scope: bundle.table,
|
|
639
|
-
asOfCommitSeq: effectiveState.asOfCommitSeq,
|
|
640
|
-
rowCursor: bundle.startCursor,
|
|
641
|
-
rowLimit: bundleRowLimit,
|
|
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
|
-
}),
|
|
675
|
-
});
|
|
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
|
-
}
|
|
834
|
-
activeBundle.isLastPage = true;
|
|
835
|
-
await flushSnapshotBundle(activeBundle);
|
|
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;
|
|
847
|
-
}
|
|
848
|
-
if (activeBundle) {
|
|
849
|
-
await flushSnapshotBundle(activeBundle);
|
|
850
|
-
}
|
|
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);
|
|
862
|
-
continue;
|
|
863
|
-
}
|
|
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);
|
|
879
|
-
}
|
|
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}`);
|
|
907
|
-
}
|
|
908
|
-
const incrementalItems = incrementalRows.map((r) => ({
|
|
909
|
-
commitSeq: r.commit_seq,
|
|
910
|
-
createdAt: r.created_at,
|
|
911
|
-
actorId: r.actor_id,
|
|
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,
|
|
1037
|
-
});
|
|
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
|
-
}
|
|
1051
|
-
subResponses.push({
|
|
1052
|
-
id: sub.id,
|
|
1053
|
-
status: 'active',
|
|
1054
|
-
scopes: effectiveScopes,
|
|
1055
|
-
bootstrap: false,
|
|
1056
|
-
nextCursor,
|
|
1057
|
-
...(integrity ? { integrity } : {}),
|
|
1058
|
-
commits,
|
|
1059
|
-
});
|
|
1060
|
-
nextCursors.push(nextCursor);
|
|
1061
|
-
}
|
|
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,
|
|
1073
|
-
};
|
|
1074
|
-
});
|
|
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, {
|
|
1080
|
-
partitionId: pending.cacheLookup.partitionId,
|
|
1081
|
-
scopeKey: pending.cacheLookup.scopeKey,
|
|
1082
|
-
scope: pending.cacheLookup.scope,
|
|
1083
|
-
asOfCommitSeq: pending.cacheLookup.asOfCommitSeq,
|
|
1084
|
-
rowCursor: pending.cacheLookup.rowCursor,
|
|
1085
|
-
rowLimit: pending.cacheLookup.rowLimit,
|
|
1086
|
-
encoding: snapshotChunkEncoding,
|
|
1087
|
-
compression: SYNC_SNAPSHOT_CHUNK_COMPRESSION,
|
|
1088
|
-
});
|
|
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
|
-
});
|
|
1152
|
-
}
|
|
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;
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
throw new Error('Pull transaction retry loop exhausted unexpectedly');
|
|
394
|
+
let delivered = 0;
|
|
395
|
+
let deliveredCommits = 0;
|
|
396
|
+
let lastDeliveredSeq = sub.cursor;
|
|
397
|
+
for (const commit of commits) {
|
|
398
|
+
if (delivered > 0 &&
|
|
399
|
+
delivered + commit.changes.length > limits.limitCommits) {
|
|
400
|
+
break;
|
|
1193
401
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
event: 'sync.server.pull',
|
|
1215
|
-
requestedSubscriptionCount,
|
|
1216
|
-
dedupeRows: request.dedupeRows === true,
|
|
1217
|
-
});
|
|
1218
|
-
throw error;
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
402
|
+
yield commitFrame(sub.table, commit);
|
|
403
|
+
delivered += commit.changes.length;
|
|
404
|
+
deliveredCommits += 1;
|
|
405
|
+
lastDeliveredSeq = commit.commitSeq;
|
|
406
|
+
if (delivered >= limits.limitCommits)
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
const totalReturned = commits.reduce((n, c) => n + c.changes.length, 0);
|
|
410
|
+
// The window is proven exhausted only when the storage lookahead came
|
|
411
|
+
// back under budget (it scanned to `throughSeq`) and every returned
|
|
412
|
+
// commit was delivered.
|
|
413
|
+
const exhausted = totalReturned <= limits.limitCommits && deliveredCommits === commits.length;
|
|
414
|
+
// §4.5: the cursor advances even when no matching changes exist; when
|
|
415
|
+
// the change limit truncated the window it stops at the last fully
|
|
416
|
+
// delivered commit.
|
|
417
|
+
const nextCursor = exhausted
|
|
418
|
+
? Math.max(sub.cursor, maxSeq)
|
|
419
|
+
: Math.max(sub.cursor, lastDeliveredSeq);
|
|
420
|
+
yield { type: 'SUB_END', nextCursor };
|
|
421
|
+
return { nextCursor, active: true };
|
|
1221
422
|
}
|
|
1222
|
-
//# sourceMappingURL=pull.js.map
|