@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,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed-URL segment delivery (SPEC.md §5.4): the native HMAC token
|
|
3
|
+
* scheme, plus the delegated-presign alternative (S3/R2/GCS) behind one
|
|
4
|
+
* issuance seam.
|
|
5
|
+
*
|
|
6
|
+
* Native: `st = base64url(payloadJson) + "." +
|
|
7
|
+
* base64url(HMAC-SHA256(key, payloadJson))` with claims
|
|
8
|
+
* `{v, seg, sd, aud, exp}`; `exp` is unix seconds. Issuance happens inside
|
|
9
|
+
* the pull immediately after scope resolution; verification checks MAC,
|
|
10
|
+
* expiry (≤ 60 s skew), and `seg`/`sd`/`aud` equality. Actual CDN serving
|
|
11
|
+
* is out of scope.
|
|
12
|
+
*
|
|
13
|
+
* Delegated presign: the provider mints the URL (§5.4 equivalence rule —
|
|
14
|
+
* the signed object key embeds the `segmentId`, the expiry obeys the same
|
|
15
|
+
* TTL guidance); `issueSegmentUrl` routes to whichever config the host
|
|
16
|
+
* supplied. Both paths issue only after scope resolution, so a URL is
|
|
17
|
+
* never minted for scopes the actor did not hold at that moment.
|
|
18
|
+
*/
|
|
19
|
+
import { syncError } from './errors';
|
|
20
|
+
|
|
21
|
+
export interface SignedUrlConfig {
|
|
22
|
+
/** HMAC key (raw bytes or UTF-8 string). */
|
|
23
|
+
readonly key: string | Uint8Array;
|
|
24
|
+
/** URL prefix; the segment URL is `${baseUrl}/${segmentId}?st=${token}`. */
|
|
25
|
+
readonly baseUrl: string;
|
|
26
|
+
/** Token TTL; SHOULD be ≤ 15 minutes (§5.4). Default 900. */
|
|
27
|
+
readonly ttlSeconds?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Opaque per-partition audience (§5.4): stable per partition, MUST NOT
|
|
30
|
+
* disclose the internal partition id.
|
|
31
|
+
*/
|
|
32
|
+
readonly audience: (partition: string) => string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** What descriptor emission needs: the §5.4 `url`/`urlExpiresAtMs` pair. */
|
|
36
|
+
export interface SegmentUrlIssue {
|
|
37
|
+
readonly url: string;
|
|
38
|
+
readonly urlExpiresAtMs: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SegmentPresignArgs {
|
|
42
|
+
readonly segmentId: string;
|
|
43
|
+
readonly partition: string;
|
|
44
|
+
readonly scopeDigest: string;
|
|
45
|
+
readonly nowMs: number;
|
|
46
|
+
/** Resolved TTL (config value or the 900 s default) — already applied. */
|
|
47
|
+
readonly ttlSeconds: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Delegated presign (§5.4): the provider signs the URL. The callback MUST
|
|
52
|
+
* satisfy the equivalence rule — the signed object key embeds exactly one
|
|
53
|
+
* `segmentId`, and `urlExpiresAtMs` reflects the real provider expiry.
|
|
54
|
+
* `S3SegmentStore.presignSegmentGet` (via `s3PresignedUrls`) is the
|
|
55
|
+
* in-tree implementation.
|
|
56
|
+
*/
|
|
57
|
+
export interface DelegatedPresignConfig {
|
|
58
|
+
readonly presign: (
|
|
59
|
+
args: SegmentPresignArgs,
|
|
60
|
+
) => SegmentUrlIssue | Promise<SegmentUrlIssue>;
|
|
61
|
+
/** URL TTL; SHOULD be ≤ 15 minutes (§5.4). Default 900. */
|
|
62
|
+
readonly ttlSeconds?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Either §5.4 scheme; the pull emits descriptors identically for both. */
|
|
66
|
+
export type SegmentUrlConfig = SignedUrlConfig | DelegatedPresignConfig;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Issue the §5.4 `url`/`urlExpiresAtMs` pair for one segment descriptor,
|
|
70
|
+
* routing to the native HMAC token or the delegated presigner. Callers
|
|
71
|
+
* (the pull) invoke this only after scope resolution and only when the
|
|
72
|
+
* client advertised accept bit 3.
|
|
73
|
+
*/
|
|
74
|
+
export async function issueSegmentUrl(
|
|
75
|
+
config: SegmentUrlConfig,
|
|
76
|
+
args: {
|
|
77
|
+
readonly segmentId: string;
|
|
78
|
+
readonly partition: string;
|
|
79
|
+
readonly scopeDigest: string;
|
|
80
|
+
readonly nowMs: number;
|
|
81
|
+
},
|
|
82
|
+
): Promise<SegmentUrlIssue> {
|
|
83
|
+
const ttlSeconds = config.ttlSeconds ?? 900;
|
|
84
|
+
if ('presign' in config) {
|
|
85
|
+
return config.presign({ ...args, ttlSeconds });
|
|
86
|
+
}
|
|
87
|
+
const exp = Math.floor(args.nowMs / 1000) + ttlSeconds;
|
|
88
|
+
const token = await signSegmentToken(config.key, {
|
|
89
|
+
v: 1,
|
|
90
|
+
seg: args.segmentId,
|
|
91
|
+
sd: args.scopeDigest,
|
|
92
|
+
aud: config.audience(args.partition),
|
|
93
|
+
exp,
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
url: `${config.baseUrl}/${args.segmentId}?st=${token}`,
|
|
97
|
+
urlExpiresAtMs: exp * 1000,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Delegated presign for BLOB downloads (§5.9.5). The blob twin of
|
|
103
|
+
* `DelegatedPresignConfig`: the provider signs the URL and the signed object
|
|
104
|
+
* key embeds the `blobId`. Issuance happens ONLY after the §5.9.5
|
|
105
|
+
* row-derived authorization check (never before), so the URL is a short-TTL
|
|
106
|
+
* bearer grant to exactly those immutable bytes — the SPEC's "authorization
|
|
107
|
+
* was resolved against the referencing rows at issuance". Unlike segments,
|
|
108
|
+
* there is no native-HMAC blob token scheme wired this rung; presign is the
|
|
109
|
+
* shipped path (`S3BlobStore.presignBlobGet` via `s3PresignedBlobUrls`).
|
|
110
|
+
*/
|
|
111
|
+
export interface BlobPresignArgs {
|
|
112
|
+
readonly partition: string;
|
|
113
|
+
readonly blobId: string;
|
|
114
|
+
readonly nowMs: number;
|
|
115
|
+
/** Resolved TTL (config value or the 900 s default) — already applied. */
|
|
116
|
+
readonly ttlSeconds: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface BlobPresignConfig {
|
|
120
|
+
readonly presign: (
|
|
121
|
+
args: BlobPresignArgs,
|
|
122
|
+
) => SegmentUrlIssue | Promise<SegmentUrlIssue>;
|
|
123
|
+
/** URL TTL; SHOULD be ≤ 15 minutes (§5.9.5). Default 900. */
|
|
124
|
+
readonly ttlSeconds?: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Issue the §5.9.5 `url`/`urlExpiresAtMs` pair for one blob download. The
|
|
129
|
+
* caller (blob-handlers `downloadBlob`) invokes this only after the
|
|
130
|
+
* row-derived authorization check passes.
|
|
131
|
+
*/
|
|
132
|
+
export async function issueBlobUrl(
|
|
133
|
+
config: BlobPresignConfig,
|
|
134
|
+
args: {
|
|
135
|
+
readonly partition: string;
|
|
136
|
+
readonly blobId: string;
|
|
137
|
+
readonly nowMs: number;
|
|
138
|
+
},
|
|
139
|
+
): Promise<SegmentUrlIssue> {
|
|
140
|
+
const ttlSeconds = config.ttlSeconds ?? 900;
|
|
141
|
+
return config.presign({ ...args, ttlSeconds });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Delegated presign for BLOB UPLOADS (§5.9.3 direct-to-storage). The upload
|
|
146
|
+
* twin of `BlobPresignConfig`: the provider signs a single PUT whose object
|
|
147
|
+
* key embeds the `blobId`. Issuance happens ONLY after host authentication in
|
|
148
|
+
* the upload-grant handler (uploading is host-auth-only, not scope-bearing —
|
|
149
|
+
* §5.9.3). The `byteLength` rides through so a provider MAY bind
|
|
150
|
+
* `Content-Length` (S3 conditional) to the granted size. Never a multipart or
|
|
151
|
+
* chunk protocol — single PUT only, this rung and the next (§5.9.3 non-goal).
|
|
152
|
+
* `S3BlobStore.presignBlobPut` (via `s3PresignedBlobUploads`) is the in-tree
|
|
153
|
+
* implementation.
|
|
154
|
+
*/
|
|
155
|
+
export interface BlobUploadPresignArgs {
|
|
156
|
+
readonly partition: string;
|
|
157
|
+
readonly blobId: string;
|
|
158
|
+
readonly byteLength: number;
|
|
159
|
+
readonly nowMs: number;
|
|
160
|
+
/** Resolved TTL (config value or the 900 s default) — already applied. */
|
|
161
|
+
readonly ttlSeconds: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface BlobUploadPresignConfig {
|
|
165
|
+
readonly presign: (
|
|
166
|
+
args: BlobUploadPresignArgs,
|
|
167
|
+
) => SegmentUrlIssue | Promise<SegmentUrlIssue>;
|
|
168
|
+
/** URL TTL; SHOULD be ≤ 15 minutes (§5.9.3). Default 900. */
|
|
169
|
+
readonly ttlSeconds?: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Issue the §5.9.3 `url`/`urlExpiresAtMs` pair for one presigned blob upload.
|
|
174
|
+
* The caller (blob-handlers `handleBlobUploadGrant`) invokes this only after
|
|
175
|
+
* host authentication and the size-cap check against the declared byteLength.
|
|
176
|
+
*/
|
|
177
|
+
export async function issueBlobUploadUrl(
|
|
178
|
+
config: BlobUploadPresignConfig,
|
|
179
|
+
args: {
|
|
180
|
+
readonly partition: string;
|
|
181
|
+
readonly blobId: string;
|
|
182
|
+
readonly byteLength: number;
|
|
183
|
+
readonly nowMs: number;
|
|
184
|
+
},
|
|
185
|
+
): Promise<SegmentUrlIssue> {
|
|
186
|
+
const ttlSeconds = config.ttlSeconds ?? 900;
|
|
187
|
+
return config.presign({ ...args, ttlSeconds });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface SegmentTokenClaims {
|
|
191
|
+
readonly v: 1;
|
|
192
|
+
readonly seg: string;
|
|
193
|
+
readonly sd: string;
|
|
194
|
+
readonly aud: string;
|
|
195
|
+
/** Unix seconds — the sole non-millisecond timestamp in the spec. */
|
|
196
|
+
readonly exp: number;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const encoder = new TextEncoder();
|
|
200
|
+
|
|
201
|
+
// Runtime-neutral base64url (no `Buffer` — Workers/Deno/browser safe; TODO
|
|
202
|
+
// §4.2): `btoa`/`atob` operate on binary strings, present in every runtime.
|
|
203
|
+
function base64url(bytes: Uint8Array): string {
|
|
204
|
+
let binary = '';
|
|
205
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
206
|
+
return btoa(binary)
|
|
207
|
+
.replaceAll('+', '-')
|
|
208
|
+
.replaceAll('/', '_')
|
|
209
|
+
.replace(/=+$/, '');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function fromBase64url(text: string): Uint8Array {
|
|
213
|
+
const binary = atob(text.replaceAll('-', '+').replaceAll('_', '/'));
|
|
214
|
+
const bytes = new Uint8Array(binary.length);
|
|
215
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
216
|
+
return bytes;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function hmac(
|
|
220
|
+
key: string | Uint8Array,
|
|
221
|
+
payload: Uint8Array,
|
|
222
|
+
): Promise<Uint8Array> {
|
|
223
|
+
const keyBytes = typeof key === 'string' ? encoder.encode(key) : key;
|
|
224
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
225
|
+
'raw',
|
|
226
|
+
keyBytes.slice().buffer as ArrayBuffer,
|
|
227
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
228
|
+
false,
|
|
229
|
+
['sign'],
|
|
230
|
+
);
|
|
231
|
+
const mac = await crypto.subtle.sign(
|
|
232
|
+
'HMAC',
|
|
233
|
+
cryptoKey,
|
|
234
|
+
payload.slice().buffer as ArrayBuffer,
|
|
235
|
+
);
|
|
236
|
+
return new Uint8Array(mac);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
240
|
+
if (a.length !== b.length) return false;
|
|
241
|
+
let diff = 0;
|
|
242
|
+
for (let i = 0; i < a.length; i++) diff |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
243
|
+
return diff === 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function signSegmentToken(
|
|
247
|
+
key: string | Uint8Array,
|
|
248
|
+
claims: SegmentTokenClaims,
|
|
249
|
+
): Promise<string> {
|
|
250
|
+
const payloadJson = JSON.stringify({
|
|
251
|
+
v: claims.v,
|
|
252
|
+
seg: claims.seg,
|
|
253
|
+
sd: claims.sd,
|
|
254
|
+
aud: claims.aud,
|
|
255
|
+
exp: claims.exp,
|
|
256
|
+
});
|
|
257
|
+
const payload = encoder.encode(payloadJson);
|
|
258
|
+
const mac = await hmac(key, payload);
|
|
259
|
+
return `${base64url(payload)}.${base64url(mac)}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const DEFAULT_SKEW_SECONDS = 60;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Verify a segment token per §5.4. Throws `SyncError sync.forbidden` on any
|
|
266
|
+
* failure (MAC, expiry, or claim mismatch).
|
|
267
|
+
*/
|
|
268
|
+
export async function verifySegmentToken(
|
|
269
|
+
key: string | Uint8Array,
|
|
270
|
+
token: string,
|
|
271
|
+
expected: {
|
|
272
|
+
readonly segmentId: string;
|
|
273
|
+
readonly scopeDigest: string;
|
|
274
|
+
readonly audience: string;
|
|
275
|
+
readonly nowMs: number;
|
|
276
|
+
readonly skewSeconds?: number;
|
|
277
|
+
},
|
|
278
|
+
): Promise<SegmentTokenClaims> {
|
|
279
|
+
const forbidden = (reason: string) =>
|
|
280
|
+
syncError('sync.forbidden', `segment token rejected: ${reason}`);
|
|
281
|
+
const dot = token.indexOf('.');
|
|
282
|
+
if (dot < 0) throw forbidden('malformed token');
|
|
283
|
+
const payload = fromBase64url(token.slice(0, dot));
|
|
284
|
+
const mac = fromBase64url(token.slice(dot + 1));
|
|
285
|
+
const computed = await hmac(key, payload);
|
|
286
|
+
if (!constantTimeEqual(mac, computed)) throw forbidden('bad MAC');
|
|
287
|
+
let claims: SegmentTokenClaims;
|
|
288
|
+
try {
|
|
289
|
+
claims = JSON.parse(
|
|
290
|
+
new TextDecoder().decode(payload),
|
|
291
|
+
) as SegmentTokenClaims;
|
|
292
|
+
} catch {
|
|
293
|
+
throw forbidden('unparseable claims');
|
|
294
|
+
}
|
|
295
|
+
if (claims.v !== 1) throw forbidden('unsupported token version');
|
|
296
|
+
const skew = expected.skewSeconds ?? DEFAULT_SKEW_SECONDS;
|
|
297
|
+
if (
|
|
298
|
+
typeof claims.exp !== 'number' ||
|
|
299
|
+
claims.exp < expected.nowMs / 1000 - skew
|
|
300
|
+
) {
|
|
301
|
+
throw forbidden('token expired');
|
|
302
|
+
}
|
|
303
|
+
if (claims.seg !== expected.segmentId) throw forbidden('segment mismatch');
|
|
304
|
+
if (claims.sd !== expected.scopeDigest)
|
|
305
|
+
throw forbidden('scope-digest mismatch');
|
|
306
|
+
if (claims.aud !== expected.audience) throw forbidden('audience mismatch');
|
|
307
|
+
return claims;
|
|
308
|
+
}
|
package/src/sigv4.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal AWS Signature Version 4 — exactly the subset the S3 segment
|
|
3
|
+
* store needs (header-signed requests and presigned GET URLs), hand-rolled
|
|
4
|
+
* per the dependency-light doctrine (REVISE): SigV4 is small and
|
|
5
|
+
* well-specified, so no SDK.
|
|
6
|
+
*
|
|
7
|
+
* Runtime-neutral (TODO §4.2): the crypto primitives are Web Crypto
|
|
8
|
+
* (`crypto.subtle`), not `node:crypto`, so this module runs unchanged on
|
|
9
|
+
* Cloudflare Workers / Deno / browsers as well as Bun/Node. `crypto.subtle`
|
|
10
|
+
* is async, so signing is async throughout; the `S3SegmentStore` consumers
|
|
11
|
+
* are already async, and the `DelegatedPresignConfig.presign` seam already
|
|
12
|
+
* accepts a `Promise<SegmentUrlIssue>`.
|
|
13
|
+
*
|
|
14
|
+
* Pinned by the published AWS SigV4 example vectors in
|
|
15
|
+
* `test/sigv4.test.ts`; the hermetic S3 stub re-derives every signature
|
|
16
|
+
* from the incoming request, so an asymmetric signing bug fails tests.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface SigV4Credentials {
|
|
20
|
+
readonly accessKeyId: string;
|
|
21
|
+
readonly secretAccessKey: string;
|
|
22
|
+
/** Optional STS session token (signed as `x-amz-security-token`). */
|
|
23
|
+
readonly sessionToken?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Hex SHA-256 of an empty body — the payload hash for GET/HEAD/DELETE. */
|
|
27
|
+
export const EMPTY_PAYLOAD_SHA256 =
|
|
28
|
+
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
|
|
29
|
+
|
|
30
|
+
/** The presigned-URL payload sentinel (S3 presigns never hash the body). */
|
|
31
|
+
export const UNSIGNED_PAYLOAD = 'UNSIGNED-PAYLOAD';
|
|
32
|
+
|
|
33
|
+
const textEncoder = new TextEncoder();
|
|
34
|
+
|
|
35
|
+
function toBytes(data: Uint8Array | string): Uint8Array {
|
|
36
|
+
return typeof data === 'string' ? textEncoder.encode(data) : data;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function toHex(bytes: Uint8Array): string {
|
|
40
|
+
let out = '';
|
|
41
|
+
for (const b of bytes) out += b.toString(16).padStart(2, '0');
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Web Crypto wants an `ArrayBuffer`; slice() gives an owned, exact-length one. */
|
|
46
|
+
function bufferOf(bytes: Uint8Array): ArrayBuffer {
|
|
47
|
+
return bytes.slice().buffer as ArrayBuffer;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function sha256Hex(data: Uint8Array | string): Promise<string> {
|
|
51
|
+
const digest = await crypto.subtle.digest('SHA-256', bufferOf(toBytes(data)));
|
|
52
|
+
return toHex(new Uint8Array(digest));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function hmacSha256(
|
|
56
|
+
key: Uint8Array | string,
|
|
57
|
+
data: string,
|
|
58
|
+
): Promise<Uint8Array> {
|
|
59
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
60
|
+
'raw',
|
|
61
|
+
bufferOf(toBytes(key)),
|
|
62
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
63
|
+
false,
|
|
64
|
+
['sign'],
|
|
65
|
+
);
|
|
66
|
+
const mac = await crypto.subtle.sign(
|
|
67
|
+
'HMAC',
|
|
68
|
+
cryptoKey,
|
|
69
|
+
bufferOf(toBytes(data)),
|
|
70
|
+
);
|
|
71
|
+
return new Uint8Array(mac);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* AWS canonical URI-encoding: RFC 3986 unreserved characters only —
|
|
76
|
+
* stricter than `encodeURIComponent` (which leaves `!'()*` alone).
|
|
77
|
+
*/
|
|
78
|
+
export function uriEncode(value: string, encodeSlash: boolean): string {
|
|
79
|
+
const encoded = encodeURIComponent(value).replace(
|
|
80
|
+
/[!'()*]/g,
|
|
81
|
+
(ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
82
|
+
);
|
|
83
|
+
return encodeSlash ? encoded : encoded.replaceAll('%2F', '/');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** `YYYYMMDD'T'HHMMSS'Z'` plus its date-only prefix, from epoch ms. */
|
|
87
|
+
export function amzTimestamps(nowMs: number): {
|
|
88
|
+
amzDate: string;
|
|
89
|
+
dateStamp: string;
|
|
90
|
+
} {
|
|
91
|
+
const iso = new Date(nowMs).toISOString();
|
|
92
|
+
const amzDate = `${iso.slice(0, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}T${iso.slice(11, 13)}${iso.slice(14, 16)}${iso.slice(17, 19)}Z`;
|
|
93
|
+
return { amzDate, dateStamp: amzDate.slice(0, 8) };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Sorted, fully re-encoded canonical query string from *decoded* pairs. */
|
|
97
|
+
export function canonicalQuery(
|
|
98
|
+
params: Iterable<readonly [string, string]>,
|
|
99
|
+
): string {
|
|
100
|
+
return [...params]
|
|
101
|
+
.map(([k, v]) => [uriEncode(k, true), uriEncode(v, true)] as const)
|
|
102
|
+
.sort(([ak, av], [bk, bv]) =>
|
|
103
|
+
ak < bk ? -1 : ak > bk ? 1 : av < bv ? -1 : av > bv ? 1 : 0,
|
|
104
|
+
)
|
|
105
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
106
|
+
.join('&');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface CanonicalRequestArgs {
|
|
110
|
+
readonly method: string;
|
|
111
|
+
/** The URI-encoded absolute path exactly as sent on the request line. */
|
|
112
|
+
readonly canonicalPath: string;
|
|
113
|
+
/** Decoded query pairs; `X-Amz-Signature` must already be excluded. */
|
|
114
|
+
readonly query: Iterable<readonly [string, string]>;
|
|
115
|
+
/** Only the headers being signed (name → raw value). */
|
|
116
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
117
|
+
readonly payloadHash: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function canonicalRequest(args: CanonicalRequestArgs): {
|
|
121
|
+
text: string;
|
|
122
|
+
signedHeaders: string;
|
|
123
|
+
} {
|
|
124
|
+
const entries = Object.entries(args.headers)
|
|
125
|
+
.map(
|
|
126
|
+
([name, value]) =>
|
|
127
|
+
[name.toLowerCase(), value.trim().replace(/\s+/g, ' ')] as const,
|
|
128
|
+
)
|
|
129
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
130
|
+
const signedHeaders = entries.map(([name]) => name).join(';');
|
|
131
|
+
const text = [
|
|
132
|
+
args.method.toUpperCase(),
|
|
133
|
+
args.canonicalPath,
|
|
134
|
+
canonicalQuery(args.query),
|
|
135
|
+
...entries.map(([name, value]) => `${name}:${value}`),
|
|
136
|
+
'',
|
|
137
|
+
signedHeaders,
|
|
138
|
+
args.payloadHash,
|
|
139
|
+
].join('\n');
|
|
140
|
+
return { text, signedHeaders };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function stringToSign(
|
|
144
|
+
amzDate: string,
|
|
145
|
+
scope: string,
|
|
146
|
+
canonicalRequestText: string,
|
|
147
|
+
): Promise<string> {
|
|
148
|
+
return `AWS4-HMAC-SHA256\n${amzDate}\n${scope}\n${await sha256Hex(canonicalRequestText)}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function sigV4Signature(args: {
|
|
152
|
+
readonly secretAccessKey: string;
|
|
153
|
+
readonly dateStamp: string;
|
|
154
|
+
readonly region: string;
|
|
155
|
+
readonly service: string;
|
|
156
|
+
readonly stringToSign: string;
|
|
157
|
+
}): Promise<string> {
|
|
158
|
+
const kDate = await hmacSha256(`AWS4${args.secretAccessKey}`, args.dateStamp);
|
|
159
|
+
const kRegion = await hmacSha256(kDate, args.region);
|
|
160
|
+
const kService = await hmacSha256(kRegion, args.service);
|
|
161
|
+
const kSigning = await hmacSha256(kService, 'aws4_request');
|
|
162
|
+
return toHex(await hmacSha256(kSigning, args.stringToSign));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface SignRequestArgs {
|
|
166
|
+
readonly method: string;
|
|
167
|
+
/** Full request URL (host is taken from here and always signed). */
|
|
168
|
+
readonly url: URL;
|
|
169
|
+
readonly region: string;
|
|
170
|
+
/** Defaults to `s3`. */
|
|
171
|
+
readonly service?: string;
|
|
172
|
+
readonly credentials: SigV4Credentials;
|
|
173
|
+
readonly nowMs: number;
|
|
174
|
+
/** Hex SHA-256 of the request body (`EMPTY_PAYLOAD_SHA256` if none). */
|
|
175
|
+
readonly payloadHash: string;
|
|
176
|
+
/** Extra headers to send *and* sign (e.g. content-type, x-amz-meta-*). */
|
|
177
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Headers for a header-authenticated request: the caller's headers plus
|
|
182
|
+
* `x-amz-date`, `x-amz-content-sha256`, optional `x-amz-security-token`,
|
|
183
|
+
* and `authorization`. `host` is signed but not returned — fetch derives
|
|
184
|
+
* it from the URL.
|
|
185
|
+
*/
|
|
186
|
+
export async function signRequest(
|
|
187
|
+
args: SignRequestArgs,
|
|
188
|
+
): Promise<Record<string, string>> {
|
|
189
|
+
const service = args.service ?? 's3';
|
|
190
|
+
const { amzDate, dateStamp } = amzTimestamps(args.nowMs);
|
|
191
|
+
const added: Record<string, string> = {
|
|
192
|
+
'x-amz-content-sha256': args.payloadHash,
|
|
193
|
+
'x-amz-date': amzDate,
|
|
194
|
+
...(args.credentials.sessionToken !== undefined
|
|
195
|
+
? { 'x-amz-security-token': args.credentials.sessionToken }
|
|
196
|
+
: {}),
|
|
197
|
+
};
|
|
198
|
+
const { text, signedHeaders } = canonicalRequest({
|
|
199
|
+
method: args.method,
|
|
200
|
+
canonicalPath: args.url.pathname,
|
|
201
|
+
query: args.url.searchParams,
|
|
202
|
+
headers: { ...args.headers, ...added, host: args.url.host },
|
|
203
|
+
payloadHash: args.payloadHash,
|
|
204
|
+
});
|
|
205
|
+
const scope = `${dateStamp}/${args.region}/${service}/aws4_request`;
|
|
206
|
+
const signature = await sigV4Signature({
|
|
207
|
+
secretAccessKey: args.credentials.secretAccessKey,
|
|
208
|
+
dateStamp,
|
|
209
|
+
region: args.region,
|
|
210
|
+
service,
|
|
211
|
+
stringToSign: await stringToSign(amzDate, scope, text),
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
...args.headers,
|
|
215
|
+
...added,
|
|
216
|
+
authorization: `AWS4-HMAC-SHA256 Credential=${args.credentials.accessKeyId}/${scope},SignedHeaders=${signedHeaders},Signature=${signature}`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface PresignArgs {
|
|
221
|
+
/** Defaults to `GET`. */
|
|
222
|
+
readonly method?: string;
|
|
223
|
+
/** Object URL without any `X-Amz-*` query parameters. */
|
|
224
|
+
readonly url: URL;
|
|
225
|
+
readonly region: string;
|
|
226
|
+
/** Defaults to `s3`. */
|
|
227
|
+
readonly service?: string;
|
|
228
|
+
readonly credentials: SigV4Credentials;
|
|
229
|
+
readonly nowMs: number;
|
|
230
|
+
readonly expiresSeconds: number;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Query-authenticated (presigned) URL; only the `host` header is signed. */
|
|
234
|
+
export async function presignUrl(args: PresignArgs): Promise<URL> {
|
|
235
|
+
const service = args.service ?? 's3';
|
|
236
|
+
const method = args.method ?? 'GET';
|
|
237
|
+
const { amzDate, dateStamp } = amzTimestamps(args.nowMs);
|
|
238
|
+
const scope = `${dateStamp}/${args.region}/${service}/aws4_request`;
|
|
239
|
+
const url = new URL(args.url);
|
|
240
|
+
url.searchParams.set('X-Amz-Algorithm', 'AWS4-HMAC-SHA256');
|
|
241
|
+
url.searchParams.set(
|
|
242
|
+
'X-Amz-Credential',
|
|
243
|
+
`${args.credentials.accessKeyId}/${scope}`,
|
|
244
|
+
);
|
|
245
|
+
url.searchParams.set('X-Amz-Date', amzDate);
|
|
246
|
+
url.searchParams.set('X-Amz-Expires', String(args.expiresSeconds));
|
|
247
|
+
url.searchParams.set('X-Amz-SignedHeaders', 'host');
|
|
248
|
+
if (args.credentials.sessionToken !== undefined) {
|
|
249
|
+
url.searchParams.set('X-Amz-Security-Token', args.credentials.sessionToken);
|
|
250
|
+
}
|
|
251
|
+
const { text } = canonicalRequest({
|
|
252
|
+
method,
|
|
253
|
+
canonicalPath: url.pathname,
|
|
254
|
+
query: url.searchParams,
|
|
255
|
+
headers: { host: url.host },
|
|
256
|
+
payloadHash: UNSIGNED_PAYLOAD,
|
|
257
|
+
});
|
|
258
|
+
const signature = await sigV4Signature({
|
|
259
|
+
secretAccessKey: args.credentials.secretAccessKey,
|
|
260
|
+
dateStamp,
|
|
261
|
+
region: args.region,
|
|
262
|
+
service,
|
|
263
|
+
stringToSign: await stringToSign(amzDate, scope, text),
|
|
264
|
+
});
|
|
265
|
+
url.searchParams.set('X-Amz-Signature', signature);
|
|
266
|
+
return url;
|
|
267
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed blob store via `bun:sqlite` (dev/bench convenience,
|
|
3
|
+
* dependency-free). Bun-specific by design (top-level `bun:sqlite` import),
|
|
4
|
+
* so it lives in its own module — the runtime-neutral `BlobStore` interface,
|
|
5
|
+
* `MemoryBlobStore`, `blobIdFor`, and `isBlobId` stay in `blob-store.ts` for
|
|
6
|
+
* the Workers/edge core (TODO §4.2 neutrality discipline; enforced by
|
|
7
|
+
* `test/runtime-neutrality.test.ts`).
|
|
8
|
+
*/
|
|
9
|
+
import { Database } from 'bun:sqlite';
|
|
10
|
+
import type { BlobRecord, BlobStore, BlobStoreStats } from './blob-store';
|
|
11
|
+
|
|
12
|
+
export class SqliteBlobStore implements BlobStore {
|
|
13
|
+
readonly db: Database;
|
|
14
|
+
|
|
15
|
+
constructor(db: Database | string = ':memory:') {
|
|
16
|
+
this.db = typeof db === 'string' ? new Database(db) : db;
|
|
17
|
+
this.db.exec(`
|
|
18
|
+
CREATE TABLE IF NOT EXISTS sync_blobs(
|
|
19
|
+
partition TEXT NOT NULL, blob_id TEXT NOT NULL,
|
|
20
|
+
byte_length INTEGER NOT NULL, media_type TEXT,
|
|
21
|
+
created_at_ms INTEGER NOT NULL, bytes BLOB NOT NULL,
|
|
22
|
+
PRIMARY KEY (partition, blob_id)
|
|
23
|
+
);
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async put(
|
|
28
|
+
partition: string,
|
|
29
|
+
blobId: string,
|
|
30
|
+
bytes: Uint8Array,
|
|
31
|
+
nowMs: number,
|
|
32
|
+
mediaType?: string,
|
|
33
|
+
): Promise<BlobRecord> {
|
|
34
|
+
const existing = await this.get(partition, blobId);
|
|
35
|
+
if (existing !== undefined) return existing.record;
|
|
36
|
+
const record: BlobRecord = {
|
|
37
|
+
blobId,
|
|
38
|
+
partition,
|
|
39
|
+
byteLength: bytes.length,
|
|
40
|
+
...(mediaType !== undefined ? { mediaType } : {}),
|
|
41
|
+
createdAtMs: nowMs,
|
|
42
|
+
};
|
|
43
|
+
this.db
|
|
44
|
+
.query(
|
|
45
|
+
`INSERT OR IGNORE INTO sync_blobs(
|
|
46
|
+
partition, blob_id, byte_length, media_type, created_at_ms, bytes
|
|
47
|
+
) VALUES (?,?,?,?,?,?)`,
|
|
48
|
+
)
|
|
49
|
+
.run(partition, blobId, bytes.length, mediaType ?? null, nowMs, bytes);
|
|
50
|
+
return record;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async has(partition: string, blobId: string): Promise<boolean> {
|
|
54
|
+
const row = this.db
|
|
55
|
+
.query<{ n: number }, [string, string]>(
|
|
56
|
+
'SELECT 1 AS n FROM sync_blobs WHERE partition=? AND blob_id=?',
|
|
57
|
+
)
|
|
58
|
+
.get(partition, blobId);
|
|
59
|
+
return row !== null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async get(
|
|
63
|
+
partition: string,
|
|
64
|
+
blobId: string,
|
|
65
|
+
): Promise<{ record: BlobRecord; bytes: Uint8Array } | undefined> {
|
|
66
|
+
const row = this.db
|
|
67
|
+
.query<
|
|
68
|
+
{
|
|
69
|
+
byte_length: number;
|
|
70
|
+
media_type: string | null;
|
|
71
|
+
created_at_ms: number;
|
|
72
|
+
bytes: Uint8Array;
|
|
73
|
+
},
|
|
74
|
+
[string, string]
|
|
75
|
+
>(
|
|
76
|
+
`SELECT byte_length, media_type, created_at_ms, bytes
|
|
77
|
+
FROM sync_blobs WHERE partition=? AND blob_id=?`,
|
|
78
|
+
)
|
|
79
|
+
.get(partition, blobId);
|
|
80
|
+
if (row === null) return undefined;
|
|
81
|
+
return {
|
|
82
|
+
record: {
|
|
83
|
+
blobId,
|
|
84
|
+
partition,
|
|
85
|
+
byteLength: row.byte_length,
|
|
86
|
+
...(row.media_type !== null ? { mediaType: row.media_type } : {}),
|
|
87
|
+
createdAtMs: row.created_at_ms,
|
|
88
|
+
},
|
|
89
|
+
bytes: new Uint8Array(row.bytes),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async sweepOrphans(
|
|
94
|
+
partition: string,
|
|
95
|
+
olderThanMs: number,
|
|
96
|
+
referencedBlobIds: ReadonlySet<string>,
|
|
97
|
+
): Promise<string[]> {
|
|
98
|
+
const candidates = this.db
|
|
99
|
+
.query<{ blob_id: string }, [string, number]>(
|
|
100
|
+
`SELECT blob_id FROM sync_blobs
|
|
101
|
+
WHERE partition=? AND created_at_ms < ?`,
|
|
102
|
+
)
|
|
103
|
+
.all(partition, olderThanMs);
|
|
104
|
+
const swept: string[] = [];
|
|
105
|
+
const del = this.db.query(
|
|
106
|
+
'DELETE FROM sync_blobs WHERE partition=? AND blob_id=?',
|
|
107
|
+
);
|
|
108
|
+
for (const { blob_id } of candidates) {
|
|
109
|
+
if (!referencedBlobIds.has(blob_id)) {
|
|
110
|
+
del.run(partition, blob_id);
|
|
111
|
+
swept.push(blob_id);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return swept;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async stats(partition: string): Promise<BlobStoreStats> {
|
|
118
|
+
const row = this.db
|
|
119
|
+
.query<{ count: number; bytes: number | null }, [string]>(
|
|
120
|
+
'SELECT count(*) AS count, sum(byte_length) AS bytes FROM sync_blobs WHERE partition=?',
|
|
121
|
+
)
|
|
122
|
+
.get(partition);
|
|
123
|
+
return { count: row?.count ?? 0, bytes: row?.bytes ?? 0 };
|
|
124
|
+
}
|
|
125
|
+
}
|