@syncular/server 0.0.1-60

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.
Files changed (211) hide show
  1. package/dist/blobs/adapters/database.d.ts +83 -0
  2. package/dist/blobs/adapters/database.d.ts.map +1 -0
  3. package/dist/blobs/adapters/database.js +180 -0
  4. package/dist/blobs/adapters/database.js.map +1 -0
  5. package/dist/blobs/adapters/s3.d.ts +82 -0
  6. package/dist/blobs/adapters/s3.d.ts.map +1 -0
  7. package/dist/blobs/adapters/s3.js +170 -0
  8. package/dist/blobs/adapters/s3.js.map +1 -0
  9. package/dist/blobs/index.d.ts +9 -0
  10. package/dist/blobs/index.d.ts.map +1 -0
  11. package/dist/blobs/index.js +9 -0
  12. package/dist/blobs/index.js.map +1 -0
  13. package/dist/blobs/manager.d.ts +195 -0
  14. package/dist/blobs/manager.d.ts.map +1 -0
  15. package/dist/blobs/manager.js +440 -0
  16. package/dist/blobs/manager.js.map +1 -0
  17. package/dist/blobs/migrate.d.ts +27 -0
  18. package/dist/blobs/migrate.d.ts.map +1 -0
  19. package/dist/blobs/migrate.js +119 -0
  20. package/dist/blobs/migrate.js.map +1 -0
  21. package/dist/blobs/types.d.ts +54 -0
  22. package/dist/blobs/types.d.ts.map +1 -0
  23. package/dist/blobs/types.js +5 -0
  24. package/dist/blobs/types.js.map +1 -0
  25. package/dist/clients.d.ts +14 -0
  26. package/dist/clients.d.ts.map +1 -0
  27. package/dist/clients.js +7 -0
  28. package/dist/clients.js.map +1 -0
  29. package/dist/compaction.d.ts +27 -0
  30. package/dist/compaction.d.ts.map +1 -0
  31. package/dist/compaction.js +49 -0
  32. package/dist/compaction.js.map +1 -0
  33. package/dist/dialect/index.d.ts +5 -0
  34. package/dist/dialect/index.d.ts.map +1 -0
  35. package/dist/dialect/index.js +5 -0
  36. package/dist/dialect/index.js.map +1 -0
  37. package/dist/dialect/types.d.ts +170 -0
  38. package/dist/dialect/types.d.ts.map +1 -0
  39. package/dist/dialect/types.js +8 -0
  40. package/dist/dialect/types.js.map +1 -0
  41. package/dist/helpers/conflict.d.ts +52 -0
  42. package/dist/helpers/conflict.d.ts.map +1 -0
  43. package/dist/helpers/conflict.js +49 -0
  44. package/dist/helpers/conflict.js.map +1 -0
  45. package/dist/helpers/emitted-change.d.ts +56 -0
  46. package/dist/helpers/emitted-change.d.ts.map +1 -0
  47. package/dist/helpers/emitted-change.js +46 -0
  48. package/dist/helpers/emitted-change.js.map +1 -0
  49. package/dist/helpers/index.d.ts +10 -0
  50. package/dist/helpers/index.d.ts.map +1 -0
  51. package/dist/helpers/index.js +10 -0
  52. package/dist/helpers/index.js.map +1 -0
  53. package/dist/helpers/paginate.d.ts +49 -0
  54. package/dist/helpers/paginate.d.ts.map +1 -0
  55. package/dist/helpers/paginate.js +54 -0
  56. package/dist/helpers/paginate.js.map +1 -0
  57. package/dist/helpers/scope-strings.d.ts +74 -0
  58. package/dist/helpers/scope-strings.d.ts.map +1 -0
  59. package/dist/helpers/scope-strings.js +82 -0
  60. package/dist/helpers/scope-strings.js.map +1 -0
  61. package/dist/index.d.ts +28 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +27 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/migrate.d.ts +14 -0
  66. package/dist/migrate.d.ts.map +1 -0
  67. package/dist/migrate.js +13 -0
  68. package/dist/migrate.js.map +1 -0
  69. package/dist/proxy/handler.d.ts +42 -0
  70. package/dist/proxy/handler.d.ts.map +1 -0
  71. package/dist/proxy/handler.js +99 -0
  72. package/dist/proxy/handler.js.map +1 -0
  73. package/dist/proxy/index.d.ts +9 -0
  74. package/dist/proxy/index.d.ts.map +1 -0
  75. package/dist/proxy/index.js +14 -0
  76. package/dist/proxy/index.js.map +1 -0
  77. package/dist/proxy/mutation-detector.d.ts +31 -0
  78. package/dist/proxy/mutation-detector.d.ts.map +1 -0
  79. package/dist/proxy/mutation-detector.js +61 -0
  80. package/dist/proxy/mutation-detector.js.map +1 -0
  81. package/dist/proxy/oplog.d.ts +30 -0
  82. package/dist/proxy/oplog.d.ts.map +1 -0
  83. package/dist/proxy/oplog.js +110 -0
  84. package/dist/proxy/oplog.js.map +1 -0
  85. package/dist/proxy/registry.d.ts +35 -0
  86. package/dist/proxy/registry.d.ts.map +1 -0
  87. package/dist/proxy/registry.js +49 -0
  88. package/dist/proxy/registry.js.map +1 -0
  89. package/dist/proxy/types.d.ts +44 -0
  90. package/dist/proxy/types.d.ts.map +1 -0
  91. package/dist/proxy/types.js +7 -0
  92. package/dist/proxy/types.js.map +1 -0
  93. package/dist/prune.d.ts +37 -0
  94. package/dist/prune.d.ts.map +1 -0
  95. package/dist/prune.js +112 -0
  96. package/dist/prune.js.map +1 -0
  97. package/dist/pull.d.ts +31 -0
  98. package/dist/pull.d.ts.map +1 -0
  99. package/dist/pull.js +414 -0
  100. package/dist/pull.js.map +1 -0
  101. package/dist/push.d.ts +33 -0
  102. package/dist/push.d.ts.map +1 -0
  103. package/dist/push.js +329 -0
  104. package/dist/push.js.map +1 -0
  105. package/dist/realtime/in-memory.d.ts +13 -0
  106. package/dist/realtime/in-memory.d.ts.map +1 -0
  107. package/dist/realtime/in-memory.js +28 -0
  108. package/dist/realtime/in-memory.js.map +1 -0
  109. package/dist/realtime/index.d.ts +3 -0
  110. package/dist/realtime/index.d.ts.map +1 -0
  111. package/dist/realtime/index.js +2 -0
  112. package/dist/realtime/index.js.map +1 -0
  113. package/dist/realtime/types.d.ts +50 -0
  114. package/dist/realtime/types.d.ts.map +1 -0
  115. package/dist/realtime/types.js +7 -0
  116. package/dist/realtime/types.js.map +1 -0
  117. package/dist/schema.d.ts +164 -0
  118. package/dist/schema.d.ts.map +1 -0
  119. package/dist/schema.js +10 -0
  120. package/dist/schema.js.map +1 -0
  121. package/dist/shapes/create-handler.d.ts +119 -0
  122. package/dist/shapes/create-handler.d.ts.map +1 -0
  123. package/dist/shapes/create-handler.js +327 -0
  124. package/dist/shapes/create-handler.js.map +1 -0
  125. package/dist/shapes/index.d.ts +4 -0
  126. package/dist/shapes/index.d.ts.map +1 -0
  127. package/dist/shapes/index.js +4 -0
  128. package/dist/shapes/index.js.map +1 -0
  129. package/dist/shapes/registry.d.ts +20 -0
  130. package/dist/shapes/registry.d.ts.map +1 -0
  131. package/dist/shapes/registry.js +88 -0
  132. package/dist/shapes/registry.js.map +1 -0
  133. package/dist/shapes/types.d.ts +204 -0
  134. package/dist/shapes/types.d.ts.map +1 -0
  135. package/dist/shapes/types.js +2 -0
  136. package/dist/shapes/types.js.map +1 -0
  137. package/dist/snapshot-chunks/adapters/s3.d.ts +63 -0
  138. package/dist/snapshot-chunks/adapters/s3.d.ts.map +1 -0
  139. package/dist/snapshot-chunks/adapters/s3.js +50 -0
  140. package/dist/snapshot-chunks/adapters/s3.js.map +1 -0
  141. package/dist/snapshot-chunks/db-metadata.d.ts +33 -0
  142. package/dist/snapshot-chunks/db-metadata.d.ts.map +1 -0
  143. package/dist/snapshot-chunks/db-metadata.js +169 -0
  144. package/dist/snapshot-chunks/db-metadata.js.map +1 -0
  145. package/dist/snapshot-chunks/index.d.ts +9 -0
  146. package/dist/snapshot-chunks/index.d.ts.map +1 -0
  147. package/dist/snapshot-chunks/index.js +9 -0
  148. package/dist/snapshot-chunks/index.js.map +1 -0
  149. package/dist/snapshot-chunks/types.d.ts +65 -0
  150. package/dist/snapshot-chunks/types.d.ts.map +1 -0
  151. package/dist/snapshot-chunks/types.js +8 -0
  152. package/dist/snapshot-chunks/types.js.map +1 -0
  153. package/dist/snapshot-chunks.d.ts +59 -0
  154. package/dist/snapshot-chunks.d.ts.map +1 -0
  155. package/dist/snapshot-chunks.js +202 -0
  156. package/dist/snapshot-chunks.js.map +1 -0
  157. package/dist/stats.d.ts +19 -0
  158. package/dist/stats.d.ts.map +1 -0
  159. package/dist/stats.js +57 -0
  160. package/dist/stats.js.map +1 -0
  161. package/dist/subscriptions/index.d.ts +2 -0
  162. package/dist/subscriptions/index.d.ts.map +1 -0
  163. package/dist/subscriptions/index.js +2 -0
  164. package/dist/subscriptions/index.js.map +1 -0
  165. package/dist/subscriptions/resolve.d.ts +35 -0
  166. package/dist/subscriptions/resolve.d.ts.map +1 -0
  167. package/dist/subscriptions/resolve.js +134 -0
  168. package/dist/subscriptions/resolve.js.map +1 -0
  169. package/package.json +80 -0
  170. package/src/blobs/adapters/database.ts +290 -0
  171. package/src/blobs/adapters/s3.ts +271 -0
  172. package/src/blobs/index.ts +9 -0
  173. package/src/blobs/manager.ts +600 -0
  174. package/src/blobs/migrate.ts +150 -0
  175. package/src/blobs/types.ts +70 -0
  176. package/src/clients.ts +21 -0
  177. package/src/compaction.ts +77 -0
  178. package/src/dialect/index.ts +5 -0
  179. package/src/dialect/types.ts +222 -0
  180. package/src/helpers/conflict.ts +64 -0
  181. package/src/helpers/emitted-change.ts +69 -0
  182. package/src/helpers/index.ts +10 -0
  183. package/src/helpers/paginate.ts +82 -0
  184. package/src/helpers/scope-strings.ts +101 -0
  185. package/src/index.ts +28 -0
  186. package/src/migrate.ts +20 -0
  187. package/src/proxy/handler.ts +152 -0
  188. package/src/proxy/index.ts +18 -0
  189. package/src/proxy/mutation-detector.ts +83 -0
  190. package/src/proxy/oplog.ts +144 -0
  191. package/src/proxy/registry.ts +56 -0
  192. package/src/proxy/types.ts +46 -0
  193. package/src/prune.ts +200 -0
  194. package/src/pull.ts +551 -0
  195. package/src/push.ts +457 -0
  196. package/src/realtime/in-memory.ts +33 -0
  197. package/src/realtime/index.ts +5 -0
  198. package/src/realtime/types.ts +55 -0
  199. package/src/schema.ts +172 -0
  200. package/src/shapes/create-handler.ts +590 -0
  201. package/src/shapes/index.ts +3 -0
  202. package/src/shapes/registry.ts +109 -0
  203. package/src/shapes/types.ts +267 -0
  204. package/src/snapshot-chunks/adapters/s3.ts +68 -0
  205. package/src/snapshot-chunks/db-metadata.ts +238 -0
  206. package/src/snapshot-chunks/index.ts +9 -0
  207. package/src/snapshot-chunks/types.ts +79 -0
  208. package/src/snapshot-chunks.ts +301 -0
  209. package/src/stats.ts +104 -0
  210. package/src/subscriptions/index.ts +1 -0
  211. package/src/subscriptions/resolve.ts +185 -0
