@syncular/server 0.1.3 → 0.2.1
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,514 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relational current-row storage (DESIGN-relational-server-storage.md).
|
|
3
|
+
*
|
|
4
|
+
* Every synced table is a REAL table in the server database — the app's
|
|
5
|
+
* columns with proper type affinities, queryable with plain SQL/joins/BI —
|
|
6
|
+
* plus five `_sync_*` meta columns:
|
|
7
|
+
*
|
|
8
|
+
* _sync_partition TEXT partition key (multi-partition servers
|
|
9
|
+
* share one database; same app PK in two
|
|
10
|
+
* partitions must coexist)
|
|
11
|
+
* _sync_row_id TEXT the change rowId (§2.2) — the string
|
|
12
|
+
* rendering of the app PK. The storage key
|
|
13
|
+
* stays TEXT so lookup and scan-pagination
|
|
14
|
+
* semantics are byte-identical to the old
|
|
15
|
+
* generic store regardless of the PK's type.
|
|
16
|
+
* _sync_server_version INTEGER §2.2 server version
|
|
17
|
+
* _sync_scopes TEXT/JSONB the stored-scope map the §3.4 authz read
|
|
18
|
+
* consumes (also present as real columns;
|
|
19
|
+
* the map is cheaper to read back)
|
|
20
|
+
* _sync_payload BLOB/BYTEA the verbatim row-codec bytes — the wire
|
|
21
|
+
* source of truth. The serve path reads
|
|
22
|
+
* THIS, never re-encodes from typed columns,
|
|
23
|
+
* so sync round-trips are byte-identical by
|
|
24
|
+
* construction. Typed columns are a
|
|
25
|
+
* queryable projection.
|
|
26
|
+
*
|
|
27
|
+
* PRIMARY KEY (_sync_partition, _sync_row_id).
|
|
28
|
+
*
|
|
29
|
+
* The typed columns are written from `decodeRow(payload)` in the same
|
|
30
|
+
* statement as the payload, so projection and payload cannot drift except
|
|
31
|
+
* through a codec bug — which the round-trip contract tests would catch.
|
|
32
|
+
*
|
|
33
|
+
* Dialect notes:
|
|
34
|
+
* - postgres maps `json` → JSONB (queryable: `->>`, GIN). JSONB normalizes
|
|
35
|
+
* bytes, which is safe because the wire never reads typed columns.
|
|
36
|
+
* A json value that fails to parse binds NULL on postgres (the payload
|
|
37
|
+
* still holds it verbatim); sqlite stores the text as-is.
|
|
38
|
+
* - D1's JSON transport represents integers as JS doubles; app integer
|
|
39
|
+
* columns beyond 2^53 lose precision in the PROJECTION only — the
|
|
40
|
+
* payload is exact, sync is unaffected.
|
|
41
|
+
* - Migration-added columns (ALTER TABLE ADD COLUMN) are always nullable at
|
|
42
|
+
* the DB layer: SQLite cannot add a NOT NULL column without a default, and
|
|
43
|
+
* the row codec — not the DB — is the type authority. Fresh CREATEs carry
|
|
44
|
+
* NOT NULL per the schema.
|
|
45
|
+
*/
|
|
46
|
+
import { decodeRow, encodeRow, } from '@syncular/core';
|
|
47
|
+
export const SYNC_PARTITION_COLUMN = '_sync_partition';
|
|
48
|
+
export const SYNC_ROW_ID_COLUMN = '_sync_row_id';
|
|
49
|
+
export const SYNC_VERSION_COLUMN = '_sync_server_version';
|
|
50
|
+
export const SYNC_SCOPES_COLUMN = '_sync_scopes';
|
|
51
|
+
export const SYNC_PAYLOAD_COLUMN = '_sync_payload';
|
|
52
|
+
/** SQL-standard identifier quoting (both dialects). */
|
|
53
|
+
export function quoteIdent(name) {
|
|
54
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
55
|
+
}
|
|
56
|
+
/** §5.3-style type affinities, per dialect (DESIGN type-mapping table). */
|
|
57
|
+
export function columnSqlType(column, dialect) {
|
|
58
|
+
if (dialect === 'postgres') {
|
|
59
|
+
switch (column.type) {
|
|
60
|
+
case 'string':
|
|
61
|
+
case 'blob_ref':
|
|
62
|
+
return 'TEXT';
|
|
63
|
+
case 'json':
|
|
64
|
+
return 'JSONB';
|
|
65
|
+
case 'integer':
|
|
66
|
+
return 'BIGINT';
|
|
67
|
+
case 'float':
|
|
68
|
+
return 'DOUBLE PRECISION';
|
|
69
|
+
case 'boolean':
|
|
70
|
+
return 'BOOLEAN';
|
|
71
|
+
case 'bytes':
|
|
72
|
+
case 'crdt':
|
|
73
|
+
return 'BYTEA';
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`unsupported column type: ${String(column.type)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
switch (column.type) {
|
|
79
|
+
case 'string':
|
|
80
|
+
case 'json':
|
|
81
|
+
case 'blob_ref':
|
|
82
|
+
return 'TEXT';
|
|
83
|
+
case 'integer':
|
|
84
|
+
return 'INTEGER';
|
|
85
|
+
case 'float':
|
|
86
|
+
return 'REAL';
|
|
87
|
+
case 'boolean':
|
|
88
|
+
return 'INTEGER';
|
|
89
|
+
case 'bytes':
|
|
90
|
+
case 'crdt':
|
|
91
|
+
return 'BLOB';
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`unsupported column type: ${String(column.type)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The full column list of a relational row table, in INSERT order:
|
|
98
|
+
* partition, row id, app columns (schema order; only when the table is
|
|
99
|
+
* materialized), version, scopes, payload.
|
|
100
|
+
*/
|
|
101
|
+
export function tableColumnNames(table) {
|
|
102
|
+
return [
|
|
103
|
+
SYNC_PARTITION_COLUMN,
|
|
104
|
+
SYNC_ROW_ID_COLUMN,
|
|
105
|
+
...(table.materialize ? table.columns.map((column) => column.name) : []),
|
|
106
|
+
SYNC_VERSION_COLUMN,
|
|
107
|
+
SYNC_SCOPES_COLUMN,
|
|
108
|
+
SYNC_PAYLOAD_COLUMN,
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
/** CREATE TABLE IF NOT EXISTS for one app table. */
|
|
112
|
+
export function createTableDdl(table, dialect) {
|
|
113
|
+
const versionType = dialect === 'postgres' ? 'BIGINT' : 'INTEGER';
|
|
114
|
+
const scopesType = dialect === 'postgres' ? 'JSONB' : 'TEXT';
|
|
115
|
+
const payloadType = dialect === 'postgres' ? 'BYTEA' : 'BLOB';
|
|
116
|
+
const defs = [
|
|
117
|
+
`${quoteIdent(SYNC_PARTITION_COLUMN)} TEXT NOT NULL`,
|
|
118
|
+
`${quoteIdent(SYNC_ROW_ID_COLUMN)} TEXT NOT NULL`,
|
|
119
|
+
...(table.materialize
|
|
120
|
+
? table.columns.map((column) => {
|
|
121
|
+
const notNull = column.nullable ? '' : ' NOT NULL';
|
|
122
|
+
return `${quoteIdent(column.name)} ${columnSqlType(column, dialect)}${notNull}`;
|
|
123
|
+
})
|
|
124
|
+
: []),
|
|
125
|
+
`${quoteIdent(SYNC_VERSION_COLUMN)} ${versionType} NOT NULL`,
|
|
126
|
+
`${quoteIdent(SYNC_SCOPES_COLUMN)} ${scopesType} NOT NULL`,
|
|
127
|
+
`${quoteIdent(SYNC_PAYLOAD_COLUMN)} ${payloadType} NOT NULL`,
|
|
128
|
+
`PRIMARY KEY (${quoteIdent(SYNC_PARTITION_COLUMN)}, ${quoteIdent(SYNC_ROW_ID_COLUMN)})`,
|
|
129
|
+
];
|
|
130
|
+
return `CREATE TABLE IF NOT EXISTS ${quoteIdent(table.name)} (${defs.join(', ')})`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* ALTER TABLE ADD COLUMN statements for schema columns missing from
|
|
134
|
+
* `existingColumns` (introspected). Added columns are nullable (header note).
|
|
135
|
+
*/
|
|
136
|
+
export function addColumnDdl(table, existingColumns, dialect) {
|
|
137
|
+
if (!table.materialize)
|
|
138
|
+
return [];
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const column of table.columns) {
|
|
141
|
+
if (existingColumns.has(column.name))
|
|
142
|
+
continue;
|
|
143
|
+
out.push(`ALTER TABLE ${quoteIdent(table.name)} ADD COLUMN ${quoteIdent(column.name)} ${columnSqlType(column, dialect)}`);
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* CREATE INDEX IF NOT EXISTS for the table's user-declared indexes
|
|
149
|
+
* (DESIGN "user indexes" — the same names/columns the client materializes;
|
|
150
|
+
* cross-table index-name uniqueness is the user's schema concern, exactly
|
|
151
|
+
* as it is client-side).
|
|
152
|
+
*/
|
|
153
|
+
export function createIndexDdl(table) {
|
|
154
|
+
// User indexes name app columns — nothing to index without the projection.
|
|
155
|
+
if (!table.materialize)
|
|
156
|
+
return [];
|
|
157
|
+
return table.indexes.map((index) => {
|
|
158
|
+
const unique = index.unique ? 'UNIQUE ' : '';
|
|
159
|
+
const columns = index.columns
|
|
160
|
+
.map((column) => quoteIdent(column))
|
|
161
|
+
.join(', ');
|
|
162
|
+
return `CREATE ${unique}INDEX IF NOT EXISTS ${quoteIdent(index.name)} ON ${quoteIdent(table.name)} (${columns})`;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Convert one decoded row value to its SQL bind value.
|
|
167
|
+
* - boolean → 0/1 on sqlite (INTEGER affinity), native on postgres;
|
|
168
|
+
* - json → the raw string on sqlite; on postgres, NULL if unparseable
|
|
169
|
+
* (JSONB would reject the INSERT — the payload keeps the verbatim value);
|
|
170
|
+
* - everything else binds naturally (Uint8Array → BLOB/BYTEA).
|
|
171
|
+
*/
|
|
172
|
+
export function toSqlValue(column, value, dialect) {
|
|
173
|
+
if (value === null)
|
|
174
|
+
return null;
|
|
175
|
+
if (column.type === 'boolean' && dialect === 'sqlite') {
|
|
176
|
+
return value === true ? 1 : 0;
|
|
177
|
+
}
|
|
178
|
+
if (column.type === 'json' && dialect === 'postgres') {
|
|
179
|
+
if (typeof value !== 'string')
|
|
180
|
+
return null;
|
|
181
|
+
try {
|
|
182
|
+
JSON.parse(value);
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Bind values for an upsert, in `tableColumnNames` order. Decodes the
|
|
193
|
+
* payload once; the typed columns and the verbatim payload land in one
|
|
194
|
+
* statement, so they cannot drift.
|
|
195
|
+
*/
|
|
196
|
+
export function upsertValues(table, partition, row, dialect) {
|
|
197
|
+
// A non-materialized table skips the decode entirely — the upsert is a
|
|
198
|
+
// five-column meta write (the old blob-store cost).
|
|
199
|
+
const projection = table.materialize
|
|
200
|
+
? decodeRow(table.columns, row.payload)
|
|
201
|
+
: undefined;
|
|
202
|
+
return [
|
|
203
|
+
partition,
|
|
204
|
+
row.rowId,
|
|
205
|
+
...(projection !== undefined
|
|
206
|
+
? table.columns.map((column, index) => toSqlValue(column, projection[index] ?? null, dialect))
|
|
207
|
+
: []),
|
|
208
|
+
row.serverVersion,
|
|
209
|
+
JSON.stringify(row.scopes),
|
|
210
|
+
row.payload,
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
function placeholderList(count, dialect) {
|
|
214
|
+
if (dialect === 'sqlite')
|
|
215
|
+
return new Array(count).fill('?').join(',');
|
|
216
|
+
const out = [];
|
|
217
|
+
for (let i = 1; i <= count; i++)
|
|
218
|
+
out.push(`$${i}`);
|
|
219
|
+
return out.join(',');
|
|
220
|
+
}
|
|
221
|
+
/** Upsert statement text (bind with `upsertValues`). */
|
|
222
|
+
export function upsertSql(table, dialect) {
|
|
223
|
+
const names = tableColumnNames(table);
|
|
224
|
+
const quoted = names.map((name) => quoteIdent(name)).join(', ');
|
|
225
|
+
const values = placeholderList(names.length, dialect);
|
|
226
|
+
if (dialect === 'sqlite') {
|
|
227
|
+
// INSERT OR REPLACE keys on the (_sync_partition, _sync_row_id) PK.
|
|
228
|
+
return `INSERT OR REPLACE INTO ${quoteIdent(table.name)} (${quoted}) VALUES (${values})`;
|
|
229
|
+
}
|
|
230
|
+
const updates = names
|
|
231
|
+
.slice(2) // partition + row id are the conflict key
|
|
232
|
+
.map((name) => `${quoteIdent(name)}=EXCLUDED.${quoteIdent(name)}`)
|
|
233
|
+
.join(', ');
|
|
234
|
+
return `INSERT INTO ${quoteIdent(table.name)} (${quoted}) VALUES (${values})
|
|
235
|
+
ON CONFLICT (${quoteIdent(SYNC_PARTITION_COLUMN)}, ${quoteIdent(SYNC_ROW_ID_COLUMN)}) DO UPDATE SET ${updates}`;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* SELECT one stored row. Aliased to the legacy record shape
|
|
239
|
+
* (`row_id`/`server_version`/`scopes`/`payload`) so the existing
|
|
240
|
+
* `toStoredRow` converters keep working. Params: [partition, rowId].
|
|
241
|
+
*/
|
|
242
|
+
export function selectRowSql(table, dialect) {
|
|
243
|
+
const p = dialect === 'sqlite' ? ['?', '?'] : ['$1', '$2'];
|
|
244
|
+
return `SELECT ${quoteIdent(SYNC_ROW_ID_COLUMN)} AS row_id, ${quoteIdent(SYNC_VERSION_COLUMN)} AS server_version, ${quoteIdent(SYNC_SCOPES_COLUMN)} AS scopes, ${quoteIdent(SYNC_PAYLOAD_COLUMN)} AS payload FROM ${quoteIdent(table.name)} WHERE ${quoteIdent(SYNC_PARTITION_COLUMN)}=${p[0]} AND ${quoteIdent(SYNC_ROW_ID_COLUMN)}=${p[1]}`;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* One-round-trip page scan for `scanRows`: candidates from the inverted
|
|
248
|
+
* scope index (ordered + LIMITed at the covering `sync_row_scopes` PK —
|
|
249
|
+
* exactly the old candidate query, so the index-first posture is unchanged)
|
|
250
|
+
* LEFT JOINed to the row table, so a whole page arrives in ONE statement
|
|
251
|
+
* instead of one lookup per candidate. That per-candidate lookup was the
|
|
252
|
+
* cold-bootstrap hot path: a 100k-row snapshot on Postgres paid ~100k
|
|
253
|
+
* network round-trips (~10 s at 0.1 ms each) before this join.
|
|
254
|
+
*
|
|
255
|
+
* LEFT JOIN (not INNER): a candidate whose row vanished (index entry
|
|
256
|
+
* without a row) must still reach the caller — its `row_id` advances the
|
|
257
|
+
* keyset cursor — so it comes back with NULL payload rather than
|
|
258
|
+
* disappearing from the page.
|
|
259
|
+
*
|
|
260
|
+
* Bind order:
|
|
261
|
+
* - postgres: [partition, tbl, var, ...values, afterRowId, limit]
|
|
262
|
+
* (the join reuses `$1` for the partition);
|
|
263
|
+
* - sqlite: [partition, tbl, var, ...values, afterRowId, limit,
|
|
264
|
+
* partition] (positional `?` — the partition binds again for the join).
|
|
265
|
+
*/
|
|
266
|
+
export function scanRowPageSql(table, valueCount, dialect) {
|
|
267
|
+
const p = (n) => (dialect === 'sqlite' ? '?' : `$${n}`);
|
|
268
|
+
const values = [];
|
|
269
|
+
for (let i = 0; i < valueCount; i++)
|
|
270
|
+
values.push(p(4 + i));
|
|
271
|
+
const after = p(4 + valueCount);
|
|
272
|
+
const limit = p(5 + valueCount);
|
|
273
|
+
const joinPartition = dialect === 'sqlite' ? '?' : '$1';
|
|
274
|
+
return `SELECT c.row_id AS row_id, r.${quoteIdent(SYNC_VERSION_COLUMN)} AS server_version, r.${quoteIdent(SYNC_SCOPES_COLUMN)} AS scopes, r.${quoteIdent(SYNC_PAYLOAD_COLUMN)} AS payload
|
|
275
|
+
FROM (SELECT DISTINCT row_id FROM sync_row_scopes
|
|
276
|
+
WHERE partition=${p(1)} AND tbl=${p(2)} AND var=${p(3)} AND value IN (${values.join(',')})
|
|
277
|
+
AND row_id>${after}
|
|
278
|
+
ORDER BY row_id LIMIT ${limit}) c
|
|
279
|
+
LEFT JOIN ${quoteIdent(table.name)} r
|
|
280
|
+
ON r.${quoteIdent(SYNC_PARTITION_COLUMN)}=${joinPartition} AND r.${quoteIdent(SYNC_ROW_ID_COLUMN)}=c.row_id
|
|
281
|
+
ORDER BY c.row_id`;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* One-round-trip page read for `readCommitWindow` (sync_* tables only; it
|
|
285
|
+
* lives here beside `scanRowPageSql` because it is the same join-the-index
|
|
286
|
+
* posture, dialect-parameterized the same way): candidates from the inverted
|
|
287
|
+
* change-scope index (ordered + LIMITed at the covering `sync_change_scopes`
|
|
288
|
+
* PK — exactly the old candidate query, so the index-first invariant of
|
|
289
|
+
* `CommitWindowQuery` is unchanged) LEFT JOINed to the commit metadata and
|
|
290
|
+
* to the commit's changes for the table, so a whole window page arrives in
|
|
291
|
+
* ONE statement instead of 1 + 2×candidates round trips. This is the
|
|
292
|
+
* incremental-pull hot path — it runs on every sync round; before the join a
|
|
293
|
+
* 500-candidate catch-up window on Postgres paid ~1000 network round trips.
|
|
294
|
+
*
|
|
295
|
+
* LEFT JOIN (not INNER): a candidate whose commit vanished (scope-index
|
|
296
|
+
* entry without a commit/changes row) must still reach the caller — its
|
|
297
|
+
* `commit_seq` advances the window cursor — so it comes back with NULL
|
|
298
|
+
* meta/change columns rather than disappearing from the page.
|
|
299
|
+
*
|
|
300
|
+
* Rows arrive ordered (commit_seq, idx): oldest-first commits, each commit's
|
|
301
|
+
* change rows consecutive in `idx` order — the caller groups consecutive
|
|
302
|
+
* rows and verifies the full multi-variable scope match in JS.
|
|
303
|
+
*
|
|
304
|
+
* Bind order:
|
|
305
|
+
* - postgres: [partition, tbl, var, ...values, afterSeq, throughSeq,
|
|
306
|
+
* limit] (the joins reuse `$1`/`$2`);
|
|
307
|
+
* - sqlite: [partition, tbl, var, ...values, afterSeq, throughSeq,
|
|
308
|
+
* limit, partition, partition, tbl] (positional `?` — partition/tbl bind
|
|
309
|
+
* again for the joins).
|
|
310
|
+
*/
|
|
311
|
+
export function commitWindowPageSql(valueCount, dialect) {
|
|
312
|
+
const p = (n) => (dialect === 'sqlite' ? '?' : `$${n}`);
|
|
313
|
+
const values = [];
|
|
314
|
+
for (let i = 0; i < valueCount; i++)
|
|
315
|
+
values.push(p(4 + i));
|
|
316
|
+
const after = p(4 + valueCount);
|
|
317
|
+
const through = p(5 + valueCount);
|
|
318
|
+
const limit = p(6 + valueCount);
|
|
319
|
+
const joinPartition = dialect === 'sqlite' ? '?' : '$1';
|
|
320
|
+
const joinTbl = dialect === 'sqlite' ? '?' : '$2';
|
|
321
|
+
return `SELECT c.commit_seq AS commit_seq, m.actor_id AS actor_id, m.created_at_ms AS created_at_ms,
|
|
322
|
+
ch.tbl AS tbl, ch.row_id AS row_id, ch.op AS op, ch.row_version AS row_version, ch.scopes AS scopes, ch.payload AS payload
|
|
323
|
+
FROM (SELECT DISTINCT commit_seq FROM sync_change_scopes
|
|
324
|
+
WHERE partition=${p(1)} AND tbl=${p(2)} AND var=${p(3)} AND value IN (${values.join(',')})
|
|
325
|
+
AND commit_seq>${after} AND commit_seq<=${through}
|
|
326
|
+
ORDER BY commit_seq LIMIT ${limit}) c
|
|
327
|
+
LEFT JOIN sync_commits m
|
|
328
|
+
ON m.partition=${joinPartition} AND m.commit_seq=c.commit_seq
|
|
329
|
+
LEFT JOIN sync_changes ch
|
|
330
|
+
ON ch.partition=${joinPartition} AND ch.commit_seq=c.commit_seq AND ch.tbl=${joinTbl}
|
|
331
|
+
ORDER BY c.commit_seq, ch.idx`;
|
|
332
|
+
}
|
|
333
|
+
/** SELECT a row's version + scope map (admin/blob authz). Params: [partition, rowId]. */
|
|
334
|
+
export function selectRowScopesSql(table, dialect) {
|
|
335
|
+
const p = dialect === 'sqlite' ? ['?', '?'] : ['$1', '$2'];
|
|
336
|
+
return `SELECT ${quoteIdent(SYNC_VERSION_COLUMN)} AS server_version, ${quoteIdent(SYNC_SCOPES_COLUMN)} AS scopes FROM ${quoteIdent(table.name)} WHERE ${quoteIdent(SYNC_PARTITION_COLUMN)}=${p[0]} AND ${quoteIdent(SYNC_ROW_ID_COLUMN)}=${p[1]}`;
|
|
337
|
+
}
|
|
338
|
+
/** DELETE one stored row. Params: [partition, rowId]. */
|
|
339
|
+
export function deleteRowSql(table, dialect) {
|
|
340
|
+
const p = dialect === 'sqlite' ? ['?', '?'] : ['$1', '$2'];
|
|
341
|
+
return `DELETE FROM ${quoteIdent(table.name)} WHERE ${quoteIdent(SYNC_PARTITION_COLUMN)}=${p[0]} AND ${quoteIdent(SYNC_ROW_ID_COLUMN)}=${p[1]}`;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* The schema-version marker table gating DDL work (DESIGN "server-side
|
|
345
|
+
* schema migration"): `ensureSchema` compares the stored version and skips
|
|
346
|
+
* all introspection/DDL when it matches — one cheap read per storage
|
|
347
|
+
* instance (relevant for D1's per-request instantiation).
|
|
348
|
+
*
|
|
349
|
+
* `layouts` persists each table's column layout (name/type/nullable, the
|
|
350
|
+
* exact inputs the row codec's byte layout depends on) as of the LAST
|
|
351
|
+
* applied schema version. The codec is strict — a payload only decodes
|
|
352
|
+
* under the column list it was encoded with — so a version bump MUST
|
|
353
|
+
* re-encode stored payloads (append trailing NULLs for added columns);
|
|
354
|
+
* decoding the old bytes requires the old layout, and this column is where
|
|
355
|
+
* it lives.
|
|
356
|
+
*/
|
|
357
|
+
export const SCHEMA_META_DDL_SQLITE = `CREATE TABLE IF NOT EXISTS sync_schema_meta(
|
|
358
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
359
|
+
schema_version INTEGER NOT NULL,
|
|
360
|
+
layouts TEXT NOT NULL DEFAULT '{}'
|
|
361
|
+
)`;
|
|
362
|
+
export const SCHEMA_META_DDL_POSTGRES = `CREATE TABLE IF NOT EXISTS sync_schema_meta(
|
|
363
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
364
|
+
schema_version BIGINT NOT NULL,
|
|
365
|
+
layouts TEXT NOT NULL DEFAULT '{}'
|
|
366
|
+
)`;
|
|
367
|
+
/** The layouts JSON persisted alongside the schema version marker. */
|
|
368
|
+
export function layoutsOf(schema) {
|
|
369
|
+
const out = {};
|
|
370
|
+
for (const table of schema.tables.values()) {
|
|
371
|
+
out[table.name] = table.columns.map((column) => ({
|
|
372
|
+
name: column.name,
|
|
373
|
+
type: column.type,
|
|
374
|
+
nullable: column.nullable,
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
return JSON.stringify(out);
|
|
378
|
+
}
|
|
379
|
+
export function parseLayouts(json) {
|
|
380
|
+
if (json === null || json === undefined || json.length === 0)
|
|
381
|
+
return {};
|
|
382
|
+
return JSON.parse(json);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Enforce the migration subset on a table's column list: the old layout
|
|
386
|
+
* must be an exact prefix (same name, type, nullability) of the new one,
|
|
387
|
+
* and appended columns must be nullable (there is no default to backfill).
|
|
388
|
+
* Returns `true` iff columns were appended.
|
|
389
|
+
*/
|
|
390
|
+
export function assertAppendOnlyMigration(tableName, oldLayout, table) {
|
|
391
|
+
if (oldLayout.length > table.columns.length) {
|
|
392
|
+
throw new Error(`table ${JSON.stringify(tableName)}: the schema removed columns — only CREATE TABLE / ADD COLUMN / CREATE INDEX migrations are supported`);
|
|
393
|
+
}
|
|
394
|
+
for (let i = 0; i < oldLayout.length; i++) {
|
|
395
|
+
const before = oldLayout[i];
|
|
396
|
+
const after = table.columns[i];
|
|
397
|
+
if (before === undefined ||
|
|
398
|
+
after === undefined ||
|
|
399
|
+
before.name !== after.name ||
|
|
400
|
+
before.type !== after.type ||
|
|
401
|
+
before.nullable !== after.nullable) {
|
|
402
|
+
throw new Error(`table ${JSON.stringify(tableName)}: column ${i} changed (${JSON.stringify(before)} → ${JSON.stringify({ name: after?.name, type: after?.type, nullable: after?.nullable })}) — only appending nullable columns is supported`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
for (let i = oldLayout.length; i < table.columns.length; i++) {
|
|
406
|
+
const added = table.columns[i];
|
|
407
|
+
if (added !== undefined && !added.nullable) {
|
|
408
|
+
throw new Error(`table ${JSON.stringify(tableName)}: added column ${JSON.stringify(added.name)} must be nullable — existing rows have no value to backfill`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return oldLayout.length < table.columns.length;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Re-encode an old-layout payload under the current columns: decode with
|
|
415
|
+
* the layout it was written under, append NULLs for the added columns,
|
|
416
|
+
* encode with the current column list. The write path (§3.4 scope-strip,
|
|
417
|
+
* CRDT merge, conflict serverRow) and the bootstrap serve path both decode
|
|
418
|
+
* stored payloads under the CURRENT schema, so this migration is a
|
|
419
|
+
* correctness requirement of the version bump, not an optimization.
|
|
420
|
+
*/
|
|
421
|
+
export function migratePayload(oldLayout, table, payload) {
|
|
422
|
+
const values = decodeRow(oldLayout, payload);
|
|
423
|
+
while (values.length < table.columns.length)
|
|
424
|
+
values.push(null);
|
|
425
|
+
return encodeRow(table.columns, values);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Keyset-paged scan of a row table for the migration rewrite.
|
|
429
|
+
* Params: [afterPartition, afterRowId, limit].
|
|
430
|
+
*/
|
|
431
|
+
export function selectRowsForRewriteSql(table, dialect) {
|
|
432
|
+
const p = dialect === 'sqlite' ? ['?', '?', '?'] : ['$1', '$2', '$3'];
|
|
433
|
+
return `SELECT ${quoteIdent(SYNC_PARTITION_COLUMN)} AS partition, ${quoteIdent(SYNC_ROW_ID_COLUMN)} AS row_id, ${quoteIdent(SYNC_PAYLOAD_COLUMN)} AS payload
|
|
434
|
+
FROM ${quoteIdent(table.name)}
|
|
435
|
+
WHERE (${quoteIdent(SYNC_PARTITION_COLUMN)}, ${quoteIdent(SYNC_ROW_ID_COLUMN)}) > (${p[0]}, ${p[1]})
|
|
436
|
+
ORDER BY ${quoteIdent(SYNC_PARTITION_COLUMN)}, ${quoteIdent(SYNC_ROW_ID_COLUMN)}
|
|
437
|
+
LIMIT ${p[2]}`;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Rewrite one row during a migration: refresh the projection columns (when
|
|
441
|
+
* materialized) and the payload. Bind with `rewriteValues`.
|
|
442
|
+
*/
|
|
443
|
+
export function rewriteRowSql(table, dialect) {
|
|
444
|
+
const sets = [];
|
|
445
|
+
let i = 1;
|
|
446
|
+
if (table.materialize) {
|
|
447
|
+
for (const column of table.columns) {
|
|
448
|
+
sets.push(`${quoteIdent(column.name)}=${dialect === 'sqlite' ? '?' : `$${i}`}`);
|
|
449
|
+
i++;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
sets.push(`${quoteIdent(SYNC_PAYLOAD_COLUMN)}=${dialect === 'sqlite' ? '?' : `$${i}`}`);
|
|
453
|
+
i++;
|
|
454
|
+
const wherePartition = dialect === 'sqlite' ? '?' : `$${i}`;
|
|
455
|
+
const whereRowId = dialect === 'sqlite' ? '?' : `$${i + 1}`;
|
|
456
|
+
return `UPDATE ${quoteIdent(table.name)} SET ${sets.join(', ')} WHERE ${quoteIdent(SYNC_PARTITION_COLUMN)}=${wherePartition} AND ${quoteIdent(SYNC_ROW_ID_COLUMN)}=${whereRowId}`;
|
|
457
|
+
}
|
|
458
|
+
/** Bind values for `rewriteRowSql` from the (already migrated) payload. */
|
|
459
|
+
export function rewriteValues(table, partition, rowId, payload, dialect) {
|
|
460
|
+
const out = [];
|
|
461
|
+
if (table.materialize) {
|
|
462
|
+
const values = decodeRow(table.columns, payload);
|
|
463
|
+
for (let i = 0; i < table.columns.length; i++) {
|
|
464
|
+
const column = table.columns[i];
|
|
465
|
+
if (column === undefined)
|
|
466
|
+
continue;
|
|
467
|
+
out.push(toSqlValue(column, values[i] ?? null, dialect));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
out.push(payload, partition, rowId);
|
|
471
|
+
return out;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* What the migration rewrite phase must do for one table, derived from the
|
|
475
|
+
* persisted layouts + the physical columns present before this run.
|
|
476
|
+
*
|
|
477
|
+
* - `migrate`: the layout gained columns — every stored payload re-encodes
|
|
478
|
+
* under the new column list (correctness; see `migratePayload`).
|
|
479
|
+
* - `backfill`: the table just gained physical projection columns whose
|
|
480
|
+
* values exist in stored payloads (materialization flipped on) — the
|
|
481
|
+
* projection refreshes from the payload, no re-encode.
|
|
482
|
+
*/
|
|
483
|
+
export function rewritePlan(table, oldLayout, physicalColumnsBefore) {
|
|
484
|
+
if (oldLayout === undefined || physicalColumnsBefore === undefined) {
|
|
485
|
+
// Fresh table (or a pre-layout database, which cannot be reasoned
|
|
486
|
+
// about): nothing stored to rewrite.
|
|
487
|
+
return { migrate: false, backfill: false };
|
|
488
|
+
}
|
|
489
|
+
const migrate = assertAppendOnlyMigration(table.name, oldLayout, table);
|
|
490
|
+
const backfill = table.materialize &&
|
|
491
|
+
table.columns.some((column, index) => index < oldLayout.length && // value exists in old payloads
|
|
492
|
+
!physicalColumnsBefore.has(column.name));
|
|
493
|
+
return { migrate, backfill };
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Every DDL statement to bring a database from `existingColumnsByTable`
|
|
497
|
+
* (introspected; a missing key = table absent) to `schema`: CREATE TABLE
|
|
498
|
+
* for absent tables, ADD COLUMN for missing columns, CREATE INDEX for the
|
|
499
|
+
* declared user indexes — exactly the migration subset.
|
|
500
|
+
*/
|
|
501
|
+
export function schemaDdl(schema, existingColumnsByTable, dialect) {
|
|
502
|
+
const out = [];
|
|
503
|
+
for (const table of schema.tables.values()) {
|
|
504
|
+
const existing = existingColumnsByTable.get(table.name);
|
|
505
|
+
if (existing === undefined) {
|
|
506
|
+
out.push(createTableDdl(table, dialect));
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
out.push(...addColumnDdl(table, existing, dialect));
|
|
510
|
+
}
|
|
511
|
+
out.push(...createIndexDdl(table));
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3-compatible blob store (SPEC.md §5.9.2 durable objects, §5.9.5 delegated
|
|
3
|
+
* presign) — AWS S3, Cloudflare R2, MinIO. The blob twin of
|
|
4
|
+
* `s3-segment-store.ts`: same hand-rolled SigV4 over `fetch` (sigv4.ts), same
|
|
5
|
+
* content-addressed key layout, same LIST-free-on-the-hot-path CAS stats
|
|
6
|
+
* accumulator. Zero dependencies.
|
|
7
|
+
*
|
|
8
|
+
* Key layout (deterministic — every point lookup is a GET/HEAD, never a LIST):
|
|
9
|
+
*
|
|
10
|
+
* {keyPrefix}blob/sha256/{hex}
|
|
11
|
+
* The blob bytes, VERBATIM (the object body MUST be exactly the
|
|
12
|
+
* content-addressed bytes so a presigned GET serves them directly and the
|
|
13
|
+
* client's §5.9.1 hash check passes — blobs are never re-compressed at
|
|
14
|
+
* rest, §5.8/§5.9.5). The `byteLength` + optional `mediaType` ride along
|
|
15
|
+
* as object user metadata `x-amz-meta-syncular-blob` = base64url(JSON), so
|
|
16
|
+
* `get` is a single GET. `createdAtMs` is stored there too so the orphan
|
|
17
|
+
* sweep (§5.9.2) can read the upload age without a separate index.
|
|
18
|
+
*
|
|
19
|
+
* DURABILITY vs SEGMENT TTL — the honest interface difference (§5.9.2): a
|
|
20
|
+
* blob referenced by a live row must stay downloadable INDEFINITELY. So,
|
|
21
|
+
* unlike `S3SegmentStore`, this store writes **no `expiresAtMs`, no S3
|
|
22
|
+
* lifecycle-expiration mapping, and no TTL config**. Expiry is
|
|
23
|
+
* REFERENCE-driven, not time-driven: reclamation is the host-scheduled
|
|
24
|
+
* `sweepOrphanBlobs` (blob-store.ts) deleting only objects no live row
|
|
25
|
+
* references after a grace period — never a bucket lifecycle rule. Do NOT
|
|
26
|
+
* put a lifecycle expiration on the `blob/` prefix; it would delete
|
|
27
|
+
* still-referenced attachments.
|
|
28
|
+
*
|
|
29
|
+
* The orphan sweep is the ONLY operation that LISTs: `sweepOrphans` pages
|
|
30
|
+
* `ListObjectsV2` over the `blob/` prefix (an admin/GC path, off the hot
|
|
31
|
+
* path) to find candidates, reads each object's `createdAtMs` from its
|
|
32
|
+
* metadata for the grace check, and DELETEs the unreferenced-and-old ones.
|
|
33
|
+
*/
|
|
34
|
+
import type { BlobRecord, BlobStore, BlobStoreStats } from './blob-store.js';
|
|
35
|
+
import type { BlobPresignConfig, BlobUploadPresignConfig, SegmentUrlIssue } from './signed-url.js';
|
|
36
|
+
export interface S3BlobStoreConfig {
|
|
37
|
+
/**
|
|
38
|
+
* Endpoint origin, no bucket, no trailing slash:
|
|
39
|
+
* AWS `https://s3.<region>.amazonaws.com`,
|
|
40
|
+
* R2 `https://<account-id>.r2.cloudflarestorage.com`,
|
|
41
|
+
* MinIO `http://127.0.0.1:9000`. Requests are path-style
|
|
42
|
+
* (`{endpoint}/{bucket}/{key}`), which all three accept.
|
|
43
|
+
*/
|
|
44
|
+
readonly endpoint: string;
|
|
45
|
+
/** AWS region; R2 uses `auto`. */
|
|
46
|
+
readonly region: string;
|
|
47
|
+
readonly bucket: string;
|
|
48
|
+
readonly accessKeyId: string;
|
|
49
|
+
readonly secretAccessKey: string;
|
|
50
|
+
/** Optional STS session token. */
|
|
51
|
+
readonly sessionToken?: string;
|
|
52
|
+
/** Key namespace inside the bucket, e.g. `syncular/`. Default: none. */
|
|
53
|
+
readonly keyPrefix?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare class S3BlobStore implements BlobStore {
|
|
56
|
+
#private;
|
|
57
|
+
constructor(config: S3BlobStoreConfig);
|
|
58
|
+
/**
|
|
59
|
+
* `{keyPrefix}blob/{partition}/sha256/{hex}` — the §5.9.5 "signed key embeds
|
|
60
|
+
* the blobId". Blobs are content-addressed, but download authorization and
|
|
61
|
+
* the reference index are per-partition, so the same bytes uploaded under
|
|
62
|
+
* two partitions are two objects (a partition MUST NOT read another's
|
|
63
|
+
* attachment by guessing a content address). The partition is folded into
|
|
64
|
+
* the key as a path segment.
|
|
65
|
+
*/
|
|
66
|
+
objectKeyFor(partition: string, blobId: string): string;
|
|
67
|
+
put(partition: string, blobId: string, bytes: Uint8Array, nowMs: number, mediaType?: string): Promise<BlobRecord>;
|
|
68
|
+
has(partition: string, blobId: string): Promise<boolean>;
|
|
69
|
+
get(partition: string, blobId: string): Promise<{
|
|
70
|
+
record: BlobRecord;
|
|
71
|
+
bytes: Uint8Array;
|
|
72
|
+
} | undefined>;
|
|
73
|
+
/**
|
|
74
|
+
* §5.9.2 orphan sweep — the ONLY LISTing operation. Page `ListObjectsV2`
|
|
75
|
+
* over `{prefix}blob/{partition}/`, HEAD each object for its `createdAtMs`,
|
|
76
|
+
* and DELETE the ones both older than `olderThanMs` AND absent from
|
|
77
|
+
* `referencedBlobIds`. Returns the deleted blobIds. Never deletes a
|
|
78
|
+
* referenced blob (§5.9.2). Off the hot path — a host-scheduled GC job.
|
|
79
|
+
*/
|
|
80
|
+
sweepOrphans(partition: string, olderThanMs: number, referencedBlobIds: ReadonlySet<string>): Promise<string[]>;
|
|
81
|
+
/**
|
|
82
|
+
* Store-wide counters from the pointer-object accumulator (never a LIST on
|
|
83
|
+
* the read path). `approximate: true` for the same reasons `S3SegmentStore`
|
|
84
|
+
* documents: best-effort ETag-CAS maintenance and sweep deletes that may
|
|
85
|
+
* miss the decrement. Exact within a single writer; a health gauge under
|
|
86
|
+
* concurrency. Note this is store-WIDE, not partition-scoped, so a shared
|
|
87
|
+
* bucket reports the whole bucket — the parameter is accepted for interface
|
|
88
|
+
* parity but the accumulator is one object per store.
|
|
89
|
+
*/
|
|
90
|
+
stats(_partition: string): Promise<BlobStoreStats>;
|
|
91
|
+
/**
|
|
92
|
+
* SigV4 presigned GET for a blob object (§5.9.5 delegated presign). The
|
|
93
|
+
* signed key embeds the `blobId`; `urlExpiresAtMs` is the provider-enforced
|
|
94
|
+
* expiry (`X-Amz-Date + X-Amz-Expires`). Issued ONLY after the row-derived
|
|
95
|
+
* authorization check (§5.9.5) — the caller (blob-handlers) resolves scopes
|
|
96
|
+
* and tests referencing rows first; the URL is then a short-TTL bearer
|
|
97
|
+
* grant to exactly those immutable bytes. TTL SHOULD be ≤ 15 minutes;
|
|
98
|
+
* default 900 s.
|
|
99
|
+
*/
|
|
100
|
+
presignBlobGet(partition: string, blobId: string, options?: {
|
|
101
|
+
readonly ttlSeconds?: number;
|
|
102
|
+
readonly nowMs?: number;
|
|
103
|
+
}): Promise<SegmentUrlIssue>;
|
|
104
|
+
/**
|
|
105
|
+
* SigV4 presigned PUT for a blob object (§5.9.3 direct-to-storage upload).
|
|
106
|
+
* The blob twin of `presignBlobGet`: the signed key embeds the `blobId`, so
|
|
107
|
+
* the grant places bytes at exactly the content-addressed key. The store
|
|
108
|
+
* does NOT recompute the SHA-256 (it is the object store, not the sync
|
|
109
|
+
* server); integrity is enforced at REFERENCE time — the §5.9.6 push
|
|
110
|
+
* existence check verifies the object exists (`has`) and every consumer
|
|
111
|
+
* re-verifies the content address over the received bytes (§5.9.5/§5.1). A
|
|
112
|
+
* client PUTting bytes that do not hash to `{blobId}` poisons only its own
|
|
113
|
+
* upload; no honest reference resolves to it. Issued by the upload-grant
|
|
114
|
+
* handler after host authentication; TTL SHOULD be ≤ 15 min (default 900).
|
|
115
|
+
*/
|
|
116
|
+
presignBlobPut(partition: string, blobId: string, options?: {
|
|
117
|
+
readonly ttlSeconds?: number;
|
|
118
|
+
readonly nowMs?: number;
|
|
119
|
+
}): Promise<SegmentUrlIssue>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Wire an `S3BlobStore` into `SyncServerConfig.blobSignedUrls` as the §5.9.5
|
|
123
|
+
* delegated-presign scheme for blob downloads:
|
|
124
|
+
* `blobSignedUrls: s3PresignedBlobUrls(blobStore, { ttlSeconds: 900 })`.
|
|
125
|
+
* The presigned URL is issued only after the row-derived authorization check
|
|
126
|
+
* (blob-handlers) — never as a bearer capability minted from the id alone.
|
|
127
|
+
*/
|
|
128
|
+
export declare function s3PresignedBlobUrls(store: S3BlobStore, options?: {
|
|
129
|
+
readonly ttlSeconds?: number;
|
|
130
|
+
}): BlobPresignConfig;
|
|
131
|
+
/**
|
|
132
|
+
* Wire an `S3BlobStore` into `SyncServerConfig.blobUploadUrls` as the §5.9.3
|
|
133
|
+
* presigned-upload (direct-to-storage) scheme:
|
|
134
|
+
* `blobUploadUrls: s3PresignedBlobUploads(blobStore, { ttlSeconds: 900 })`.
|
|
135
|
+
* The upload-grant handler issues the presigned PUT only after host
|
|
136
|
+
* authentication + the size-cap check; the client PUTs bytes straight to the
|
|
137
|
+
* object store with no host auth (§5.9.3). Integrity stays the content-address
|
|
138
|
+
* check at reference/download time — never a store-side hash recompute.
|
|
139
|
+
*/
|
|
140
|
+
export declare function s3PresignedBlobUploads(store: S3BlobStore, options?: {
|
|
141
|
+
readonly ttlSeconds?: number;
|
|
142
|
+
}): BlobUploadPresignConfig;
|