@syncular/server 0.1.2 → 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
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
import { syncError } from './errors.js';
|
|
2
|
+
import { asBytes, asNumber, } from './pg-executor.js';
|
|
3
|
+
import { commitWindowPageSql, deleteRowSql, layoutsOf, migratePayload, parseLayouts, rewritePlan, rewriteRowSql, rewriteValues, SCHEMA_META_DDL_POSTGRES, scanRowPageSql, schemaDdl, selectRowScopesSql, selectRowSql, selectRowsForRewriteSql, upsertSql, upsertValues, } from './relational-rows.js';
|
|
4
|
+
import { matchesEffective } from './scopes.js';
|
|
5
|
+
/**
|
|
6
|
+
* Schema DDL. Applied by `PostgresServerStorage.migrate()` (idempotent).
|
|
7
|
+
*
|
|
8
|
+
* Covering index design (the reason this file exists):
|
|
9
|
+
* - `sync_change_scopes_pk (partition, tbl, var, value, commit_seq)` — the
|
|
10
|
+
* candidate scan for `readCommitWindow` ranges on the (partition, tbl,
|
|
11
|
+
* var, value IN …) prefix and returns `commit_seq` already ascending;
|
|
12
|
+
* - `sync_row_scopes_pk (partition, tbl, var, value, row_id)` — same shape
|
|
13
|
+
* for `scanRows`, ordered by `row_id`.
|
|
14
|
+
* Both are the PRIMARY KEY, so they are the clustering/covering index for
|
|
15
|
+
* their table. No secondary index is needed for the hot path.
|
|
16
|
+
*
|
|
17
|
+
* Blob reference index (§5.9.4) — parity with the SQLite dialect's
|
|
18
|
+
* `sync_blob_refs`:
|
|
19
|
+
* - PRIMARY KEY `(partition, tbl, row_id, blob_id)` — the by-row prefix
|
|
20
|
+
* lets `setBlobRefs` replace a row's set with a single ranged DELETE and
|
|
21
|
+
* the delete path clear it by (partition, tbl, row_id);
|
|
22
|
+
* - the secondary `sync_blob_refs_by_blob (partition, blob_id)` index
|
|
23
|
+
* drives `listRowsReferencingBlob` (the download-authorization candidate
|
|
24
|
+
* set, §5.9.5) as an index range, never a scan. `postgres-explain
|
|
25
|
+
* .test.ts` asserts an `Index` node here too.
|
|
26
|
+
*/
|
|
27
|
+
export const POSTGRES_DDL = `
|
|
28
|
+
CREATE TABLE IF NOT EXISTS sync_partitions(
|
|
29
|
+
partition TEXT PRIMARY KEY,
|
|
30
|
+
max_commit_seq BIGINT NOT NULL DEFAULT 0,
|
|
31
|
+
horizon_seq BIGINT NOT NULL DEFAULT 0
|
|
32
|
+
);
|
|
33
|
+
CREATE TABLE IF NOT EXISTS sync_row_scopes(
|
|
34
|
+
partition TEXT NOT NULL, tbl TEXT NOT NULL,
|
|
35
|
+
var TEXT NOT NULL, value TEXT NOT NULL, row_id TEXT NOT NULL,
|
|
36
|
+
PRIMARY KEY(partition, tbl, var, value, row_id)
|
|
37
|
+
);
|
|
38
|
+
CREATE TABLE IF NOT EXISTS sync_commits(
|
|
39
|
+
partition TEXT NOT NULL, commit_seq BIGINT NOT NULL,
|
|
40
|
+
client_id TEXT NOT NULL, client_commit_id TEXT NOT NULL,
|
|
41
|
+
actor_id TEXT NOT NULL, created_at_ms BIGINT NOT NULL,
|
|
42
|
+
PRIMARY KEY(partition, commit_seq)
|
|
43
|
+
);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS sync_commits_by_time
|
|
45
|
+
ON sync_commits(partition, created_at_ms);
|
|
46
|
+
CREATE TABLE IF NOT EXISTS sync_changes(
|
|
47
|
+
partition TEXT NOT NULL, commit_seq BIGINT NOT NULL, idx INTEGER NOT NULL,
|
|
48
|
+
tbl TEXT NOT NULL, row_id TEXT NOT NULL, op SMALLINT NOT NULL,
|
|
49
|
+
row_version BIGINT, scopes JSONB NOT NULL, payload BYTEA,
|
|
50
|
+
PRIMARY KEY(partition, commit_seq, idx)
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS sync_changes_by_table
|
|
53
|
+
ON sync_changes(partition, commit_seq, tbl, idx);
|
|
54
|
+
CREATE TABLE IF NOT EXISTS sync_change_scopes(
|
|
55
|
+
partition TEXT NOT NULL, tbl TEXT NOT NULL,
|
|
56
|
+
var TEXT NOT NULL, value TEXT NOT NULL, commit_seq BIGINT NOT NULL,
|
|
57
|
+
PRIMARY KEY(partition, tbl, var, value, commit_seq)
|
|
58
|
+
);
|
|
59
|
+
CREATE TABLE IF NOT EXISTS sync_push_results(
|
|
60
|
+
partition TEXT NOT NULL, client_id TEXT NOT NULL,
|
|
61
|
+
client_commit_id TEXT NOT NULL, result JSONB NOT NULL,
|
|
62
|
+
PRIMARY KEY(partition, client_id, client_commit_id)
|
|
63
|
+
);
|
|
64
|
+
CREATE TABLE IF NOT EXISTS sync_clients(
|
|
65
|
+
partition TEXT NOT NULL, client_id TEXT NOT NULL, actor_id TEXT NOT NULL,
|
|
66
|
+
cursor BIGINT NOT NULL, subscriptions JSONB NOT NULL,
|
|
67
|
+
updated_at_ms BIGINT NOT NULL,
|
|
68
|
+
PRIMARY KEY(partition, client_id)
|
|
69
|
+
);
|
|
70
|
+
CREATE TABLE IF NOT EXISTS sync_blob_refs(
|
|
71
|
+
partition TEXT NOT NULL, tbl TEXT NOT NULL, row_id TEXT NOT NULL,
|
|
72
|
+
blob_id TEXT NOT NULL,
|
|
73
|
+
PRIMARY KEY(partition, tbl, row_id, blob_id)
|
|
74
|
+
);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS sync_blob_refs_by_blob
|
|
76
|
+
ON sync_blob_refs(partition, blob_id);
|
|
77
|
+
`;
|
|
78
|
+
function toBase64(bytes) {
|
|
79
|
+
return Buffer.from(bytes).toString('base64');
|
|
80
|
+
}
|
|
81
|
+
function fromBase64(text) {
|
|
82
|
+
return new Uint8Array(Buffer.from(text, 'base64'));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Serialize a push result to a JSON-able object (stored in a JSONB column).
|
|
86
|
+
* `serverRow` bytes are base64-encoded — JSONB cannot hold raw bytes.
|
|
87
|
+
*/
|
|
88
|
+
function serializePushResult(result) {
|
|
89
|
+
return {
|
|
90
|
+
status: result.status,
|
|
91
|
+
...(result.commitSeq !== undefined ? { commitSeq: result.commitSeq } : {}),
|
|
92
|
+
results: result.results.map((record) => {
|
|
93
|
+
if (record.status === 'conflict') {
|
|
94
|
+
return {
|
|
95
|
+
opIndex: record.opIndex,
|
|
96
|
+
status: record.status,
|
|
97
|
+
code: record.code,
|
|
98
|
+
message: record.message,
|
|
99
|
+
serverVersion: record.serverVersion,
|
|
100
|
+
serverRow: toBase64(record.serverRow),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (record.status === 'error') {
|
|
104
|
+
return {
|
|
105
|
+
opIndex: record.opIndex,
|
|
106
|
+
status: record.status,
|
|
107
|
+
code: record.code,
|
|
108
|
+
message: record.message,
|
|
109
|
+
retryable: record.retryable,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return { opIndex: record.opIndex, status: record.status };
|
|
113
|
+
}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function deserializePushResult(value) {
|
|
117
|
+
const parsed = value;
|
|
118
|
+
const results = parsed.results.map((record) => {
|
|
119
|
+
if (record.status === 'conflict') {
|
|
120
|
+
return {
|
|
121
|
+
opIndex: record.opIndex,
|
|
122
|
+
status: 'conflict',
|
|
123
|
+
code: record.code ?? '',
|
|
124
|
+
message: record.message ?? '',
|
|
125
|
+
serverVersion: record.serverVersion ?? 0,
|
|
126
|
+
serverRow: fromBase64(record.serverRow ?? ''),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (record.status === 'error') {
|
|
130
|
+
return {
|
|
131
|
+
opIndex: record.opIndex,
|
|
132
|
+
status: 'error',
|
|
133
|
+
code: record.code ?? '',
|
|
134
|
+
message: record.message ?? '',
|
|
135
|
+
retryable: record.retryable ?? false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return { opIndex: record.opIndex, status: 'applied' };
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
status: parsed.status,
|
|
142
|
+
...(parsed.commitSeq !== undefined ? { commitSeq: parsed.commitSeq } : {}),
|
|
143
|
+
results,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Some drivers return a JSONB column already parsed (pglite, node-postgres);
|
|
148
|
+
* accept a string too for defensiveness.
|
|
149
|
+
*/
|
|
150
|
+
function asJson(value) {
|
|
151
|
+
if (typeof value === 'string')
|
|
152
|
+
return JSON.parse(value);
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
function toStoredRow(record) {
|
|
156
|
+
return {
|
|
157
|
+
rowId: record.row_id,
|
|
158
|
+
serverVersion: asNumber(record.server_version),
|
|
159
|
+
scopes: asJson(record.scopes),
|
|
160
|
+
payload: asBytes(record.payload),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function toStoredChange(record) {
|
|
164
|
+
return {
|
|
165
|
+
table: record.tbl,
|
|
166
|
+
rowId: record.row_id,
|
|
167
|
+
op: asNumber(record.op) === 1 ? 'upsert' : 'delete',
|
|
168
|
+
...(record.row_version !== null && record.row_version !== undefined
|
|
169
|
+
? { rowVersion: asNumber(record.row_version) }
|
|
170
|
+
: {}),
|
|
171
|
+
scopes: asJson(record.scopes),
|
|
172
|
+
...(record.payload !== null && record.payload !== undefined
|
|
173
|
+
? { payload: asBytes(record.payload) }
|
|
174
|
+
: {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Migration rewrite (DESIGN "optional materialization"): keyset-paged walk
|
|
179
|
+
* of a row table inside the migration transaction; when `oldLayout` is
|
|
180
|
+
* given every payload re-encodes under the current columns, and the
|
|
181
|
+
* projection (when materialized) refreshes from the payload either way.
|
|
182
|
+
*/
|
|
183
|
+
async function rewriteRowsOn(q, table, oldLayout) {
|
|
184
|
+
const select = selectRowsForRewriteSql(table, 'postgres');
|
|
185
|
+
const update = rewriteRowSql(table, 'postgres');
|
|
186
|
+
const BATCH = 500;
|
|
187
|
+
let afterPartition = '';
|
|
188
|
+
let afterRowId = '';
|
|
189
|
+
for (;;) {
|
|
190
|
+
const { rows } = await q.query(select, [afterPartition, afterRowId, BATCH]);
|
|
191
|
+
if (rows.length === 0)
|
|
192
|
+
break;
|
|
193
|
+
for (const row of rows) {
|
|
194
|
+
const bytes = asBytes(row.payload);
|
|
195
|
+
const payload = oldLayout !== undefined
|
|
196
|
+
? migratePayload(oldLayout, table, bytes)
|
|
197
|
+
: bytes;
|
|
198
|
+
await q.query(update, rewriteValues(table, row.partition, row.row_id, payload, 'postgres'));
|
|
199
|
+
}
|
|
200
|
+
const last = rows[rows.length - 1];
|
|
201
|
+
if (last === undefined || rows.length < BATCH)
|
|
202
|
+
break;
|
|
203
|
+
afterPartition = last.partition;
|
|
204
|
+
afterRowId = last.row_id;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** Shared read/write query logic, parameterized by the queryable in scope. */
|
|
208
|
+
async function getRowOn(q, compiled, partition, rowId) {
|
|
209
|
+
const { rows } = await q.query(selectRowSql(compiled, 'postgres'), [partition, rowId]);
|
|
210
|
+
const record = rows[0];
|
|
211
|
+
return record === undefined ? undefined : toStoredRow(record);
|
|
212
|
+
}
|
|
213
|
+
async function writeRowOn(q, compiled, partition, row) {
|
|
214
|
+
await q.query(upsertSql(compiled, 'postgres'), upsertValues(compiled, partition, row, 'postgres'));
|
|
215
|
+
await q.query('DELETE FROM sync_row_scopes WHERE partition=$1 AND tbl=$2 AND row_id=$3', [partition, compiled.name, row.rowId]);
|
|
216
|
+
for (const [variable, value] of Object.entries(row.scopes)) {
|
|
217
|
+
await q.query(`INSERT INTO sync_row_scopes(partition, tbl, var, value, row_id)
|
|
218
|
+
VALUES ($1,$2,$3,$4,$5) ON CONFLICT DO NOTHING`, [partition, compiled.name, variable, value, row.rowId]);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
class PostgresTransaction {
|
|
222
|
+
#client;
|
|
223
|
+
#partition;
|
|
224
|
+
#resolveTable;
|
|
225
|
+
#open = true;
|
|
226
|
+
/** Resolves/rejects the `transaction(fn)` wrapper (see `begin`). */
|
|
227
|
+
#resolve;
|
|
228
|
+
#reject;
|
|
229
|
+
constructor(client, partition, resolveTable, resolve, reject) {
|
|
230
|
+
this.#client = client;
|
|
231
|
+
this.#partition = partition;
|
|
232
|
+
this.#resolveTable = resolveTable;
|
|
233
|
+
this.#resolve = resolve;
|
|
234
|
+
this.#reject = reject;
|
|
235
|
+
}
|
|
236
|
+
#assertOpen() {
|
|
237
|
+
if (!this.#open)
|
|
238
|
+
throw new Error('transaction already finished');
|
|
239
|
+
}
|
|
240
|
+
getRow(table, rowId) {
|
|
241
|
+
this.#assertOpen();
|
|
242
|
+
return getRowOn(this.#client, this.#resolveTable(table), this.#partition, rowId);
|
|
243
|
+
}
|
|
244
|
+
async upsertRow(table, row) {
|
|
245
|
+
this.#assertOpen();
|
|
246
|
+
await writeRowOn(this.#client, this.#resolveTable(table), this.#partition, row);
|
|
247
|
+
}
|
|
248
|
+
async deleteRow(table, rowId) {
|
|
249
|
+
this.#assertOpen();
|
|
250
|
+
await this.#client.query(deleteRowSql(this.#resolveTable(table), 'postgres'), [this.#partition, rowId]);
|
|
251
|
+
await this.#client.query('DELETE FROM sync_row_scopes WHERE partition=$1 AND tbl=$2 AND row_id=$3', [this.#partition, table, rowId]);
|
|
252
|
+
// §5.9.4: a deleted row references no blobs.
|
|
253
|
+
await this.#client.query('DELETE FROM sync_blob_refs WHERE partition=$1 AND tbl=$2 AND row_id=$3', [this.#partition, table, rowId]);
|
|
254
|
+
}
|
|
255
|
+
async setBlobRefs(table, rowId, blobIds) {
|
|
256
|
+
this.#assertOpen();
|
|
257
|
+
// Replace the row's reference set atomically inside the commit tx (§5.9.4).
|
|
258
|
+
await this.#client.query('DELETE FROM sync_blob_refs WHERE partition=$1 AND tbl=$2 AND row_id=$3', [this.#partition, table, rowId]);
|
|
259
|
+
for (const blobId of blobIds) {
|
|
260
|
+
await this.#client.query(`INSERT INTO sync_blob_refs(partition, tbl, row_id, blob_id)
|
|
261
|
+
VALUES ($1,$2,$3,$4) ON CONFLICT DO NOTHING`, [this.#partition, table, rowId, blobId]);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async appendCommit(commit) {
|
|
265
|
+
this.#assertOpen();
|
|
266
|
+
const q = this.#client;
|
|
267
|
+
const p = this.#partition;
|
|
268
|
+
// Allocate the next dense commitSeq under a per-partition row lock: the
|
|
269
|
+
// UPDATE … RETURNING serializes concurrent pushes to this partition and
|
|
270
|
+
// never leaves a gap on rollback (see the file header).
|
|
271
|
+
const { rows } = await q.query(`INSERT INTO sync_partitions(partition, max_commit_seq) VALUES ($1, 1)
|
|
272
|
+
ON CONFLICT (partition) DO UPDATE
|
|
273
|
+
SET max_commit_seq = sync_partitions.max_commit_seq + 1
|
|
274
|
+
RETURNING max_commit_seq`, [p]);
|
|
275
|
+
const commitSeq = asNumber(rows[0]?.max_commit_seq);
|
|
276
|
+
await q.query(`INSERT INTO sync_commits(partition, commit_seq, client_id, client_commit_id, actor_id, created_at_ms)
|
|
277
|
+
VALUES ($1,$2,$3,$4,$5,$6)`, [
|
|
278
|
+
p,
|
|
279
|
+
commitSeq,
|
|
280
|
+
commit.clientId,
|
|
281
|
+
commit.clientCommitId,
|
|
282
|
+
commit.actorId,
|
|
283
|
+
commit.createdAtMs,
|
|
284
|
+
]);
|
|
285
|
+
for (let idx = 0; idx < commit.changes.length; idx++) {
|
|
286
|
+
const change = commit.changes[idx];
|
|
287
|
+
if (change === undefined)
|
|
288
|
+
continue;
|
|
289
|
+
await q.query(`INSERT INTO sync_changes(partition, commit_seq, idx, tbl, row_id, op, row_version, scopes, payload)
|
|
290
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`, [
|
|
291
|
+
p,
|
|
292
|
+
commitSeq,
|
|
293
|
+
idx,
|
|
294
|
+
change.table,
|
|
295
|
+
change.rowId,
|
|
296
|
+
change.op === 'upsert' ? 1 : 2,
|
|
297
|
+
change.rowVersion ?? null,
|
|
298
|
+
JSON.stringify(change.scopes),
|
|
299
|
+
change.payload ?? null,
|
|
300
|
+
]);
|
|
301
|
+
for (const [variable, value] of Object.entries(change.scopes)) {
|
|
302
|
+
await q.query(`INSERT INTO sync_change_scopes(partition, tbl, var, value, commit_seq)
|
|
303
|
+
VALUES ($1,$2,$3,$4,$5) ON CONFLICT DO NOTHING`, [p, change.table, variable, value, commitSeq]);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return commitSeq;
|
|
307
|
+
}
|
|
308
|
+
async putPushResult(clientId, clientCommitId, result) {
|
|
309
|
+
this.#assertOpen();
|
|
310
|
+
await this.#client.query(`INSERT INTO sync_push_results(partition, client_id, client_commit_id, result)
|
|
311
|
+
VALUES ($1,$2,$3,$4)
|
|
312
|
+
ON CONFLICT (partition, client_id, client_commit_id) DO UPDATE
|
|
313
|
+
SET result=EXCLUDED.result`, [
|
|
314
|
+
this.#partition,
|
|
315
|
+
clientId,
|
|
316
|
+
clientCommitId,
|
|
317
|
+
JSON.stringify(serializePushResult(result)),
|
|
318
|
+
]);
|
|
319
|
+
}
|
|
320
|
+
async commit() {
|
|
321
|
+
this.#assertOpen();
|
|
322
|
+
this.#open = false;
|
|
323
|
+
// Signal the transaction wrapper to COMMIT; wait for it to actually land.
|
|
324
|
+
this.#resolve();
|
|
325
|
+
await this.#done;
|
|
326
|
+
}
|
|
327
|
+
async rollback() {
|
|
328
|
+
if (!this.#open)
|
|
329
|
+
return;
|
|
330
|
+
this.#open = false;
|
|
331
|
+
this.#reject(new RollbackSignal());
|
|
332
|
+
// Swallow — a rollback is a normal outcome, not an error to the caller.
|
|
333
|
+
await this.#done.catch(() => { });
|
|
334
|
+
}
|
|
335
|
+
/** Set by `begin`: resolves when BEGIN…COMMIT/ROLLBACK has fully landed. */
|
|
336
|
+
#done;
|
|
337
|
+
_attachDone(done) {
|
|
338
|
+
this.#done = done;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/** Internal marker: a caller-requested rollback, not a real failure. */
|
|
342
|
+
class RollbackSignal extends Error {
|
|
343
|
+
constructor() {
|
|
344
|
+
super('rollback');
|
|
345
|
+
this.name = 'RollbackSignal';
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
export class PostgresServerStorage {
|
|
349
|
+
#exec;
|
|
350
|
+
/** Set by `ensureSchema`: app-table lookup for the relational row store. */
|
|
351
|
+
#tables;
|
|
352
|
+
#schemaVersion;
|
|
353
|
+
constructor(exec) {
|
|
354
|
+
this.#exec = exec;
|
|
355
|
+
}
|
|
356
|
+
/** Apply the schema DDL (idempotent). Call once before use. */
|
|
357
|
+
async migrate() {
|
|
358
|
+
// Split on the statement boundary so drivers that reject multi-statement
|
|
359
|
+
// query strings (node-postgres extended protocol) still apply each DDL.
|
|
360
|
+
const statements = POSTGRES_DDL.split(';')
|
|
361
|
+
.map((s) => s.trim())
|
|
362
|
+
.filter((s) => s.length > 0);
|
|
363
|
+
for (const statement of statements) {
|
|
364
|
+
await this.#exec.query(statement);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/** Resolve a table's compiled schema; row operations require `ensureSchema`. */
|
|
368
|
+
table(name) {
|
|
369
|
+
const table = this.#tables?.get(name);
|
|
370
|
+
if (table === undefined) {
|
|
371
|
+
throw new Error(`unknown table ${JSON.stringify(name)} — ensureSchema(schema) must run before row operations`);
|
|
372
|
+
}
|
|
373
|
+
return table;
|
|
374
|
+
}
|
|
375
|
+
async ensureSchema(schema) {
|
|
376
|
+
// Memoized fast path: same instance, same schema version.
|
|
377
|
+
if (this.#schemaVersion === schema.version)
|
|
378
|
+
return;
|
|
379
|
+
await this.migrate();
|
|
380
|
+
await this.#exec.query(SCHEMA_META_DDL_POSTGRES);
|
|
381
|
+
const marker = await this.#exec.query('SELECT schema_version, layouts FROM sync_schema_meta WHERE id=1');
|
|
382
|
+
const stored = marker.rows[0] === undefined
|
|
383
|
+
? undefined
|
|
384
|
+
: asNumber(marker.rows[0].schema_version);
|
|
385
|
+
if (stored !== undefined && stored > schema.version) {
|
|
386
|
+
throw new Error(`stored schema version ${stored} is newer than the configured schema (${schema.version}) — refusing to run an older server against a migrated database`);
|
|
387
|
+
}
|
|
388
|
+
if (stored === undefined || stored < schema.version) {
|
|
389
|
+
// Introspect existing app tables, apply the migration subset
|
|
390
|
+
// (CREATE TABLE / ADD COLUMN / CREATE INDEX), then rewrite stored
|
|
391
|
+
// rows (payload re-encode for layout changes and/or projection
|
|
392
|
+
// backfill for flipped-on materialization) — all inside one
|
|
393
|
+
// transaction (Postgres DDL is transactional — a failed bump leaves
|
|
394
|
+
// no half-state).
|
|
395
|
+
const layouts = parseLayouts(typeof marker.rows[0]?.layouts === 'string'
|
|
396
|
+
? marker.rows[0].layouts
|
|
397
|
+
: undefined);
|
|
398
|
+
await this.#exec.transaction(async (client) => {
|
|
399
|
+
const existing = new Map();
|
|
400
|
+
for (const table of schema.tables.values()) {
|
|
401
|
+
const { rows } = await client.query(`SELECT column_name FROM information_schema.columns
|
|
402
|
+
WHERE table_schema = current_schema() AND table_name = $1`, [table.name]);
|
|
403
|
+
if (rows.length > 0) {
|
|
404
|
+
existing.set(table.name, new Set(rows.map((r) => r.column_name)));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
for (const statement of schemaDdl(schema, existing, 'postgres')) {
|
|
408
|
+
await client.query(statement);
|
|
409
|
+
}
|
|
410
|
+
for (const table of schema.tables.values()) {
|
|
411
|
+
const oldLayout = layouts[table.name];
|
|
412
|
+
const plan = rewritePlan(table, oldLayout, existing.get(table.name));
|
|
413
|
+
if (!plan.migrate && !plan.backfill)
|
|
414
|
+
continue;
|
|
415
|
+
await rewriteRowsOn(client, table, plan.migrate ? oldLayout : undefined);
|
|
416
|
+
}
|
|
417
|
+
await client.query(`INSERT INTO sync_schema_meta(id, schema_version, layouts) VALUES (1, $1, $2)
|
|
418
|
+
ON CONFLICT (id) DO UPDATE
|
|
419
|
+
SET schema_version=EXCLUDED.schema_version, layouts=EXCLUDED.layouts`, [schema.version, layoutsOf(schema)]);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
this.#tables = schema.tables;
|
|
423
|
+
this.#schemaVersion = schema.version;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Open a real Postgres transaction. The push handler drives the returned
|
|
427
|
+
* `StorageTransaction` imperatively (getRow/upsert/…/commit), but the
|
|
428
|
+
* driver seam models a transaction as a callback scope. We bridge the two:
|
|
429
|
+
* `transaction(fn)` blocks inside `fn` on a promise that the imperative
|
|
430
|
+
* `commit()`/`rollback()` resolves/rejects, so BEGIN…COMMIT wraps exactly
|
|
431
|
+
* the handler's writes on one pinned connection.
|
|
432
|
+
*/
|
|
433
|
+
async begin(partition) {
|
|
434
|
+
let resolveReady;
|
|
435
|
+
const ready = new Promise((r) => {
|
|
436
|
+
resolveReady = r;
|
|
437
|
+
});
|
|
438
|
+
let resolveScope;
|
|
439
|
+
let rejectScope;
|
|
440
|
+
const scope = new Promise((res, rej) => {
|
|
441
|
+
resolveScope = res;
|
|
442
|
+
rejectScope = rej;
|
|
443
|
+
});
|
|
444
|
+
const done = this.#exec
|
|
445
|
+
.transaction(async (client) => {
|
|
446
|
+
const tx = new PostgresTransaction(client, partition, (name) => this.table(name), resolveScope, rejectScope);
|
|
447
|
+
resolveReady(tx);
|
|
448
|
+
// Hold the transaction open until commit()/rollback() settles `scope`.
|
|
449
|
+
await scope;
|
|
450
|
+
})
|
|
451
|
+
.catch((error) => {
|
|
452
|
+
if (error instanceof RollbackSignal)
|
|
453
|
+
return;
|
|
454
|
+
throw error;
|
|
455
|
+
});
|
|
456
|
+
const tx = await ready;
|
|
457
|
+
tx._attachDone(done);
|
|
458
|
+
return tx;
|
|
459
|
+
}
|
|
460
|
+
async getMaxCommitSeq(partition) {
|
|
461
|
+
const { rows } = await this.#exec.query('SELECT max_commit_seq FROM sync_partitions WHERE partition=$1', [partition]);
|
|
462
|
+
return rows[0] === undefined ? 0 : asNumber(rows[0].max_commit_seq);
|
|
463
|
+
}
|
|
464
|
+
async getHorizonSeq(partition) {
|
|
465
|
+
const { rows } = await this.#exec.query('SELECT horizon_seq FROM sync_partitions WHERE partition=$1', [partition]);
|
|
466
|
+
return rows[0] === undefined ? 0 : asNumber(rows[0].horizon_seq);
|
|
467
|
+
}
|
|
468
|
+
async setHorizonSeq(partition, seq) {
|
|
469
|
+
await this.#exec.query(`INSERT INTO sync_partitions(partition, horizon_seq) VALUES ($1,$2)
|
|
470
|
+
ON CONFLICT (partition) DO UPDATE SET horizon_seq=EXCLUDED.horizon_seq`, [partition, seq]);
|
|
471
|
+
}
|
|
472
|
+
async pruneCommitsThrough(partition, seq) {
|
|
473
|
+
const removed = await this.#exec.query('DELETE FROM sync_commits WHERE partition=$1 AND commit_seq<=$2', [partition, seq]);
|
|
474
|
+
await this.#exec.query('DELETE FROM sync_changes WHERE partition=$1 AND commit_seq<=$2', [partition, seq]);
|
|
475
|
+
await this.#exec.query('DELETE FROM sync_change_scopes WHERE partition=$1 AND commit_seq<=$2', [partition, seq]);
|
|
476
|
+
return removed.rowCount;
|
|
477
|
+
}
|
|
478
|
+
async getCommitSeqBefore(partition, createdBeforeMs) {
|
|
479
|
+
const { rows } = await this.#exec.query('SELECT max(commit_seq) AS seq FROM sync_commits WHERE partition=$1 AND created_at_ms<$2', [partition, createdBeforeMs]);
|
|
480
|
+
const seq = rows[0]?.seq;
|
|
481
|
+
return seq === null || seq === undefined ? 0 : asNumber(seq);
|
|
482
|
+
}
|
|
483
|
+
getRow(partition, table, rowId) {
|
|
484
|
+
return getRowOn(this.#exec, this.table(table), partition, rowId);
|
|
485
|
+
}
|
|
486
|
+
async getPushResult(partition, clientId, clientCommitId) {
|
|
487
|
+
const { rows } = await this.#exec.query('SELECT result FROM sync_push_results WHERE partition=$1 AND client_id=$2 AND client_commit_id=$3', [partition, clientId, clientCommitId]);
|
|
488
|
+
if (rows[0] === undefined)
|
|
489
|
+
return undefined;
|
|
490
|
+
try {
|
|
491
|
+
return deserializePushResult(asJson(rows[0].result));
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
throw syncError('sync.idempotency_cache_miss', 'persisted push result unreadable (§6.3)');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async readCommitWindow(partition, query) {
|
|
498
|
+
const variables = Object.keys(query.scopeFilter).sort();
|
|
499
|
+
const firstVariable = variables[0];
|
|
500
|
+
if (firstVariable === undefined)
|
|
501
|
+
return [];
|
|
502
|
+
const firstValues = query.scopeFilter[firstVariable] ?? [];
|
|
503
|
+
if (firstValues.length === 0)
|
|
504
|
+
return [];
|
|
505
|
+
// Candidates via the inverted index (one variable): the (partition, tbl,
|
|
506
|
+
// var, value, commit_seq) PK makes the candidate subquery an index range
|
|
507
|
+
// scan returning commit_seq already ascending (see postgres-explain
|
|
508
|
+
// .test.ts), LEFT JOINed to the commit meta + the table's changes — one
|
|
509
|
+
// round trip per page, never two per candidate (see
|
|
510
|
+
// `commitWindowPageSql`). Exact multi-variable verification against the
|
|
511
|
+
// stored scope map happens below.
|
|
512
|
+
const sql = commitWindowPageSql(firstValues.length, 'postgres');
|
|
513
|
+
const commits = [];
|
|
514
|
+
let deliveredChanges = 0;
|
|
515
|
+
let afterSeq = query.afterSeq;
|
|
516
|
+
const batchSize = Math.max(64, query.limitChanges);
|
|
517
|
+
while (deliveredChanges < query.limitChanges) {
|
|
518
|
+
const { rows: records } = await this.#exec.query(sql, [
|
|
519
|
+
partition,
|
|
520
|
+
query.table,
|
|
521
|
+
firstVariable,
|
|
522
|
+
...firstValues,
|
|
523
|
+
afterSeq,
|
|
524
|
+
query.throughSeq,
|
|
525
|
+
batchSize,
|
|
526
|
+
]);
|
|
527
|
+
if (records.length === 0)
|
|
528
|
+
break;
|
|
529
|
+
// Fold the page: rows arrive ordered (commit_seq, idx); consecutive
|
|
530
|
+
// rows with the same commit_seq are one candidate. Every candidate
|
|
531
|
+
// advances the cursor — including vanished commits (NULL meta, LEFT
|
|
532
|
+
// JOIN contract) and commits whose changes all fail the exact
|
|
533
|
+
// multi-variable match.
|
|
534
|
+
let candidateCount = 0;
|
|
535
|
+
let i = 0;
|
|
536
|
+
while (i < records.length) {
|
|
537
|
+
const head = records[i];
|
|
538
|
+
if (head === undefined)
|
|
539
|
+
break;
|
|
540
|
+
const commitSeq = asNumber(head.commit_seq);
|
|
541
|
+
candidateCount += 1;
|
|
542
|
+
afterSeq = commitSeq;
|
|
543
|
+
const changes = [];
|
|
544
|
+
for (; i < records.length; i++) {
|
|
545
|
+
const record = records[i];
|
|
546
|
+
if (record === undefined || asNumber(record.commit_seq) !== commitSeq)
|
|
547
|
+
break;
|
|
548
|
+
// NULL tbl: a candidate with no change rows for the table.
|
|
549
|
+
if (record.tbl === null || record.row_id === null)
|
|
550
|
+
continue;
|
|
551
|
+
const change = toStoredChange({
|
|
552
|
+
tbl: record.tbl,
|
|
553
|
+
row_id: record.row_id,
|
|
554
|
+
op: record.op,
|
|
555
|
+
row_version: record.row_version,
|
|
556
|
+
scopes: record.scopes,
|
|
557
|
+
payload: record.payload,
|
|
558
|
+
});
|
|
559
|
+
if (matchesEffective(change.scopes, query.scopeFilter)) {
|
|
560
|
+
changes.push(change);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// NULL meta: the commit vanished — cursor advanced, nothing emitted.
|
|
564
|
+
if (head.actor_id === null)
|
|
565
|
+
continue;
|
|
566
|
+
if (changes.length === 0)
|
|
567
|
+
continue;
|
|
568
|
+
commits.push({
|
|
569
|
+
commitSeq,
|
|
570
|
+
createdAtMs: asNumber(head.created_at_ms),
|
|
571
|
+
actorId: head.actor_id,
|
|
572
|
+
changes,
|
|
573
|
+
});
|
|
574
|
+
deliveredChanges += changes.length;
|
|
575
|
+
if (deliveredChanges >= query.limitChanges)
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
if (deliveredChanges >= query.limitChanges)
|
|
579
|
+
break;
|
|
580
|
+
if (candidateCount < batchSize)
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
return commits;
|
|
584
|
+
}
|
|
585
|
+
async scanRows(partition, query) {
|
|
586
|
+
const variables = Object.keys(query.scopeFilter).sort();
|
|
587
|
+
const firstVariable = variables[0];
|
|
588
|
+
if (firstVariable === undefined)
|
|
589
|
+
return [];
|
|
590
|
+
const firstValues = query.scopeFilter[firstVariable] ?? [];
|
|
591
|
+
if (firstValues.length === 0)
|
|
592
|
+
return [];
|
|
593
|
+
// Candidates via the inverted index (ordered + LIMITed at the covering
|
|
594
|
+
// PK) LEFT JOINed to the row table — one round trip per page, never one
|
|
595
|
+
// per row (see `scanRowPageSql`). Exact multi-variable verification
|
|
596
|
+
// against the stored scope map happens below.
|
|
597
|
+
const sql = scanRowPageSql(this.table(query.table), firstValues.length, 'postgres');
|
|
598
|
+
const rows = [];
|
|
599
|
+
let afterRowId = query.afterRowId ?? '';
|
|
600
|
+
const batchSize = Math.max(64, query.limit);
|
|
601
|
+
while (rows.length < query.limit) {
|
|
602
|
+
const { rows: records } = await this.#exec.query(sql, [
|
|
603
|
+
partition,
|
|
604
|
+
query.table,
|
|
605
|
+
firstVariable,
|
|
606
|
+
...firstValues,
|
|
607
|
+
afterRowId,
|
|
608
|
+
batchSize,
|
|
609
|
+
]);
|
|
610
|
+
if (records.length === 0)
|
|
611
|
+
break;
|
|
612
|
+
for (const record of records) {
|
|
613
|
+
afterRowId = record.row_id;
|
|
614
|
+
// NULL payload: an index candidate whose row vanished — it still
|
|
615
|
+
// advances the keyset cursor (LEFT JOIN contract) but yields no row.
|
|
616
|
+
if (record.payload === null || record.payload === undefined)
|
|
617
|
+
continue;
|
|
618
|
+
const stored = toStoredRow(record);
|
|
619
|
+
if (!matchesEffective(stored.scopes, query.scopeFilter))
|
|
620
|
+
continue;
|
|
621
|
+
rows.push(stored);
|
|
622
|
+
if (rows.length >= query.limit)
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
if (records.length < batchSize)
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
return rows;
|
|
629
|
+
}
|
|
630
|
+
async getClientRecord(partition, clientId) {
|
|
631
|
+
const { rows } = await this.#exec.query('SELECT client_id, actor_id, cursor, subscriptions, updated_at_ms FROM sync_clients WHERE partition=$1 AND client_id=$2', [partition, clientId]);
|
|
632
|
+
const record = rows[0];
|
|
633
|
+
if (record === undefined)
|
|
634
|
+
return undefined;
|
|
635
|
+
return {
|
|
636
|
+
clientId: record.client_id,
|
|
637
|
+
actorId: record.actor_id,
|
|
638
|
+
cursor: asNumber(record.cursor),
|
|
639
|
+
updatedAtMs: asNumber(record.updated_at_ms),
|
|
640
|
+
subscriptions: asJson(record.subscriptions),
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
async putClientRecord(partition, record) {
|
|
644
|
+
await this.#exec.query(`INSERT INTO sync_clients(partition, client_id, actor_id, cursor, subscriptions, updated_at_ms)
|
|
645
|
+
VALUES ($1,$2,$3,$4,$5,$6)
|
|
646
|
+
ON CONFLICT (partition, client_id) DO UPDATE
|
|
647
|
+
SET actor_id=EXCLUDED.actor_id, cursor=EXCLUDED.cursor,
|
|
648
|
+
subscriptions=EXCLUDED.subscriptions,
|
|
649
|
+
updated_at_ms=EXCLUDED.updated_at_ms`, [
|
|
650
|
+
partition,
|
|
651
|
+
record.clientId,
|
|
652
|
+
record.actorId,
|
|
653
|
+
record.cursor,
|
|
654
|
+
JSON.stringify(record.subscriptions),
|
|
655
|
+
record.updatedAtMs,
|
|
656
|
+
]);
|
|
657
|
+
}
|
|
658
|
+
async listClientCursors(partition) {
|
|
659
|
+
const { rows } = await this.#exec.query('SELECT client_id, cursor, updated_at_ms FROM sync_clients WHERE partition=$1', [partition]);
|
|
660
|
+
return rows.map((r) => ({
|
|
661
|
+
clientId: r.client_id,
|
|
662
|
+
cursor: asNumber(r.cursor),
|
|
663
|
+
updatedAtMs: asNumber(r.updated_at_ms),
|
|
664
|
+
}));
|
|
665
|
+
}
|
|
666
|
+
async listRowsReferencingBlob(partition, blobId) {
|
|
667
|
+
// Candidate rows via the by-blob index (§5.9.4/§5.9.5); each row's
|
|
668
|
+
// stored scopes come from sync_rows for the §3.4 authorization test.
|
|
669
|
+
const { rows: refs } = await this.#exec.query('SELECT tbl, row_id FROM sync_blob_refs WHERE partition=$1 AND blob_id=$2', [partition, blobId]);
|
|
670
|
+
const out = [];
|
|
671
|
+
for (const ref of refs) {
|
|
672
|
+
const compiled = this.#tables?.get(ref.tbl);
|
|
673
|
+
if (compiled === undefined)
|
|
674
|
+
continue; // table no longer in the schema
|
|
675
|
+
const { rows } = await this.#exec.query(selectRowScopesSql(compiled, 'postgres'), [partition, ref.row_id]);
|
|
676
|
+
const row = rows[0];
|
|
677
|
+
if (row === undefined)
|
|
678
|
+
continue;
|
|
679
|
+
out.push({
|
|
680
|
+
table: ref.tbl,
|
|
681
|
+
rowId: ref.row_id,
|
|
682
|
+
scopes: asJson(row.scopes),
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
return out;
|
|
686
|
+
}
|
|
687
|
+
async listReferencedBlobIds(partition) {
|
|
688
|
+
const { rows } = await this.#exec.query('SELECT DISTINCT blob_id FROM sync_blob_refs WHERE partition=$1', [partition]);
|
|
689
|
+
return rows.map((r) => r.blob_id);
|
|
690
|
+
}
|
|
691
|
+
// -- admin/console read surface (TODO §2.5) --------------------------------
|
|
692
|
+
async listClientRecords(partition) {
|
|
693
|
+
const { rows } = await this.#exec.query('SELECT client_id, actor_id, cursor, subscriptions, updated_at_ms FROM sync_clients WHERE partition=$1 ORDER BY updated_at_ms DESC', [partition]);
|
|
694
|
+
return rows.map((r) => ({
|
|
695
|
+
clientId: r.client_id,
|
|
696
|
+
actorId: r.actor_id,
|
|
697
|
+
cursor: asNumber(r.cursor),
|
|
698
|
+
updatedAtMs: asNumber(r.updated_at_ms),
|
|
699
|
+
subscriptions: (typeof r.subscriptions === 'string'
|
|
700
|
+
? JSON.parse(r.subscriptions)
|
|
701
|
+
: r.subscriptions),
|
|
702
|
+
}));
|
|
703
|
+
}
|
|
704
|
+
async listCommitMetadata(partition, query) {
|
|
705
|
+
const { rows } = query.table
|
|
706
|
+
? await this.#exec.query(`SELECT c.commit_seq, c.client_id, c.client_commit_id, c.actor_id, c.created_at_ms
|
|
707
|
+
FROM sync_commits c
|
|
708
|
+
WHERE c.partition=$1 AND c.commit_seq>$2
|
|
709
|
+
AND EXISTS (SELECT 1 FROM sync_changes ch
|
|
710
|
+
WHERE ch.partition=c.partition AND ch.commit_seq=c.commit_seq AND ch.tbl=$3)
|
|
711
|
+
ORDER BY c.commit_seq DESC LIMIT $4`, [partition, query.afterSeq, query.table, query.limit])
|
|
712
|
+
: await this.#exec.query(`SELECT commit_seq, client_id, client_commit_id, actor_id, created_at_ms
|
|
713
|
+
FROM sync_commits
|
|
714
|
+
WHERE partition=$1 AND commit_seq>$2
|
|
715
|
+
ORDER BY commit_seq DESC LIMIT $3`, [partition, query.afterSeq, query.limit]);
|
|
716
|
+
const out = [];
|
|
717
|
+
for (const row of rows) {
|
|
718
|
+
const commitSeq = asNumber(row.commit_seq);
|
|
719
|
+
const changes = await this.#exec.query('SELECT tbl, count(*) AS n FROM sync_changes WHERE partition=$1 AND commit_seq=$2 GROUP BY tbl', [partition, commitSeq]);
|
|
720
|
+
out.push({
|
|
721
|
+
commitSeq,
|
|
722
|
+
clientId: row.client_id,
|
|
723
|
+
clientCommitId: row.client_commit_id,
|
|
724
|
+
actorId: row.actor_id,
|
|
725
|
+
createdAtMs: asNumber(row.created_at_ms),
|
|
726
|
+
changeCount: changes.rows.reduce((sum, c) => sum + asNumber(c.n), 0),
|
|
727
|
+
tables: changes.rows.map((c) => c.tbl),
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
return out;
|
|
731
|
+
}
|
|
732
|
+
async scopeActivity(partition, query) {
|
|
733
|
+
const { rows } = await this.#exec.query(`SELECT DISTINCT commit_seq, tbl FROM sync_change_scopes
|
|
734
|
+
WHERE partition=$1 AND var=$2 AND value=$3
|
|
735
|
+
ORDER BY commit_seq DESC LIMIT $4`, [partition, query.variable, query.value, query.limit]);
|
|
736
|
+
const out = [];
|
|
737
|
+
for (const row of rows) {
|
|
738
|
+
const commitSeq = asNumber(row.commit_seq);
|
|
739
|
+
const meta = await this.#exec.query('SELECT actor_id, created_at_ms FROM sync_commits WHERE partition=$1 AND commit_seq=$2', [partition, commitSeq]);
|
|
740
|
+
const metaRow = meta.rows[0];
|
|
741
|
+
if (metaRow === undefined)
|
|
742
|
+
continue;
|
|
743
|
+
const count = await this.#exec.query('SELECT count(*) AS n FROM sync_changes WHERE partition=$1 AND commit_seq=$2 AND tbl=$3', [partition, commitSeq, row.tbl]);
|
|
744
|
+
out.push({
|
|
745
|
+
commitSeq,
|
|
746
|
+
table: row.tbl,
|
|
747
|
+
createdAtMs: asNumber(metaRow.created_at_ms),
|
|
748
|
+
actorId: metaRow.actor_id,
|
|
749
|
+
changeCount: count.rows[0] === undefined ? 0 : asNumber(count.rows[0].n),
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
return out;
|
|
753
|
+
}
|
|
754
|
+
async getRowScopes(partition, table, rowId) {
|
|
755
|
+
const { rows } = await this.#exec.query(selectRowScopesSql(this.table(table), 'postgres'), [partition, rowId]);
|
|
756
|
+
const row = rows[0];
|
|
757
|
+
if (row === undefined)
|
|
758
|
+
return undefined;
|
|
759
|
+
return {
|
|
760
|
+
serverVersion: asNumber(row.server_version),
|
|
761
|
+
scopes: (typeof row.scopes === 'string'
|
|
762
|
+
? JSON.parse(row.scopes)
|
|
763
|
+
: row.scopes),
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
}
|