@@ -0,0 +1,204 @@
1
+ import type { ScopePattern, ScopeValues, StoredScopes, SyncOp, SyncOperation, SyncOperationResult } from '@syncular/core';
2
+ import type { ZodSchema, z } from 'zod';
3
+ import type { DbExecutor } from '../dialect/types';
4
+ import type { SyncCoreDb } from '../schema';
5
+ /**
6
+ * Emitted change to be stored in the oplog.
7
+ * Uses JSONB scopes instead of scope_keys array.
8
+ */
9
+ export interface EmittedChange {
10
+ /** Table name */
11
+ table: string;
12
+ /** Row primary key */
13
+ row_id: string;
14
+ /** Operation type */
15
+ op: SyncOp;
16
+ /** Row data as JSON (null for deletes) */
17
+ row_json: unknown | null;
18
+ /** Row version for optimistic concurrency */
19
+ row_version: number | null;
20
+ /**
21
+ * Scope values for this change (stored as JSONB).
22
+ * Example: { user_id: 'U1', project_id: 'P1' }
23
+ */
24
+ scopes: StoredScopes;
25
+ }
26
+ export interface ApplyOperationResult {
27
+ result: SyncOperationResult;
28
+ emittedChanges: EmittedChange[];
29
+ }
30
+ /**
31
+ * Context for server operations.
32
+ */
33
+ export interface ServerContext<DB extends SyncCoreDb = SyncCoreDb> {
34
+ /** Database connection (transaction in applyOperation) */
35
+ db: DbExecutor<DB>;
36
+ /** Actor ID (user ID from auth) */
37
+ actorId: string;
38
+ }
39
+ /**
40
+ * Context passed to snapshot method.
41
+ */
42
+ export interface ServerSnapshotContext<DB extends SyncCoreDb = SyncCoreDb> extends ServerContext<DB> {
43
+ /** Database executor for the snapshot */
44
+ db: DbExecutor<DB>;
45
+ /** Effective scope values for this subscription */
46
+ scopeValues: ScopeValues;
47
+ /** Pagination cursor (row_id for keyset pagination) */
48
+ cursor: string | null;
49
+ /** Max rows to return */
50
+ limit: number;
51
+ }
52
+ /**
53
+ * Context passed to applyOperation method.
54
+ */
55
+ export interface ServerApplyOperationContext<DB extends SyncCoreDb = SyncCoreDb> extends ServerContext<DB> {
56
+ /** Database executor for the operation */
57
+ trx: DbExecutor<DB>;
58
+ /** Client/device identifier */
59
+ clientId: string;
60
+ /** Unique commit identifier */
61
+ commitId: string;
62
+ /**
63
+ * Client's schema version when the commit was created.
64
+ * Use this to transform payloads from older client versions.
65
+ */
66
+ schemaVersion: number;
67
+ }
68
+ /**
69
+ * Server-side scope configuration for advanced use cases.
70
+ * Use this when you need custom extraction, access control, or filtering.
71
+ *
72
+ * For simple cases, use the simplified scope array format:
73
+ * `scopes: ['user:{user_id}']`
74
+ */
75
+ interface ServerScopeConfig {
76
+ /**
77
+ * Column name containing the scope value.
78
+ * For simple patterns like 'user:{user_id}' → column: 'user_id'
79
+ */
80
+ column?: string;
81
+ /**
82
+ * Custom extractor for complex patterns.
83
+ * Example: extract year/month from a date column
84
+ */
85
+ extract?: (row: Record<string, unknown>) => Record<string, string>;
86
+ /**
87
+ * Optional access control per scope pattern.
88
+ * Return true if the actor can access this scope value.
89
+ */
90
+ access?: (ctx: ServerContext, vars: Record<string, string>) => Promise<boolean>;
91
+ /**
92
+ * Optional filter builder for wildcard subscriptions.
93
+ * Called when the subscription uses wildcards for this pattern.
94
+ */
95
+ toFilter?: (vars: Record<string, string | undefined>, query: unknown) => unknown;
96
+ }
97
+ /**
98
+ * Server shape options - configuration for a table's sync behavior.
99
+ */
100
+ export interface ServerShapeOptions<DB extends SyncCoreDb = SyncCoreDb, Scopes extends Record<ScopePattern, Record<string, string>> = Record<ScopePattern, Record<string, string>>, TableName extends string = string, Params extends ZodSchema = ZodSchema> {
101
+ /**
102
+ * Scope patterns this shape uses.
103
+ * Array of pattern keys from SharedScopes.
104
+ */
105
+ scopes: (keyof Scopes)[];
106
+ /**
107
+ * Scope definitions - how each pattern maps to row data.
108
+ * Defaults to using column name = variable name.
109
+ */
110
+ scopeDefinitions?: Partial<Record<keyof Scopes, ServerScopeConfig>>;
111
+ /**
112
+ * Resolve allowed scope values for the current actor.
113
+ * Called once per request to determine what the actor can access.
114
+ *
115
+ * Returns scope values the actor is allowed to access.
116
+ * The server will intersect requested scopes with these.
117
+ *
118
+ * @example
119
+ * resolveScopes: async (ctx) => ({
120
+ * user_id: [ctx.user.id],
121
+ * project_id: ctx.user.projectIds,
122
+ * })
123
+ */
124
+ resolveScopes: (ctx: ServerContext<DB>) => Promise<ScopeValues>;
125
+ /**
126
+ * Optional Zod schema for subscription parameters.
127
+ */
128
+ params?: Params;
129
+ /**
130
+ * Primary key column (default: 'id')
131
+ */
132
+ primaryKey?: string;
133
+ /**
134
+ * Version column for optimistic concurrency (default: 'server_version')
135
+ */
136
+ versionColumn?: string;
137
+ /**
138
+ * Tables that must be bootstrapped before this one.
139
+ */
140
+ dependsOn?: string[];
141
+ /**
142
+ * TTL for cached snapshot chunks (ms). Default: 24 hours.
143
+ */
144
+ snapshotChunkTtlMs?: number;
145
+ /**
146
+ * Transform client payload → server row on writes.
147
+ */
148
+ transformInbound?: (payload: Record<string, unknown>, ctx: ServerApplyOperationContext<DB>) => Partial<DB[TableName & keyof DB]>;
149
+ /**
150
+ * Transform server row → client payload on reads.
151
+ */
152
+ transformOutbound?: (row: DB[TableName & keyof DB]) => Record<string, unknown>;
153
+ /**
154
+ * Custom snapshot implementation.
155
+ * Default uses keyset pagination ordered by primary key.
156
+ */
157
+ snapshot?: (ctx: ServerSnapshotContext<DB>, params: Params extends ZodSchema ? z.infer<Params> : undefined) => Promise<{
158
+ rows: unknown[];
159
+ nextCursor: string | null;
160
+ }>;
161
+ /**
162
+ * Custom apply operation implementation.
163
+ */
164
+ applyOperation?: (ctx: ServerApplyOperationContext<DB>, op: SyncOperation, opIndex: number) => Promise<ApplyOperationResult>;
165
+ }
166
+ /**
167
+ * Server-side table handler for snapshots and mutations.
168
+ * This is the internal handler interface used by the sync engine.
169
+ */
170
+ export interface ServerTableHandler<DB extends SyncCoreDb = SyncCoreDb> {
171
+ /** Table name */
172
+ table: string;
173
+ /** Scope patterns used by this shape */
174
+ scopePatterns: ScopePattern[];
175
+ /**
176
+ * Tables that must be bootstrapped before this one.
177
+ */
178
+ dependsOn?: string[];
179
+ /**
180
+ * TTL for cached snapshot chunks (ms).
181
+ */
182
+ snapshotChunkTtlMs?: number;
183
+ /**
184
+ * Resolve allowed scope values for the current actor.
185
+ */
186
+ resolveScopes: (ctx: ServerContext<DB>) => Promise<ScopeValues>;
187
+ /**
188
+ * Extract stored scopes from a row.
189
+ */
190
+ extractScopes: (row: Record<string, unknown>) => StoredScopes;
191
+ /**
192
+ * Build a bootstrap snapshot page.
193
+ */
194
+ snapshot(ctx: ServerSnapshotContext<DB>, params: Record<string, unknown> | undefined): Promise<{
195
+ rows: unknown[];
196
+ nextCursor: string | null;
197
+ }>;
198
+ /**
199
+ * Apply a single operation.
200
+ */
201
+ applyOperation(ctx: ServerApplyOperationContext<DB>, op: SyncOperation, opIndex: number): Promise<ApplyOperationResult>;
202
+ }
203
+ export {};
204
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shapes/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/shapes/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @syncular/server - S3-compatible snapshot chunk storage adapter
3
+ *
4
+ * Stores snapshot chunk bodies in S3/R2/MinIO with metadata in database.
5
+ */
6
+ import type { BlobStorageAdapter } from '@syncular/core';
7
+ import type { Kysely } from 'kysely';
8
+ import type { SyncCoreDb } from '../../schema';
9
+ export interface S3SnapshotChunkStorageOptions {
10
+ /** Database instance for metadata */
11
+ db: Kysely<SyncCoreDb>;
12
+ /** S3 blob storage adapter */
13
+ s3Adapter: BlobStorageAdapter;
14
+ /** Optional key prefix for all chunks */
15
+ keyPrefix?: string;
16
+ }
17
+ /**
18
+ * Create S3-compatible snapshot chunk storage.
19
+ *
20
+ * Stores chunk bodies in S3/R2/MinIO and metadata in the database.
21
+ * Supports presigned URLs for direct client downloads.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { createS3BlobStorageAdapter } from '@syncular/server/blobs/adapters/s3';
26
+ * import { createS3SnapshotChunkStorage } from '@syncular/server/snapshot-chunks/adapters/s3';
27
+ *
28
+ * const s3Adapter = createS3BlobStorageAdapter({
29
+ * client: new S3Client({ region: 'us-east-1' }),
30
+ * bucket: 'my-snapshot-chunks',
31
+ * commands: { PutObjectCommand, GetObjectCommand, HeadObjectCommand, DeleteObjectCommand },
32
+ * getSignedUrl,
33
+ * });
34
+ *
35
+ * const chunkStorage = createS3SnapshotChunkStorage({
36
+ * db: kysely,
37
+ * s3Adapter,
38
+ * keyPrefix: 'snapshots/',
39
+ * });
40
+ * ```
41
+ */
42
+ export declare function createS3SnapshotChunkStorage(options: S3SnapshotChunkStorageOptions): {
43
+ name: string;
44
+ storeChunk: (metadata: Omit<import("..").SnapshotChunkMetadata, "blobHash" | "byteLength" | "chunkId"> & {
45
+ body: Uint8Array<ArrayBufferLike>;
46
+ }) => Promise<{
47
+ id: string;
48
+ byteLength: number;
49
+ sha256: string;
50
+ encoding: "ndjson";
51
+ compression: "gzip";
52
+ }>;
53
+ readChunk: (chunkId: string) => Promise<Uint8Array<ArrayBufferLike> | null>;
54
+ findChunk: (pageKey: import("..").SnapshotChunkPageKey) => Promise<{
55
+ id: string;
56
+ byteLength: number;
57
+ sha256: string;
58
+ encoding: "ndjson";
59
+ compression: "gzip";
60
+ } | null>;
61
+ cleanupExpired: (beforeIso: string) => Promise<number>;
62
+ };
63
+ //# sourceMappingURL=s3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../src/snapshot-chunks/adapters/s3.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/C,MAAM,WAAW,6BAA6B;IAC5C,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACvB,8BAA8B;IAC9B,SAAS,EAAE,kBAAkB,CAAC;IAC9B,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,6BAA6B;;;;;;;;;;;;;;;;;;;;EAqBvC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @syncular/server - S3-compatible snapshot chunk storage adapter
3
+ *
4
+ * Stores snapshot chunk bodies in S3/R2/MinIO with metadata in database.
5
+ */
6
+ import { createDbMetadataChunkStorage } from '../db-metadata';
7
+ /**
8
+ * Create S3-compatible snapshot chunk storage.
9
+ *
10
+ * Stores chunk bodies in S3/R2/MinIO and metadata in the database.
11
+ * Supports presigned URLs for direct client downloads.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { createS3BlobStorageAdapter } from '@syncular/server/blobs/adapters/s3';
16
+ * import { createS3SnapshotChunkStorage } from '@syncular/server/snapshot-chunks/adapters/s3';
17
+ *
18
+ * const s3Adapter = createS3BlobStorageAdapter({
19
+ * client: new S3Client({ region: 'us-east-1' }),
20
+ * bucket: 'my-snapshot-chunks',
21
+ * commands: { PutObjectCommand, GetObjectCommand, HeadObjectCommand, DeleteObjectCommand },
22
+ * getSignedUrl,
23
+ * });
24
+ *
25
+ * const chunkStorage = createS3SnapshotChunkStorage({
26
+ * db: kysely,
27
+ * s3Adapter,
28
+ * keyPrefix: 'snapshots/',
29
+ * });
30
+ * ```
31
+ */
32
+ export function createS3SnapshotChunkStorage(options) {
33
+ const { db, s3Adapter, keyPrefix } = options;
34
+ // Wrap the S3 adapter to use prefixed keys
35
+ const prefixedAdapter = keyPrefix
36
+ ? {
37
+ ...s3Adapter,
38
+ name: `${s3Adapter.name}+prefixed`,
39
+ // Keys are already handled by the S3 adapter, prefix is applied there
40
+ }
41
+ : s3Adapter;
42
+ // Use the database metadata storage with S3 for bodies
43
+ const storage = createDbMetadataChunkStorage({
44
+ db,
45
+ blobAdapter: prefixedAdapter,
46
+ chunkIdPrefix: keyPrefix ? `${keyPrefix.replace(/\/$/, '')}_` : 'chunk_',
47
+ });
48
+ return storage;
49
+ }
50
+ //# sourceMappingURL=s3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.js","sourceRoot":"","sources":["../../../src/snapshot-chunks/adapters/s3.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAW9D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,4BAA4B,CAC1C,OAAsC,EACtC;IACA,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAE7C,2CAA2C;IAC3C,MAAM,eAAe,GAAuB,SAAS;QACnD,CAAC,CAAC;YACE,GAAG,SAAS;YACZ,IAAI,EAAE,GAAG,SAAS,CAAC,IAAI,WAAW;YAClC,sEAAsE;SACvE;QACH,CAAC,CAAC,SAAS,CAAC;IAEd,uDAAuD;IACvD,MAAM,OAAO,GAAG,4BAA4B,CAAC;QAC3C,EAAE;QACF,WAAW,EAAE,eAAe;QAC5B,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ;KACzE,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AAAA,CAChB"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @syncular/server - Database-backed metadata store for snapshot chunks
3
+ *
4
+ * Stores chunk metadata in sync_snapshot_chunks_metadata table,
5
+ * body content in blob storage adapter.
6
+ */
7
+ import type { BlobStorageAdapter, SyncSnapshotChunkRef } from '@syncular/core';
8
+ import type { Kysely } from 'kysely';
9
+ import type { SyncCoreDb } from '../schema';
10
+ import type { SnapshotChunkMetadata, SnapshotChunkPageKey } from './types';
11
+ export interface DbMetadataSnapshotChunkStorageOptions {
12
+ /** Database instance */
13
+ db: Kysely<SyncCoreDb>;
14
+ /** Blob storage adapter for body content */
15
+ blobAdapter: BlobStorageAdapter;
16
+ /** Optional prefix for chunk IDs */
17
+ chunkIdPrefix?: string;
18
+ }
19
+ /**
20
+ * Create a snapshot chunk storage that uses:
21
+ * - Database for metadata (scope, commit seq, etc.)
22
+ * - Blob adapter for body content
23
+ */
24
+ export declare function createDbMetadataChunkStorage(options: DbMetadataSnapshotChunkStorageOptions): {
25
+ name: string;
26
+ storeChunk: (metadata: Omit<SnapshotChunkMetadata, 'chunkId' | 'byteLength' | 'blobHash'> & {
27
+ body: Uint8Array;
28
+ }) => Promise<SyncSnapshotChunkRef>;
29
+ readChunk: (chunkId: string) => Promise<Uint8Array | null>;
30
+ findChunk: (pageKey: SnapshotChunkPageKey) => Promise<SyncSnapshotChunkRef | null>;
31
+ cleanupExpired: (beforeIso: string) => Promise<number>;
32
+ };
33
+ //# sourceMappingURL=db-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-metadata.d.ts","sourceRoot":"","sources":["../../src/snapshot-chunks/db-metadata.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE3E,MAAM,WAAW,qCAAqC;IACpD,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACvB,4CAA4C;IAC5C,WAAW,EAAE,kBAAkB,CAAC;IAChC,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,qCAAqC,GAC7C;IACD,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,CACV,QAAQ,EAAE,IAAI,CACZ,qBAAqB,EACrB,SAAS,GAAG,YAAY,GAAG,UAAU,CACtC,GAAG;QACF,IAAI,EAAE,UAAU,CAAC;KAClB,KACE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACnC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC3D,SAAS,EAAE,CACT,OAAO,EAAE,oBAAoB,KAC1B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAC1C,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACxD,CAiMA"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * @syncular/server - Database-backed metadata store for snapshot chunks
3
+ *
4
+ * Stores chunk metadata in sync_snapshot_chunks_metadata table,
5
+ * body content in blob storage adapter.
6
+ */
7
+ import { createHash } from 'node:crypto';
8
+ /**
9
+ * Create a snapshot chunk storage that uses:
10
+ * - Database for metadata (scope, commit seq, etc.)
11
+ * - Blob adapter for body content
12
+ */
13
+ export function createDbMetadataChunkStorage(options) {
14
+ const { db, blobAdapter, chunkIdPrefix = 'chunk_' } = options;
15
+ // Generate deterministic blob hash from content
16
+ function computeBlobHash(body) {
17
+ return `sha256:${createHash('sha256').update(body).digest('hex')}`;
18
+ }
19
+ // Generate unique chunk ID
20
+ function generateChunkId() {
21
+ return `${chunkIdPrefix}${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
22
+ }
23
+ return {
24
+ name: `db-metadata+${blobAdapter.name}`,
25
+ async storeChunk(metadata) {
26
+ const { body, ...metaWithoutBody } = metadata;
27
+ const blobHash = computeBlobHash(body);
28
+ const chunkId = generateChunkId();
29
+ const now = new Date().toISOString();
30
+ // Check if blob already exists (content-addressed dedup)
31
+ const blobExists = await blobAdapter.exists(blobHash);
32
+ if (!blobExists) {
33
+ // Store body in blob adapter
34
+ if (blobAdapter.put) {
35
+ await blobAdapter.put(blobHash, body);
36
+ }
37
+ else {
38
+ throw new Error(`Blob adapter ${blobAdapter.name} does not support direct put() for snapshot chunks`);
39
+ }
40
+ }
41
+ // Upsert metadata in database
42
+ await db
43
+ .insertInto('sync_snapshot_chunks')
44
+ .values({
45
+ chunk_id: chunkId,
46
+ partition_id: metaWithoutBody.partitionId,
47
+ scope_key: metaWithoutBody.scopeKey,
48
+ scope: metaWithoutBody.scope,
49
+ as_of_commit_seq: metaWithoutBody.asOfCommitSeq,
50
+ row_cursor: metaWithoutBody.rowCursor ?? '',
51
+ row_limit: metaWithoutBody.rowLimit,
52
+ encoding: metaWithoutBody.encoding,
53
+ compression: metaWithoutBody.compression,
54
+ sha256: metaWithoutBody.sha256,
55
+ byte_length: body.length,
56
+ blob_hash: blobHash,
57
+ expires_at: metaWithoutBody.expiresAt,
58
+ created_at: now,
59
+ })
60
+ .onConflict((oc) => oc
61
+ .columns([
62
+ 'partition_id',
63
+ 'scope_key',
64
+ 'scope',
65
+ 'as_of_commit_seq',
66
+ 'row_cursor',
67
+ 'row_limit',
68
+ 'encoding',
69
+ 'compression',
70
+ ])
71
+ .doUpdateSet({
72
+ expires_at: metaWithoutBody.expiresAt,
73
+ blob_hash: blobHash,
74
+ sha256: metaWithoutBody.sha256,
75
+ byte_length: body.length,
76
+ row_cursor: metaWithoutBody.rowCursor ?? '',
77
+ }))
78
+ .execute();
79
+ return {
80
+ id: chunkId,
81
+ sha256: metaWithoutBody.sha256,
82
+ byteLength: body.length,
83
+ encoding: metaWithoutBody.encoding,
84
+ compression: metaWithoutBody.compression,
85
+ };
86
+ },
87
+ async readChunk(chunkId) {
88
+ // Get metadata to find blob hash
89
+ const row = await db
90
+ .selectFrom('sync_snapshot_chunks')
91
+ .select(['blob_hash'])
92
+ .where('chunk_id', '=', chunkId)
93
+ .executeTakeFirst();
94
+ if (!row)
95
+ return null;
96
+ // Read from blob adapter
97
+ if (blobAdapter.get) {
98
+ return blobAdapter.get(row.blob_hash);
99
+ }
100
+ throw new Error(`Blob adapter ${blobAdapter.name} does not support direct get() for snapshot chunks`);
101
+ },
102
+ async findChunk(pageKey) {
103
+ const nowIso = new Date().toISOString();
104
+ const rowCursorKey = pageKey.rowCursor ?? '';
105
+ const row = await db
106
+ .selectFrom('sync_snapshot_chunks')
107
+ .select([
108
+ 'chunk_id',
109
+ 'sha256',
110
+ 'byte_length',
111
+ 'encoding',
112
+ 'compression',
113
+ ])
114
+ .where('partition_id', '=', pageKey.partitionId)
115
+ .where('scope_key', '=', pageKey.scopeKey)
116
+ .where('scope', '=', pageKey.scope)
117
+ .where('as_of_commit_seq', '=', pageKey.asOfCommitSeq)
118
+ .where('row_cursor', '=', rowCursorKey)
119
+ .where('row_limit', '=', pageKey.rowLimit)
120
+ .where('encoding', '=', pageKey.encoding)
121
+ .where('compression', '=', pageKey.compression)
122
+ .where('expires_at', '>', nowIso)
123
+ .executeTakeFirst();
124
+ if (!row)
125
+ return null;
126
+ if (row.encoding !== 'ndjson') {
127
+ throw new Error(`Unexpected snapshot chunk encoding: ${String(row.encoding)}`);
128
+ }
129
+ if (row.compression !== 'gzip') {
130
+ throw new Error(`Unexpected snapshot chunk compression: ${String(row.compression)}`);
131
+ }
132
+ return {
133
+ id: row.chunk_id,
134
+ sha256: row.sha256,
135
+ byteLength: Number(row.byte_length ?? 0),
136
+ encoding: row.encoding,
137
+ compression: row.compression,
138
+ };
139
+ },
140
+ async cleanupExpired(beforeIso) {
141
+ // Find expired chunks
142
+ const expiredRows = await db
143
+ .selectFrom('sync_snapshot_chunks')
144
+ .select(['chunk_id', 'blob_hash'])
145
+ .where('expires_at', '<=', beforeIso)
146
+ .execute();
147
+ if (expiredRows.length === 0)
148
+ return 0;
149
+ // Delete from blob storage (best effort)
150
+ for (const row of expiredRows) {
151
+ try {
152
+ await blobAdapter.delete(row.blob_hash);
153
+ }
154
+ catch {
155
+ // Ignore deletion errors - blob may be shared or already deleted
156
+ // Log for observability but don't fail the cleanup
157
+ console.warn(`Failed to delete blob ${row.blob_hash} for chunk ${row.chunk_id}, may be already deleted or shared`);
158
+ }
159
+ }
160
+ // Delete metadata from database
161
+ const result = await db
162
+ .deleteFrom('sync_snapshot_chunks')
163
+ .where('expires_at', '<=', beforeIso)
164
+ .executeTakeFirst();
165
+ return Number(result.numDeletedRows ?? 0);
166
+ },
167
+ };
168
+ }
169
+ //# sourceMappingURL=db-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-metadata.js","sourceRoot":"","sources":["../../src/snapshot-chunks/db-metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @syncular/server - Snapshot chunk storage
3
+ *
4
+ * Separates chunk metadata (database) from body content (blob storage).
5
+ */
6
+ export * from './adapters/s3';
7
+ export * from './db-metadata';
8
+ export * from './types';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/snapshot-chunks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @syncular/server - Snapshot chunk storage
3
+ *
4
+ * Separates chunk metadata (database) from body content (blob storage).
5
+ */
6
+ export * from './adapters/s3';
7
+ export * from './db-metadata';
8
+ export * from './types';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/snapshot-chunks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @syncular/server - Snapshot chunk storage types
3
+ *
4
+ * Separates chunk metadata (in database) from chunk body (in blob storage).
5
+ * Enables flexible storage backends (database, S3, R2, etc.)
6
+ */
7
+ import type { SyncSnapshotChunkRef } from '@syncular/core';
8
+ /**
9
+ * Page key for identifying a specific chunk
10
+ */
11
+ export interface SnapshotChunkPageKey {
12
+ partitionId: string;
13
+ scopeKey: string;
14
+ scope: string;
15
+ asOfCommitSeq: number;
16
+ rowCursor: string | null;
17
+ rowLimit: number;
18
+ encoding: 'ndjson';
19
+ compression: 'gzip';
20
+ }
21
+ /**
22
+ * Metadata stored in the database for each chunk
23
+ */
24
+ export interface SnapshotChunkMetadata {
25
+ chunkId: string;
26
+ partitionId: string;
27
+ scopeKey: string;
28
+ scope: string;
29
+ asOfCommitSeq: number;
30
+ rowCursor: string | null;
31
+ rowLimit: number;
32
+ encoding: 'ndjson';
33
+ compression: 'gzip';
34
+ sha256: string;
35
+ byteLength: number;
36
+ blobHash: string;
37
+ expiresAt: string;
38
+ }
39
+ /**
40
+ * Storage interface for snapshot chunks
41
+ */
42
+ export interface SnapshotChunkStorage {
43
+ /** Storage adapter name */
44
+ readonly name: string;
45
+ /**
46
+ * Store a chunk. Returns chunk reference.
47
+ * If chunk with same content already exists (by hash), returns existing reference.
48
+ */
49
+ storeChunk(metadata: Omit<SnapshotChunkMetadata, 'chunkId' | 'byteLength' | 'blobHash'> & {
50
+ body: Uint8Array;
51
+ }): Promise<SyncSnapshotChunkRef>;
52
+ /**
53
+ * Read chunk body by chunk ID
54
+ */
55
+ readChunk(chunkId: string): Promise<Uint8Array | null>;
56
+ /**
57
+ * Find existing chunk by page key
58
+ */
59
+ findChunk(pageKey: SnapshotChunkPageKey): Promise<SyncSnapshotChunkRef | null>;
60
+ /**
61
+ * Delete expired chunks. Returns number deleted.
62
+ */
63
+ cleanupExpired(beforeIso: string): Promise<number>;
64
+ }
65
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/snapshot-chunks/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,UAAU,CACR,QAAQ,EAAE,IAAI,CACZ,qBAAqB,EACrB,SAAS,GAAG,YAAY,GAAG,UAAU,CACtC,GAAG;QACF,IAAI,EAAE,UAAU,CAAC;KAClB,GACA,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAEvD;;OAEG;IACH,SAAS,CACP,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAExC;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACpD"}