@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
package/dist/push.js
CHANGED
|
@@ -1,703 +1,454 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Push apply (SPEC.md §6) with §3.4 write-path authorization.
|
|
3
|
+
*
|
|
4
|
+
* Security-critical rules implemented here:
|
|
5
|
+
* - authorization runs against the STORED row when it exists, never the
|
|
6
|
+
* pushed payload (§3.4 step 2);
|
|
7
|
+
* - declared scope columns are stripped from every update path (§3.4
|
|
8
|
+
* rule 5) — updates keep the stored row's scope column values;
|
|
9
|
+
* - a lost `baseVersion = 0` insert race re-authorizes the winner's row
|
|
10
|
+
* before disclosing it in a conflict record (§6.2).
|
|
11
|
+
*
|
|
12
|
+
* Per-commit atomicity (§6.4): one storage transaction per commit; the
|
|
13
|
+
* idempotency record persists in the same transaction as the writes.
|
|
14
|
+
*
|
|
15
|
+
* Optional per-table write validation (§6.7) runs after decode + the §3.4
|
|
16
|
+
* scope check, on the row that will persist (post scope-strip, post CRDT
|
|
17
|
+
* merge); a validator throw rejects the whole commit atomically with a
|
|
18
|
+
* host code.
|
|
19
|
+
*/
|
|
20
|
+
import { decodeRow, encodeRow, parseBlobRef, } from '@syncular/core';
|
|
21
|
+
import { clockOf } from './context.js';
|
|
22
|
+
import { SyncError } from './errors.js';
|
|
23
|
+
import { authorizeWrite, renderScopeValue, storedScopesForRow } from './scopes.js';
|
|
24
|
+
import { toValidateRow, ValidationRejection } from './validate.js';
|
|
25
|
+
/**
|
|
26
|
+
* Extract the blobIds a decoded row references through its `blob_ref`
|
|
27
|
+
* columns (§5.9.4), skipping NULLs. Malformed BlobRefs already failed at
|
|
28
|
+
* row-codec decode (§5.9.1), so `parseBlobRef` here is total.
|
|
29
|
+
*/
|
|
30
|
+
function blobIdsInRow(table, values) {
|
|
31
|
+
const ids = [];
|
|
32
|
+
for (const index of table.blobRefColumnIndices) {
|
|
33
|
+
const value = values[index];
|
|
34
|
+
if (typeof value === 'string')
|
|
35
|
+
ids.push(parseBlobRef(value).blobId);
|
|
13
36
|
}
|
|
37
|
+
return ids;
|
|
14
38
|
}
|
|
15
|
-
function
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
function errorRecord(opIndex, code, message, retryable = false) {
|
|
40
|
+
return {
|
|
41
|
+
kind: 'terminate',
|
|
42
|
+
record: { opIndex, status: 'error', code, message, retryable },
|
|
43
|
+
};
|
|
20
44
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
function conflictRecord(opIndex, serverVersion, serverRow) {
|
|
46
|
+
return {
|
|
47
|
+
kind: 'terminate',
|
|
48
|
+
record: {
|
|
49
|
+
opIndex,
|
|
50
|
+
status: 'conflict',
|
|
51
|
+
code: 'sync.version_conflict',
|
|
52
|
+
message: 'row version does not match baseVersion (§6.2)',
|
|
53
|
+
serverVersion,
|
|
54
|
+
serverRow,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
31
57
|
}
|
|
32
|
-
|
|
58
|
+
/**
|
|
59
|
+
* §6.7: run the table's write-validation hook, if configured, on the row
|
|
60
|
+
* that WILL persist (post scope-strip, post CRDT-merge — the values the
|
|
61
|
+
* store receives). Returns a terminating outcome iff the validator rejects
|
|
62
|
+
* (its `ValidationRejection` code, or `sync.constraint_violation` for a
|
|
63
|
+
* non-`ValidationRejection` throw), else `undefined` (accept / no hook).
|
|
64
|
+
* A no-op for tables with no validator — the `undefined` short-circuit
|
|
65
|
+
* keeps the feature zero-cost when off.
|
|
66
|
+
*/
|
|
67
|
+
async function runValidator(validators, table, op, rowId, values, storedValues, opIndex, partition, actorId) {
|
|
68
|
+
const validator = validators?.[table.name];
|
|
69
|
+
if (validator === undefined)
|
|
70
|
+
return undefined;
|
|
33
71
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
await validator({
|
|
73
|
+
op,
|
|
74
|
+
table: table.name,
|
|
75
|
+
rowId,
|
|
76
|
+
row: values !== undefined
|
|
77
|
+
? toValidateRow(table.columns, values)
|
|
78
|
+
: undefined,
|
|
79
|
+
stored: storedValues !== undefined
|
|
80
|
+
? toValidateRow(table.columns, storedValues)
|
|
81
|
+
: undefined,
|
|
82
|
+
}, { actorId, partition });
|
|
42
83
|
}
|
|
43
|
-
catch {
|
|
44
|
-
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof ValidationRejection) {
|
|
86
|
+
return errorRecord(opIndex, error.code, error.message);
|
|
87
|
+
}
|
|
88
|
+
// §6.7: a non-ValidationRejection throw is still a rejection, mapped to
|
|
89
|
+
// the generic server-side constraint code (§10.2) — the validator's
|
|
90
|
+
// failure never crashes the request or leaks its message as a code.
|
|
91
|
+
return errorRecord(opIndex, 'sync.constraint_violation', `write validator for table ${JSON.stringify(table.name)} threw: ${error instanceof Error ? error.message : String(error)}`);
|
|
45
92
|
}
|
|
46
|
-
|
|
47
|
-
return dialect.readAffectedTablesFromChanges(db, commitSeq, { partitionId });
|
|
93
|
+
return undefined;
|
|
48
94
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
95
|
+
/**
|
|
96
|
+
* §5.10.3: merge the row's `crdt` columns in place. For each crdt column,
|
|
97
|
+
* replace the incoming value with `merge(stored, incoming)` (§5.10.2) —
|
|
98
|
+
* never the raw pushed bytes. `storedValues` is undefined on insert (the
|
|
99
|
+
* stored value is `null` — the empty document). Returns `true` iff any crdt
|
|
100
|
+
* column value changed (so the caller re-encodes), or a terminating
|
|
101
|
+
* `sync.crdt_merge_failed` outcome if a merger is missing or throws.
|
|
102
|
+
*
|
|
103
|
+
* A NULL incoming crdt value is a semantic clear, not a merge — it passes
|
|
104
|
+
* through untouched (the app is nulling the column, the same as any other
|
|
105
|
+
* type). Merging only runs for a non-NULL incoming crdt value.
|
|
106
|
+
*/
|
|
107
|
+
async function mergeCrdtColumns(table, values, storedValues, opIndex, mergers) {
|
|
108
|
+
if (table.crdtColumns.length === 0)
|
|
109
|
+
return { changed: false };
|
|
110
|
+
let changed = false;
|
|
111
|
+
for (const { index, crdtType } of table.crdtColumns) {
|
|
112
|
+
const incoming = values[index];
|
|
113
|
+
if (!(incoming instanceof Uint8Array))
|
|
114
|
+
continue; // NULL clear or absent
|
|
115
|
+
const merger = mergers?.[crdtType];
|
|
116
|
+
if (merger === undefined) {
|
|
117
|
+
return errorRecord(opIndex, 'sync.crdt_merge_failed', `no CRDT merger registered for crdtType ${JSON.stringify(crdtType)} (§5.10.2)`);
|
|
118
|
+
}
|
|
119
|
+
const storedRaw = storedValues?.[index];
|
|
120
|
+
const stored = storedRaw instanceof Uint8Array ? storedRaw : null;
|
|
121
|
+
let merged;
|
|
122
|
+
try {
|
|
123
|
+
merged = await merger(stored, incoming);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return errorRecord(opIndex, 'sync.crdt_merge_failed', `CRDT merger for ${JSON.stringify(crdtType)} threw: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
127
|
}
|
|
128
|
+
values[index] = merged;
|
|
129
|
+
changed = true;
|
|
55
130
|
}
|
|
56
|
-
return
|
|
131
|
+
return { changed };
|
|
57
132
|
}
|
|
58
|
-
function
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
distributionSyncMetric('sync.server.push.duration_ms', durationMs, {
|
|
67
|
-
unit: 'millisecond',
|
|
68
|
-
attributes: { status },
|
|
69
|
-
});
|
|
70
|
-
distributionSyncMetric('sync.server.push.emitted_changes', emittedChangeCount, {
|
|
71
|
-
attributes: { status },
|
|
72
|
-
});
|
|
73
|
-
distributionSyncMetric('sync.server.push.affected_tables', affectedTableCount, {
|
|
74
|
-
attributes: { status },
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
function createRejectedPushResult(error) {
|
|
78
|
-
return {
|
|
79
|
-
response: {
|
|
80
|
-
ok: true,
|
|
81
|
-
status: 'rejected',
|
|
82
|
-
results: [error],
|
|
83
|
-
},
|
|
84
|
-
affectedTables: [],
|
|
85
|
-
scopeKeys: [],
|
|
86
|
-
emittedChanges: [],
|
|
87
|
-
commitActorId: null,
|
|
88
|
-
commitCreatedAt: null,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
function createRejectedPushResponse(error, commitSeq) {
|
|
92
|
-
return {
|
|
93
|
-
ok: true,
|
|
94
|
-
status: 'rejected',
|
|
95
|
-
...(commitSeq !== undefined ? { commitSeq } : {}),
|
|
96
|
-
results: [error],
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
function validatePushRequest(request) {
|
|
100
|
-
if (!request.clientId || !request.clientCommitId) {
|
|
101
|
-
return {
|
|
102
|
-
opIndex: 0,
|
|
103
|
-
status: 'error',
|
|
104
|
-
error: 'Invalid push request',
|
|
105
|
-
code: 'sync.invalid_request',
|
|
106
|
-
retriable: false,
|
|
107
|
-
};
|
|
133
|
+
async function applyOperation(tx, schema, resolved, op, opIndex, blobCtx, crdtMergers, validators, partition, actorId) {
|
|
134
|
+
const table = schema.tables.get(op.table);
|
|
135
|
+
if (table === undefined) {
|
|
136
|
+
return errorRecord(opIndex, 'sync.unknown_table', `table ${JSON.stringify(op.table)} is not handled by this server`);
|
|
137
|
+
}
|
|
138
|
+
if (!resolved.ok) {
|
|
139
|
+
return errorRecord(opIndex, 'sync.forbidden', 'scope resolution failed (§3.4 step 4)');
|
|
108
140
|
}
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
141
|
+
const stored = await tx.getRow(op.table, op.rowId);
|
|
142
|
+
if (op.op === 'delete') {
|
|
143
|
+
if (stored === undefined) {
|
|
144
|
+
// Deleting an absent row is applied (idempotent, §6.2); no change.
|
|
145
|
+
return { kind: 'applied', change: undefined };
|
|
146
|
+
}
|
|
147
|
+
if (!authorizeWrite(table, stored.scopes, resolved)) {
|
|
148
|
+
return errorRecord(opIndex, 'sync.forbidden', 'delete denied by scope authorization (§3.4)');
|
|
149
|
+
}
|
|
150
|
+
const missing = missingScopeVariable(table, stored.scopes);
|
|
151
|
+
if (missing !== undefined) {
|
|
152
|
+
return errorRecord(opIndex, 'sync.missing_scopes', `stored row lacks scope variable ${JSON.stringify(missing)} (§3.1)`);
|
|
153
|
+
}
|
|
154
|
+
// §6.7: validate the delete against the stored row (row = undefined,
|
|
155
|
+
// stored = the row about to be removed). Only reached for an existing
|
|
156
|
+
// row — an absent-row delete is an idempotent no-op above.
|
|
157
|
+
const deleteReject = await runValidator(validators, table, 'delete', op.rowId, undefined, decodeRow(table.columns, stored.payload), opIndex, partition, actorId);
|
|
158
|
+
if (deleteReject !== undefined)
|
|
159
|
+
return deleteReject;
|
|
160
|
+
await tx.deleteRow(op.table, op.rowId);
|
|
111
161
|
return {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
162
|
+
kind: 'applied',
|
|
163
|
+
change: {
|
|
164
|
+
table: op.table,
|
|
165
|
+
rowId: op.rowId,
|
|
166
|
+
op: 'delete',
|
|
167
|
+
scopes: stored.scopes,
|
|
168
|
+
},
|
|
117
169
|
};
|
|
118
170
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return false;
|
|
171
|
+
// upsert — payload presence is enforced by the envelope codec (§6.1).
|
|
172
|
+
const payload = op.payload;
|
|
173
|
+
if (payload === undefined) {
|
|
174
|
+
return errorRecord(opIndex, 'sync.invalid_request', 'upsert without payload');
|
|
124
175
|
}
|
|
125
|
-
|
|
126
|
-
|
|
176
|
+
let values;
|
|
177
|
+
try {
|
|
178
|
+
values = decodeRow(table.columns, payload);
|
|
127
179
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return true;
|
|
180
|
+
catch (error) {
|
|
181
|
+
return errorRecord(opIndex, 'sync.invalid_request', `row payload failed row-codec decode (§1.7): ${error instanceof Error ? error.message : String(error)}`);
|
|
131
182
|
}
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
|
|
183
|
+
const pkValue = renderScopeValue(values[table.primaryKeyIndex]);
|
|
184
|
+
if (pkValue !== op.rowId) {
|
|
185
|
+
return errorRecord(opIndex, 'sync.invalid_request', 'payload primary key does not match rowId');
|
|
135
186
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
187
|
+
if (stored !== undefined) {
|
|
188
|
+
// §3.4 step 2: authorize against the STORED row, never the payload.
|
|
189
|
+
if (!authorizeWrite(table, stored.scopes, resolved)) {
|
|
190
|
+
return errorRecord(opIndex, 'sync.forbidden', 'write denied by scope authorization (§3.4)');
|
|
191
|
+
}
|
|
192
|
+
if (op.baseVersion === 0) {
|
|
193
|
+
// Lost insert race (§6.2); the stored row was authorized above,
|
|
194
|
+
// so disclosure of the winner is permitted.
|
|
195
|
+
return conflictRecord(opIndex, stored.serverVersion, stored.payload);
|
|
196
|
+
}
|
|
197
|
+
if (op.baseVersion !== undefined &&
|
|
198
|
+
op.baseVersion !== stored.serverVersion) {
|
|
199
|
+
return conflictRecord(opIndex, stored.serverVersion, stored.payload);
|
|
200
|
+
}
|
|
201
|
+
// §3.4 rule 5: scope columns are immutable on update — keep the
|
|
202
|
+
// stored row's scope column values on both the baseVersion and
|
|
203
|
+
// last-write-wins paths.
|
|
204
|
+
const storedValues = decodeRow(table.columns, stored.payload);
|
|
205
|
+
let mutated = false;
|
|
206
|
+
for (const pattern of table.scopePatterns) {
|
|
207
|
+
const storedValue = storedValues[pattern.columnIndex] ?? null;
|
|
208
|
+
if (values[pattern.columnIndex] !== storedValue) {
|
|
209
|
+
values[pattern.columnIndex] = storedValue;
|
|
210
|
+
mutated = true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// §5.10.3: crdt columns merge (stored ⊕ incoming) — never LWW, never
|
|
214
|
+
// baseVersion-conflict (they were excluded from the checks above).
|
|
215
|
+
const mergeOutcome = await mergeCrdtColumns(table, values, storedValues, opIndex, crdtMergers);
|
|
216
|
+
if ('kind' in mergeOutcome)
|
|
217
|
+
return mergeOutcome;
|
|
218
|
+
if (mergeOutcome.changed)
|
|
219
|
+
mutated = true;
|
|
220
|
+
const newPayload = mutated ? encodeRow(table.columns, values) : payload;
|
|
221
|
+
const newVersion = stored.serverVersion + 1;
|
|
222
|
+
// §6.6 / §5.9.6: verify referenced blobs exist before writing.
|
|
223
|
+
const blobCheck = await checkAndRecordBlobs(tx, table, op.rowId, values, opIndex, blobCtx);
|
|
224
|
+
if (blobCheck !== undefined)
|
|
225
|
+
return blobCheck;
|
|
226
|
+
// §6.7: validate the merged, scope-stripped row that will persist —
|
|
227
|
+
// for a crdt column the validator sees the MERGED value (§5.10.3), the
|
|
228
|
+
// state the store holds, not the raw pushed update.
|
|
229
|
+
const updateReject = await runValidator(validators, table, 'upsert', op.rowId, values, storedValues, opIndex, partition, actorId);
|
|
230
|
+
if (updateReject !== undefined)
|
|
231
|
+
return updateReject;
|
|
232
|
+
const newRow = {
|
|
233
|
+
rowId: op.rowId,
|
|
234
|
+
serverVersion: newVersion,
|
|
235
|
+
scopes: stored.scopes,
|
|
236
|
+
payload: newPayload,
|
|
237
|
+
};
|
|
238
|
+
await tx.upsertRow(op.table, newRow);
|
|
239
|
+
return {
|
|
240
|
+
kind: 'applied',
|
|
241
|
+
change: {
|
|
242
|
+
table: op.table,
|
|
243
|
+
rowId: op.rowId,
|
|
244
|
+
op: 'upsert',
|
|
245
|
+
rowVersion: newVersion,
|
|
246
|
+
scopes: stored.scopes,
|
|
247
|
+
payload: newPayload,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
168
250
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
.where('commit_seq', '=', args.commitSeq)
|
|
180
|
-
.execute();
|
|
181
|
-
if (args.affectedTables.length > 0) {
|
|
182
|
-
await syncTrx
|
|
183
|
-
.insertInto('sync_table_commits')
|
|
184
|
-
.values(args.affectedTables.map((table) => ({
|
|
185
|
-
partition_id: args.partitionId,
|
|
186
|
-
table,
|
|
187
|
-
commit_seq: args.commitSeq,
|
|
188
|
-
})))
|
|
189
|
-
.onConflict((oc) => oc.columns(['partition_id', 'table', 'commit_seq']).doNothing())
|
|
190
|
-
.execute();
|
|
251
|
+
// Insert path: no stored row.
|
|
252
|
+
if (op.baseVersion !== undefined && op.baseVersion !== 0) {
|
|
253
|
+
// Authorize the payload first so absence is not disclosed to actors
|
|
254
|
+
// without the scope; then §6.2: baseVersion ≠ 0, row absent.
|
|
255
|
+
const extractedFirst = storedScopesForRow(table, values);
|
|
256
|
+
if ('missing' in extractedFirst ||
|
|
257
|
+
!authorizeWrite(table, extractedFirst.scopes, resolved)) {
|
|
258
|
+
return errorRecord(opIndex, 'sync.forbidden', 'write denied by scope authorization (§3.4)');
|
|
259
|
+
}
|
|
260
|
+
return errorRecord(opIndex, 'sync.row_missing', 'upsert with baseVersion targets an absent row (§6.2)');
|
|
191
261
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
commitSeq: args.commitSeq,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
async function loadExistingCommitResult(args) {
|
|
200
|
-
let query = args.syncTrx.selectFrom('sync_commits')
|
|
201
|
-
.selectAll()
|
|
202
|
-
.where('partition_id', '=', args.partitionId)
|
|
203
|
-
.where('client_id', '=', args.request.clientId)
|
|
204
|
-
.where('client_commit_id', '=', args.request.clientCommitId);
|
|
205
|
-
if (args.dialect.supportsForUpdate) {
|
|
206
|
-
query = query.forUpdate();
|
|
262
|
+
const extracted = storedScopesForRow(table, values);
|
|
263
|
+
if ('missing' in extracted) {
|
|
264
|
+
// §3.4 step 2: a missing or empty scope column value ⇒ deny.
|
|
265
|
+
return errorRecord(opIndex, 'sync.forbidden', `insert missing scope column value for ${JSON.stringify(extracted.missing)} (§3.4)`);
|
|
207
266
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!isSyncPushResponse(parsedCached)) {
|
|
211
|
-
return createRejectedPushResult({
|
|
212
|
-
opIndex: 0,
|
|
213
|
-
status: 'error',
|
|
214
|
-
error: 'Idempotency cache miss',
|
|
215
|
-
code: 'sync.idempotency_cache_miss',
|
|
216
|
-
retriable: true,
|
|
217
|
-
});
|
|
267
|
+
if (!authorizeWrite(table, extracted.scopes, resolved)) {
|
|
268
|
+
return errorRecord(opIndex, 'sync.forbidden', 'insert denied by scope authorization (§3.4)');
|
|
218
269
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
270
|
+
// §5.10.3: on insert a crdt column merges against the empty document
|
|
271
|
+
// (stored = null) — normalizes the initial state through the merger.
|
|
272
|
+
const insertMerge = await mergeCrdtColumns(table, values, undefined, opIndex, crdtMergers);
|
|
273
|
+
if ('kind' in insertMerge)
|
|
274
|
+
return insertMerge;
|
|
275
|
+
const insertPayload = insertMerge.changed
|
|
276
|
+
? encodeRow(table.columns, values)
|
|
277
|
+
: payload;
|
|
278
|
+
// §6.6 / §5.9.6: verify referenced blobs exist before writing.
|
|
279
|
+
const blobCheck = await checkAndRecordBlobs(tx, table, op.rowId, values, opIndex, blobCtx);
|
|
280
|
+
if (blobCheck !== undefined)
|
|
281
|
+
return blobCheck;
|
|
282
|
+
// §6.7: validate the insert row (stored = undefined, so a validator can
|
|
283
|
+
// distinguish create from update); crdt columns are already merged
|
|
284
|
+
// against the empty document.
|
|
285
|
+
const insertReject = await runValidator(validators, table, 'upsert', op.rowId, values, undefined, opIndex, partition, actorId);
|
|
286
|
+
if (insertReject !== undefined)
|
|
287
|
+
return insertReject;
|
|
288
|
+
const newRow = {
|
|
289
|
+
rowId: op.rowId,
|
|
290
|
+
serverVersion: 1,
|
|
291
|
+
scopes: extracted.scopes,
|
|
292
|
+
payload: insertPayload,
|
|
222
293
|
};
|
|
223
|
-
|
|
224
|
-
const tablesFromDb = args.dialect.dbToArray(existing.affected_tables);
|
|
225
|
-
return {
|
|
226
|
-
response: { ...base, status: 'cached' },
|
|
227
|
-
affectedTables: tablesFromDb.length > 0
|
|
228
|
-
? tablesFromDb
|
|
229
|
-
: await readCommitAffectedTables(args.trx, args.dialect, Number(existing.commit_seq), args.partitionId),
|
|
230
|
-
scopeKeys: [],
|
|
231
|
-
emittedChanges: [],
|
|
232
|
-
commitActorId: typeof existing.actor_id === 'string' ? existing.actor_id : null,
|
|
233
|
-
commitCreatedAt: typeof existing.created_at === 'string' ? existing.created_at : null,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
294
|
+
await tx.upsertRow(op.table, newRow);
|
|
236
295
|
return {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
296
|
+
kind: 'applied',
|
|
297
|
+
change: {
|
|
298
|
+
table: op.table,
|
|
299
|
+
rowId: op.rowId,
|
|
300
|
+
op: 'upsert',
|
|
301
|
+
rowVersion: 1,
|
|
302
|
+
scopes: extracted.scopes,
|
|
303
|
+
payload: insertPayload,
|
|
304
|
+
},
|
|
243
305
|
};
|
|
244
306
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
307
|
+
/**
|
|
308
|
+
* §5.9.6/§6.6: for a row's `blob_ref` columns, verify every referenced blob
|
|
309
|
+
* exists, then record the row's reference set in the index (§5.9.4). Returns
|
|
310
|
+
* a terminating `blob.not_found` outcome if any blob is absent (or the store
|
|
311
|
+
* is unconfigured while a ref exists), else `undefined` (proceed). No-op for
|
|
312
|
+
* tables with no `blob_ref` columns.
|
|
313
|
+
*/
|
|
314
|
+
async function checkAndRecordBlobs(tx, table, rowId, values, opIndex, blobCtx) {
|
|
315
|
+
if (table.blobRefColumnIndices.length === 0)
|
|
316
|
+
return undefined;
|
|
317
|
+
const blobIds = blobIdsInRow(table, values);
|
|
318
|
+
if (blobIds.length > 0) {
|
|
319
|
+
if (blobCtx.store === undefined) {
|
|
320
|
+
return errorRecord(opIndex, 'blob.not_found', 'row references a blob but the server has no blob store (§5.9.6)');
|
|
321
|
+
}
|
|
322
|
+
for (const blobId of blobIds) {
|
|
323
|
+
if (!(await blobCtx.store.has(blobCtx.partition, blobId))) {
|
|
324
|
+
return errorRecord(opIndex, 'blob.not_found', `push references blob ${blobId} which has not been uploaded (§5.9.6)`);
|
|
325
|
+
}
|
|
257
326
|
}
|
|
258
|
-
return coerceNumber(insertedCommit.commit_seq) ?? 0;
|
|
259
327
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.
|
|
263
|
-
.onConflict((oc) => oc.columns(['partition_id', 'client_id', 'client_commit_id']).doNothing())
|
|
264
|
-
.executeTakeFirstOrThrow();
|
|
265
|
-
const insertedRows = Number(insertResult.numInsertedOrUpdatedRows ?? 0);
|
|
266
|
-
if (insertedRows === 0) {
|
|
267
|
-
return null;
|
|
328
|
+
// Update the reference index for this row (empty set clears it, §5.9.4).
|
|
329
|
+
if (tx.setBlobRefs !== undefined) {
|
|
330
|
+
await tx.setBlobRefs(table.name, rowId, blobIds);
|
|
268
331
|
}
|
|
269
|
-
return
|
|
332
|
+
return undefined;
|
|
270
333
|
}
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
for (let i = 0; i < ops.length;) {
|
|
277
|
-
const op = ops[i];
|
|
278
|
-
const handler = args.handlers.byTable.get(op.table);
|
|
279
|
-
if (!handler) {
|
|
280
|
-
throw new Error(`Unknown table: ${op.table}`);
|
|
281
|
-
}
|
|
282
|
-
const operationCtx = {
|
|
283
|
-
db: args.trx,
|
|
284
|
-
trx: args.trx,
|
|
285
|
-
actorId: args.actorId,
|
|
286
|
-
auth: args.auth,
|
|
287
|
-
clientId: args.request.clientId,
|
|
288
|
-
commitId: args.commitId,
|
|
289
|
-
schemaVersion: args.request.schemaVersion,
|
|
290
|
-
authLease: args.request.authLease,
|
|
291
|
-
};
|
|
292
|
-
let transformedOp = op;
|
|
293
|
-
for (const plugin of args.pushPlugins) {
|
|
294
|
-
if (!plugin.beforeApplyOperation)
|
|
295
|
-
continue;
|
|
296
|
-
const nextOp = await plugin.beforeApplyOperation({
|
|
297
|
-
ctx: operationCtx,
|
|
298
|
-
tableHandler: handler,
|
|
299
|
-
op: transformedOp,
|
|
300
|
-
opIndex: i,
|
|
301
|
-
});
|
|
302
|
-
assertOperationIdentityUnchanged(plugin.name, op, nextOp);
|
|
303
|
-
transformedOp = nextOp;
|
|
304
|
-
}
|
|
305
|
-
let appliedBatch = null;
|
|
306
|
-
let consumed = 1;
|
|
307
|
-
if (args.pushPlugins.length === 0 && handler.applyOperationBatch) {
|
|
308
|
-
const batchInput = [];
|
|
309
|
-
for (let j = i; j < ops.length; j++) {
|
|
310
|
-
const nextOp = ops[j];
|
|
311
|
-
if (nextOp.table !== op.table)
|
|
312
|
-
break;
|
|
313
|
-
batchInput.push({ op: nextOp, opIndex: j });
|
|
314
|
-
}
|
|
315
|
-
if (batchInput.length > 1) {
|
|
316
|
-
appliedBatch = await handler.applyOperationBatch(operationCtx, batchInput);
|
|
317
|
-
consumed = Math.max(1, appliedBatch.length);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
if (!appliedBatch) {
|
|
321
|
-
let appliedSingle = await handler.applyOperation(operationCtx, transformedOp, i);
|
|
322
|
-
for (const plugin of args.pushPlugins) {
|
|
323
|
-
if (!plugin.afterApplyOperation)
|
|
324
|
-
continue;
|
|
325
|
-
appliedSingle = await plugin.afterApplyOperation({
|
|
326
|
-
ctx: operationCtx,
|
|
327
|
-
tableHandler: handler,
|
|
328
|
-
op: transformedOp,
|
|
329
|
-
opIndex: i,
|
|
330
|
-
applied: appliedSingle,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
appliedBatch = [appliedSingle];
|
|
334
|
-
}
|
|
335
|
-
if (appliedBatch.length === 0) {
|
|
336
|
-
throw new Error(`Handler "${op.table}" returned no results from applyOperationBatch`);
|
|
337
|
-
}
|
|
338
|
-
for (const applied of appliedBatch) {
|
|
339
|
-
if (applied.result.status !== 'applied') {
|
|
340
|
-
results.push(applied.result);
|
|
341
|
-
throw new RejectCommitError(createRejectedPushResponse(applied.result, args.commitSeq));
|
|
342
|
-
}
|
|
343
|
-
for (const change of applied.emittedChanges ?? []) {
|
|
344
|
-
const scopes = change?.scopes;
|
|
345
|
-
if (!scopes || typeof scopes !== 'object') {
|
|
346
|
-
const error = {
|
|
347
|
-
opIndex: applied.result.opIndex,
|
|
348
|
-
status: 'error',
|
|
349
|
-
error: 'Missing scopes',
|
|
350
|
-
code: 'sync.missing_scopes',
|
|
351
|
-
retriable: false,
|
|
352
|
-
};
|
|
353
|
-
results.push(error);
|
|
354
|
-
throw new RejectCommitError(createRejectedPushResponse(error, args.commitSeq));
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
results.push(applied.result);
|
|
358
|
-
allEmitted.push(...applied.emittedChanges);
|
|
359
|
-
for (const change of applied.emittedChanges) {
|
|
360
|
-
affectedTablesSet.add(change.table);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
i += consumed;
|
|
334
|
+
function missingScopeVariable(table, scopes) {
|
|
335
|
+
for (const pattern of table.scopePatterns) {
|
|
336
|
+
const value = scopes[pattern.variable];
|
|
337
|
+
if (value === undefined || value.length === 0)
|
|
338
|
+
return pattern.variable;
|
|
364
339
|
}
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
function resultFrame(clientCommitId, stored, replay) {
|
|
343
|
+
const status = stored.status === 'applied' ? (replay ? 'cached' : 'applied') : 'rejected';
|
|
365
344
|
return {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
345
|
+
type: 'PUSH_RESULT',
|
|
346
|
+
clientCommitId,
|
|
347
|
+
status,
|
|
348
|
+
...(stored.status === 'applied' && stored.commitSeq !== undefined
|
|
349
|
+
? { commitSeq: stored.commitSeq }
|
|
350
|
+
: {}),
|
|
351
|
+
results: [...stored.results],
|
|
369
352
|
};
|
|
370
353
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
.where('partition_id', '=', partitionId)
|
|
381
|
-
.where('client_id', '=', request.clientId)
|
|
382
|
-
.where('client_commit_id', '=', request.clientCommitId)
|
|
383
|
-
.where('result_json', 'is', null)
|
|
384
|
-
.execute();
|
|
385
|
-
}
|
|
386
|
-
const commitCreatedAt = new Date().toISOString();
|
|
387
|
-
const commitRow = {
|
|
388
|
-
partition_id: partitionId,
|
|
389
|
-
actor_id: actorId,
|
|
390
|
-
client_id: request.clientId,
|
|
391
|
-
client_commit_id: request.clientCommitId,
|
|
392
|
-
created_at: commitCreatedAt,
|
|
393
|
-
meta: request.authLease
|
|
394
|
-
? toDialectJsonValue(dialect, {
|
|
395
|
-
authLease: request.authLease,
|
|
396
|
-
})
|
|
397
|
-
: null,
|
|
398
|
-
result_json: null,
|
|
399
|
-
};
|
|
400
|
-
let commitSeq = (await insertPendingCommit({
|
|
401
|
-
syncTrx,
|
|
402
|
-
dialect,
|
|
403
|
-
commitRow,
|
|
404
|
-
})) ?? 0;
|
|
405
|
-
if (commitSeq === 0) {
|
|
406
|
-
return loadExistingCommitResult({
|
|
407
|
-
trx,
|
|
408
|
-
syncTrx,
|
|
409
|
-
dialect,
|
|
410
|
-
request,
|
|
411
|
-
partitionId,
|
|
412
|
-
});
|
|
354
|
+
/**
|
|
355
|
+
* Process one `PUSH_COMMIT` frame: idempotency replay (§2.3), sequential
|
|
356
|
+
* atomic apply (§6.4), realtime notification for applied commits.
|
|
357
|
+
*/
|
|
358
|
+
export async function processPushCommit(ctx, schema, resolved, clientId, frame) {
|
|
359
|
+
const { storage, partition } = ctx;
|
|
360
|
+
let persisted;
|
|
361
|
+
try {
|
|
362
|
+
persisted = await storage.getPushResult(partition, clientId, frame.clientCommitId);
|
|
413
363
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
.
|
|
417
|
-
.
|
|
418
|
-
.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
364
|
+
catch (error) {
|
|
365
|
+
if (error instanceof SyncError &&
|
|
366
|
+
error.code === 'sync.idempotency_cache_miss') {
|
|
367
|
+
// §6.3: answer the retryable cache-miss for this commit rather than
|
|
368
|
+
// re-applying. Not persisted — a retry may find a readable record.
|
|
369
|
+
return {
|
|
370
|
+
type: 'PUSH_RESULT',
|
|
371
|
+
clientCommitId: frame.clientCommitId,
|
|
372
|
+
status: 'rejected',
|
|
373
|
+
results: [
|
|
374
|
+
{
|
|
375
|
+
opIndex: 0,
|
|
376
|
+
status: 'error',
|
|
377
|
+
code: 'sync.idempotency_cache_miss',
|
|
378
|
+
message: error.message,
|
|
379
|
+
retryable: true,
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
throw error;
|
|
422
385
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
trx,
|
|
426
|
-
dialect,
|
|
427
|
-
auth: args.auth,
|
|
428
|
-
request,
|
|
429
|
-
partitionId,
|
|
430
|
-
actorId,
|
|
431
|
-
commitSeq,
|
|
432
|
-
});
|
|
433
|
-
if (validationResult) {
|
|
434
|
-
const response = createRejectedPushResponse(validationResult, commitSeq);
|
|
435
|
-
await persistCommitOutcome({
|
|
436
|
-
trx,
|
|
437
|
-
dialect,
|
|
438
|
-
partitionId,
|
|
439
|
-
commitSeq,
|
|
440
|
-
response,
|
|
441
|
-
affectedTables: [],
|
|
442
|
-
emittedChangeCount: 0,
|
|
443
|
-
});
|
|
444
|
-
return {
|
|
445
|
-
response,
|
|
446
|
-
affectedTables: [],
|
|
447
|
-
scopeKeys: [],
|
|
448
|
-
emittedChanges: [],
|
|
449
|
-
commitActorId: actorId,
|
|
450
|
-
commitCreatedAt,
|
|
451
|
-
};
|
|
386
|
+
if (persisted !== undefined) {
|
|
387
|
+
return resultFrame(frame.clientCommitId, persisted, true);
|
|
452
388
|
}
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
});
|
|
459
|
-
let savepointCreated = false;
|
|
389
|
+
const createdAtMs = clockOf(ctx)();
|
|
390
|
+
const blobCtx = { store: ctx.blobs, partition };
|
|
391
|
+
const crdtMergers = ctx.crdtMergers;
|
|
392
|
+
const validators = ctx.validators;
|
|
393
|
+
const tx = await storage.begin(partition);
|
|
460
394
|
try {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
ok: true,
|
|
477
|
-
status: 'applied',
|
|
478
|
-
commitSeq,
|
|
479
|
-
results: applied.results,
|
|
480
|
-
};
|
|
481
|
-
await persistEmittedChanges({
|
|
482
|
-
trx,
|
|
483
|
-
dialect,
|
|
484
|
-
partitionId,
|
|
485
|
-
commitSeq,
|
|
486
|
-
emittedChanges: applied.emittedChanges,
|
|
487
|
-
});
|
|
488
|
-
await persistCommitOutcome({
|
|
489
|
-
trx,
|
|
490
|
-
dialect,
|
|
491
|
-
partitionId,
|
|
492
|
-
commitSeq,
|
|
493
|
-
response: appliedResponse,
|
|
494
|
-
affectedTables: applied.affectedTables,
|
|
495
|
-
emittedChangeCount: applied.emittedChanges.length,
|
|
496
|
-
});
|
|
497
|
-
if (useSavepoints) {
|
|
498
|
-
await sql.raw(`RELEASE SAVEPOINT ${savepointName}`).execute(trx);
|
|
395
|
+
const results = [];
|
|
396
|
+
const changes = [];
|
|
397
|
+
let terminated;
|
|
398
|
+
for (let opIndex = 0; opIndex < frame.operations.length; opIndex++) {
|
|
399
|
+
const op = frame.operations[opIndex];
|
|
400
|
+
if (op === undefined)
|
|
401
|
+
continue;
|
|
402
|
+
const outcome = await applyOperation(tx, schema, resolved, op, opIndex, blobCtx, crdtMergers, validators, partition, ctx.actorId);
|
|
403
|
+
if (outcome.kind === 'terminate') {
|
|
404
|
+
terminated = outcome.record;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
results.push({ opIndex, status: 'applied' });
|
|
408
|
+
if (outcome.change !== undefined)
|
|
409
|
+
changes.push(outcome.change);
|
|
499
410
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
catch (error) {
|
|
510
|
-
if (savepointCreated) {
|
|
411
|
+
if (terminated !== undefined) {
|
|
412
|
+
// §6.3 rejected: only the terminating operation's record; §6.4:
|
|
413
|
+
// every write of the commit rolls back.
|
|
414
|
+
await tx.rollback();
|
|
415
|
+
const stored = {
|
|
416
|
+
status: 'rejected',
|
|
417
|
+
results: [terminated],
|
|
418
|
+
};
|
|
419
|
+
const rejectionTx = await storage.begin(partition);
|
|
511
420
|
try {
|
|
512
|
-
await
|
|
513
|
-
await
|
|
421
|
+
await rejectionTx.putPushResult(clientId, frame.clientCommitId, stored);
|
|
422
|
+
await rejectionTx.commit();
|
|
514
423
|
}
|
|
515
|
-
catch (
|
|
516
|
-
|
|
517
|
-
throw
|
|
424
|
+
catch (error) {
|
|
425
|
+
await rejectionTx.rollback();
|
|
426
|
+
throw error;
|
|
518
427
|
}
|
|
428
|
+
return resultFrame(frame.clientCommitId, stored, false);
|
|
519
429
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
partitionId,
|
|
527
|
-
commitSeq,
|
|
528
|
-
response: error.response,
|
|
529
|
-
affectedTables: [],
|
|
530
|
-
emittedChangeCount: 0,
|
|
430
|
+
const commitSeq = await tx.appendCommit({
|
|
431
|
+
clientId,
|
|
432
|
+
clientCommitId: frame.clientCommitId,
|
|
433
|
+
actorId: ctx.actorId,
|
|
434
|
+
createdAtMs,
|
|
435
|
+
changes,
|
|
531
436
|
});
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
export async function pushCommit(args) {
|
|
543
|
-
const { db, dialect, handlers, request } = args;
|
|
544
|
-
const pushPlugins = sortServerPushPlugins(args.plugins);
|
|
545
|
-
const requestedOps = Array.isArray(request.operations)
|
|
546
|
-
? request.operations
|
|
547
|
-
: [];
|
|
548
|
-
const operationCount = requestedOps.length;
|
|
549
|
-
const startedAtMs = Date.now();
|
|
550
|
-
const suppressTelemetry = args.suppressTelemetry === true;
|
|
551
|
-
return startSyncSpan({
|
|
552
|
-
name: 'sync.server.push',
|
|
553
|
-
op: 'sync.push',
|
|
554
|
-
attributes: {
|
|
555
|
-
operation_count: operationCount,
|
|
556
|
-
},
|
|
557
|
-
}, async (span) => {
|
|
558
|
-
const finalizeResult = (result) => {
|
|
559
|
-
const durationMs = Math.max(0, Date.now() - startedAtMs);
|
|
560
|
-
const status = result.response.status;
|
|
561
|
-
span.setAttribute('status', status);
|
|
562
|
-
span.setAttribute('duration_ms', durationMs);
|
|
563
|
-
span.setAttribute('emitted_change_count', result.emittedChanges.length);
|
|
564
|
-
span.setAttribute('affected_table_count', result.affectedTables.length);
|
|
565
|
-
span.setStatus('ok');
|
|
566
|
-
if (!suppressTelemetry) {
|
|
567
|
-
recordPushMetrics({
|
|
568
|
-
status,
|
|
569
|
-
durationMs,
|
|
570
|
-
operationCount,
|
|
571
|
-
emittedChangeCount: result.emittedChanges.length,
|
|
572
|
-
affectedTableCount: result.affectedTables.length,
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
return result;
|
|
576
|
-
};
|
|
577
|
-
try {
|
|
578
|
-
const validationError = validatePushRequest(request);
|
|
579
|
-
if (validationError) {
|
|
580
|
-
return finalizeResult(createRejectedPushResult(validationError));
|
|
581
|
-
}
|
|
582
|
-
return finalizeResult(await dialect.executeInTransaction(db, async (trx) => executePushCommitInExecutor({
|
|
583
|
-
trx,
|
|
584
|
-
dialect,
|
|
585
|
-
handlers,
|
|
586
|
-
pushPlugins,
|
|
587
|
-
auth: args.auth,
|
|
588
|
-
request,
|
|
589
|
-
validateCommit: args.validateCommit,
|
|
590
|
-
})));
|
|
591
|
-
}
|
|
592
|
-
catch (error) {
|
|
593
|
-
const durationMs = Math.max(0, Date.now() - startedAtMs);
|
|
594
|
-
span.setAttribute('status', 'error');
|
|
595
|
-
span.setAttribute('duration_ms', durationMs);
|
|
596
|
-
span.setStatus('error');
|
|
597
|
-
if (!suppressTelemetry) {
|
|
598
|
-
recordPushMetrics({
|
|
599
|
-
status: 'error',
|
|
600
|
-
durationMs,
|
|
601
|
-
operationCount,
|
|
602
|
-
emittedChangeCount: 0,
|
|
603
|
-
affectedTableCount: 0,
|
|
604
|
-
});
|
|
605
|
-
captureSyncException(error, {
|
|
606
|
-
event: 'sync.server.push',
|
|
607
|
-
operationCount,
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
throw error;
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
export async function pushCommitBatch(args) {
|
|
615
|
-
const { db, dialect, handlers, requests } = args;
|
|
616
|
-
const pushPlugins = sortServerPushPlugins(args.plugins);
|
|
617
|
-
const startedAtMs = Date.now();
|
|
618
|
-
const suppressTelemetry = args.suppressTelemetry === true;
|
|
619
|
-
const totalOperationCount = requests.reduce((count, request) => {
|
|
620
|
-
const operations = Array.isArray(request.operations)
|
|
621
|
-
? request.operations
|
|
622
|
-
: [];
|
|
623
|
-
return count + operations.length;
|
|
624
|
-
}, 0);
|
|
625
|
-
return startSyncSpan({
|
|
626
|
-
name: 'sync.server.push_batch',
|
|
627
|
-
op: 'sync.push.batch',
|
|
628
|
-
attributes: {
|
|
629
|
-
commit_count: requests.length,
|
|
630
|
-
operation_count: totalOperationCount,
|
|
631
|
-
},
|
|
632
|
-
}, async (span) => {
|
|
633
|
-
try {
|
|
634
|
-
const results = await dialect.executeInTransaction(db, async (trx) => {
|
|
635
|
-
const executed = [];
|
|
636
|
-
for (const request of requests) {
|
|
637
|
-
const validationError = validatePushRequest(request);
|
|
638
|
-
if (validationError) {
|
|
639
|
-
executed.push(createRejectedPushResult(validationError));
|
|
640
|
-
continue;
|
|
641
|
-
}
|
|
642
|
-
executed.push(await executePushCommitInExecutor({
|
|
643
|
-
trx,
|
|
644
|
-
dialect,
|
|
645
|
-
handlers,
|
|
646
|
-
pushPlugins,
|
|
647
|
-
auth: args.auth,
|
|
648
|
-
request,
|
|
649
|
-
validateCommit: args.validateCommit,
|
|
650
|
-
}));
|
|
651
|
-
}
|
|
652
|
-
return executed;
|
|
437
|
+
const stored = { status: 'applied', commitSeq, results };
|
|
438
|
+
await tx.putPushResult(clientId, frame.clientCommitId, stored);
|
|
439
|
+
await tx.commit();
|
|
440
|
+
if (ctx.realtime !== undefined && changes.length > 0) {
|
|
441
|
+
await ctx.realtime.notifyCommit(partition, {
|
|
442
|
+
commitSeq,
|
|
443
|
+
createdAtMs,
|
|
444
|
+
actorId: ctx.actorId,
|
|
445
|
+
changes,
|
|
653
446
|
});
|
|
654
|
-
const durationMs = Math.max(0, Date.now() - startedAtMs);
|
|
655
|
-
const emittedChangeCount = results.reduce((count, result) => count + result.emittedChanges.length, 0);
|
|
656
|
-
const affectedTableCount = results.reduce((count, result) => count + result.affectedTables.length, 0);
|
|
657
|
-
const status = results.every((result) => result.response.status === 'cached')
|
|
658
|
-
? 'cached'
|
|
659
|
-
: results.every((result) => result.response.status === 'applied' ||
|
|
660
|
-
result.response.status === 'cached')
|
|
661
|
-
? 'applied'
|
|
662
|
-
: 'rejected';
|
|
663
|
-
span.setAttribute('status', status);
|
|
664
|
-
span.setAttribute('duration_ms', durationMs);
|
|
665
|
-
span.setAttribute('commit_count', results.length);
|
|
666
|
-
span.setAttribute('emitted_change_count', emittedChangeCount);
|
|
667
|
-
span.setAttribute('affected_table_count', affectedTableCount);
|
|
668
|
-
span.setStatus('ok');
|
|
669
|
-
if (!suppressTelemetry) {
|
|
670
|
-
recordPushMetrics({
|
|
671
|
-
status,
|
|
672
|
-
durationMs,
|
|
673
|
-
operationCount: totalOperationCount,
|
|
674
|
-
emittedChangeCount,
|
|
675
|
-
affectedTableCount,
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
return results;
|
|
679
|
-
}
|
|
680
|
-
catch (error) {
|
|
681
|
-
const durationMs = Math.max(0, Date.now() - startedAtMs);
|
|
682
|
-
span.setAttribute('status', 'error');
|
|
683
|
-
span.setAttribute('duration_ms', durationMs);
|
|
684
|
-
span.setStatus('error');
|
|
685
|
-
if (!suppressTelemetry) {
|
|
686
|
-
recordPushMetrics({
|
|
687
|
-
status: 'error',
|
|
688
|
-
durationMs,
|
|
689
|
-
operationCount: totalOperationCount,
|
|
690
|
-
emittedChangeCount: 0,
|
|
691
|
-
affectedTableCount: 0,
|
|
692
|
-
});
|
|
693
|
-
captureSyncException(error, {
|
|
694
|
-
event: 'sync.server.push_batch',
|
|
695
|
-
commitCount: requests.length,
|
|
696
|
-
operationCount: totalOperationCount,
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
throw error;
|
|
700
447
|
}
|
|
701
|
-
|
|
448
|
+
return resultFrame(frame.clientCommitId, stored, false);
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
await tx.rollback();
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
702
454
|
}
|
|
703
|
-
//# sourceMappingURL=push.js.map
|