@syncular/server 0.0.1 → 0.0.2-127
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 +25 -0
- package/dist/blobs/adapters/database.d.ts.map +1 -1
- package/dist/blobs/adapters/database.js +25 -3
- package/dist/blobs/adapters/database.js.map +1 -1
- package/dist/blobs/adapters/filesystem.d.ts +31 -0
- package/dist/blobs/adapters/filesystem.d.ts.map +1 -0
- package/dist/blobs/adapters/filesystem.js +140 -0
- package/dist/blobs/adapters/filesystem.js.map +1 -0
- package/dist/blobs/adapters/s3.d.ts +3 -2
- package/dist/blobs/adapters/s3.d.ts.map +1 -1
- package/dist/blobs/adapters/s3.js +49 -0
- package/dist/blobs/adapters/s3.js.map +1 -1
- package/dist/blobs/index.d.ts +1 -0
- package/dist/blobs/index.d.ts.map +1 -1
- package/dist/blobs/index.js +6 -5
- package/dist/blobs/index.js.map +1 -1
- package/dist/clients.d.ts +1 -0
- package/dist/clients.d.ts.map +1 -1
- package/dist/clients.js.map +1 -1
- package/dist/compaction.d.ts +1 -1
- package/dist/compaction.js +1 -1
- package/dist/dialect/base.d.ts +83 -0
- package/dist/dialect/base.d.ts.map +1 -0
- package/dist/dialect/base.js +144 -0
- package/dist/dialect/base.js.map +1 -0
- package/dist/dialect/helpers.d.ts +10 -0
- package/dist/dialect/helpers.d.ts.map +1 -0
- package/dist/dialect/helpers.js +59 -0
- package/dist/dialect/helpers.js.map +1 -0
- package/dist/dialect/index.d.ts +2 -0
- package/dist/dialect/index.d.ts.map +1 -1
- package/dist/dialect/index.js +3 -1
- package/dist/dialect/index.js.map +1 -1
- package/dist/dialect/types.d.ts +38 -46
- package/dist/dialect/types.d.ts.map +1 -1
- package/dist/{shapes → handlers}/create-handler.d.ts +18 -5
- package/dist/handlers/create-handler.d.ts.map +1 -0
- package/dist/{shapes → handlers}/create-handler.js +140 -43
- package/dist/handlers/create-handler.js.map +1 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +4 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/registry.d.ts.map +1 -0
- package/dist/handlers/registry.js.map +1 -0
- package/dist/{shapes → handlers}/types.d.ts +7 -7
- package/dist/{shapes → handlers}/types.d.ts.map +1 -1
- package/dist/{shapes → handlers}/types.js.map +1 -1
- package/dist/helpers/conflict.d.ts +1 -1
- package/dist/helpers/conflict.d.ts.map +1 -1
- package/dist/helpers/emitted-change.d.ts +1 -1
- package/dist/helpers/emitted-change.d.ts.map +1 -1
- package/dist/helpers/index.js +4 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -16
- package/dist/index.js.map +1 -1
- package/dist/notify.d.ts +47 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +85 -0
- package/dist/notify.js.map +1 -0
- package/dist/proxy/handler.d.ts +1 -1
- package/dist/proxy/handler.d.ts.map +1 -1
- package/dist/proxy/handler.js +15 -11
- package/dist/proxy/handler.js.map +1 -1
- package/dist/proxy/index.d.ts +2 -2
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +3 -3
- package/dist/proxy/index.js.map +1 -1
- package/dist/proxy/mutation-detector.d.ts +4 -0
- package/dist/proxy/mutation-detector.d.ts.map +1 -1
- package/dist/proxy/mutation-detector.js +209 -24
- package/dist/proxy/mutation-detector.js.map +1 -1
- package/dist/proxy/oplog.d.ts +2 -1
- package/dist/proxy/oplog.d.ts.map +1 -1
- package/dist/proxy/oplog.js +15 -9
- package/dist/proxy/oplog.js.map +1 -1
- package/dist/proxy/registry.d.ts +0 -11
- package/dist/proxy/registry.d.ts.map +1 -1
- package/dist/proxy/registry.js +0 -24
- package/dist/proxy/registry.js.map +1 -1
- package/dist/proxy/types.d.ts +2 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/pull.d.ts +4 -3
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +565 -314
- package/dist/pull.js.map +1 -1
- package/dist/push.d.ts +15 -3
- package/dist/push.d.ts.map +1 -1
- package/dist/push.js +359 -229
- package/dist/push.js.map +1 -1
- package/dist/realtime/index.js +1 -1
- package/dist/realtime/types.d.ts +2 -0
- package/dist/realtime/types.d.ts.map +1 -1
- package/dist/schema.d.ts +11 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/snapshot-chunks/db-metadata.d.ts +6 -1
- package/dist/snapshot-chunks/db-metadata.d.ts.map +1 -1
- package/dist/snapshot-chunks/db-metadata.js +261 -92
- package/dist/snapshot-chunks/db-metadata.js.map +1 -1
- package/dist/snapshot-chunks/index.d.ts +0 -1
- package/dist/snapshot-chunks/index.d.ts.map +1 -1
- package/dist/snapshot-chunks/index.js +2 -3
- package/dist/snapshot-chunks/index.js.map +1 -1
- package/dist/snapshot-chunks/types.d.ts +20 -5
- package/dist/snapshot-chunks/types.d.ts.map +1 -1
- package/dist/snapshot-chunks.d.ts +12 -8
- package/dist/snapshot-chunks.d.ts.map +1 -1
- package/dist/snapshot-chunks.js +40 -12
- package/dist/snapshot-chunks.js.map +1 -1
- package/dist/subscriptions/index.js +1 -1
- package/dist/subscriptions/resolve.d.ts +6 -6
- package/dist/subscriptions/resolve.d.ts.map +1 -1
- package/dist/subscriptions/resolve.js +53 -14
- package/dist/subscriptions/resolve.js.map +1 -1
- package/package.json +28 -7
- package/src/blobs/adapters/database.test.ts +67 -0
- package/src/blobs/adapters/database.ts +34 -9
- package/src/blobs/adapters/filesystem.test.ts +132 -0
- package/src/blobs/adapters/filesystem.ts +189 -0
- package/src/blobs/adapters/s3.test.ts +522 -0
- package/src/blobs/adapters/s3.ts +55 -2
- package/src/blobs/index.ts +1 -0
- package/src/clients.ts +1 -0
- package/src/compaction.ts +1 -1
- package/src/dialect/base.ts +292 -0
- package/src/dialect/helpers.ts +61 -0
- package/src/dialect/index.ts +2 -0
- package/src/dialect/types.ts +50 -54
- package/src/{shapes → handlers}/create-handler.ts +219 -64
- package/src/{shapes → handlers}/types.ts +10 -7
- package/src/helpers/conflict.ts +1 -1
- package/src/helpers/emitted-change.ts +1 -1
- package/src/index.ts +2 -1
- package/src/notify.test.ts +516 -0
- package/src/notify.ts +131 -0
- package/src/proxy/handler.test.ts +120 -0
- package/src/proxy/handler.ts +18 -10
- package/src/proxy/index.ts +2 -1
- package/src/proxy/mutation-detector.test.ts +71 -0
- package/src/proxy/mutation-detector.ts +227 -29
- package/src/proxy/oplog.ts +19 -10
- package/src/proxy/registry.ts +0 -33
- package/src/proxy/types.ts +2 -0
- package/src/pull.ts +788 -405
- package/src/push.ts +507 -312
- package/src/realtime/types.ts +2 -0
- package/src/schema.ts +11 -1
- package/src/snapshot-chunks/db-metadata.test.ts +169 -0
- package/src/snapshot-chunks/db-metadata.ts +347 -105
- package/src/snapshot-chunks/index.ts +0 -1
- package/src/snapshot-chunks/types.ts +31 -5
- package/src/snapshot-chunks.ts +60 -21
- package/src/subscriptions/resolve.ts +73 -18
- package/dist/shapes/create-handler.d.ts.map +0 -1
- package/dist/shapes/create-handler.js.map +0 -1
- package/dist/shapes/index.d.ts.map +0 -1
- package/dist/shapes/index.js +0 -4
- package/dist/shapes/index.js.map +0 -1
- package/dist/shapes/registry.d.ts.map +0 -1
- package/dist/shapes/registry.js.map +0 -1
- package/dist/snapshot-chunks/adapters/s3.d.ts +0 -63
- package/dist/snapshot-chunks/adapters/s3.d.ts.map +0 -1
- package/dist/snapshot-chunks/adapters/s3.js +0 -50
- package/dist/snapshot-chunks/adapters/s3.js.map +0 -1
- package/src/snapshot-chunks/adapters/s3.ts +0 -68
- /package/dist/{shapes → handlers}/index.d.ts +0 -0
- /package/dist/{shapes → handlers}/registry.d.ts +0 -0
- /package/dist/{shapes → handlers}/registry.js +0 -0
- /package/dist/{shapes → handlers}/types.js +0 -0
- /package/src/{shapes → handlers}/index.ts +0 -0
- /package/src/{shapes → handlers}/registry.ts +0 -0
|
@@ -5,11 +5,20 @@
|
|
|
5
5
|
import type {
|
|
6
6
|
ScopePattern,
|
|
7
7
|
ScopeValues,
|
|
8
|
+
ScopeValuesFromPatterns,
|
|
8
9
|
ScopeDefinition as SimpleScopeDefinition,
|
|
9
10
|
StoredScopes,
|
|
10
11
|
SyncOperation,
|
|
11
12
|
} from '@syncular/core';
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
applyCodecsFromDbRow,
|
|
15
|
+
applyCodecsToDbRow,
|
|
16
|
+
type ColumnCodecDialect,
|
|
17
|
+
type ColumnCodecSource,
|
|
18
|
+
extractScopeVars,
|
|
19
|
+
normalizeScopes,
|
|
20
|
+
toTableColumnCodecs,
|
|
21
|
+
} from '@syncular/core';
|
|
13
22
|
import type {
|
|
14
23
|
DeleteQueryBuilder,
|
|
15
24
|
DeleteResult,
|
|
@@ -39,6 +48,24 @@ type AuthorizeResult =
|
|
|
39
48
|
| true
|
|
40
49
|
| { error: string; code: string; retriable?: boolean };
|
|
41
50
|
|
|
51
|
+
function classifyConstraintViolationCode(message: string): string {
|
|
52
|
+
const normalized = message.toLowerCase();
|
|
53
|
+
if (normalized.includes('not null')) return 'NOT_NULL_CONSTRAINT';
|
|
54
|
+
if (normalized.includes('unique')) return 'UNIQUE_CONSTRAINT';
|
|
55
|
+
if (normalized.includes('foreign key')) return 'FOREIGN_KEY_CONSTRAINT';
|
|
56
|
+
return 'CONSTRAINT_VIOLATION';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isConstraintViolationError(message: string): boolean {
|
|
60
|
+
const normalized = message.toLowerCase();
|
|
61
|
+
return (
|
|
62
|
+
normalized.includes('constraint') ||
|
|
63
|
+
normalized.includes('not null') ||
|
|
64
|
+
normalized.includes('foreign key') ||
|
|
65
|
+
normalized.includes('unique')
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
42
69
|
/**
|
|
43
70
|
* Scope definition for a column - maps scope variable to column name.
|
|
44
71
|
*/
|
|
@@ -51,6 +78,8 @@ export interface CreateServerHandlerOptions<
|
|
|
51
78
|
ServerDB extends SyncCoreDb,
|
|
52
79
|
ClientDB,
|
|
53
80
|
TableName extends keyof ServerDB & keyof ClientDB & string,
|
|
81
|
+
ScopeDefs extends
|
|
82
|
+
readonly SimpleScopeDefinition[] = readonly SimpleScopeDefinition[],
|
|
54
83
|
> {
|
|
55
84
|
/** Table name in the database */
|
|
56
85
|
table: TableName;
|
|
@@ -70,7 +99,7 @@ export interface CreateServerHandlerOptions<
|
|
|
70
99
|
* ]
|
|
71
100
|
* ```
|
|
72
101
|
*/
|
|
73
|
-
scopes:
|
|
102
|
+
scopes: ScopeDefs;
|
|
74
103
|
|
|
75
104
|
/** Primary key column name (default: 'id') */
|
|
76
105
|
primaryKey?: string;
|
|
@@ -94,7 +123,9 @@ export interface CreateServerHandlerOptions<
|
|
|
94
123
|
* project_id: await getProjectsForUser(ctx.db, ctx.actorId),
|
|
95
124
|
* })
|
|
96
125
|
*/
|
|
97
|
-
resolveScopes: (
|
|
126
|
+
resolveScopes: (
|
|
127
|
+
ctx: ServerContext<ServerDB>
|
|
128
|
+
) => Promise<ScopeValuesFromPatterns<ScopeDefs>>;
|
|
98
129
|
|
|
99
130
|
/**
|
|
100
131
|
* Transform inbound row from client to server format.
|
|
@@ -112,6 +143,20 @@ export interface CreateServerHandlerOptions<
|
|
|
112
143
|
row: Selectable<ServerDB[TableName]>
|
|
113
144
|
) => ClientDB[TableName];
|
|
114
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Optional column codec resolver.
|
|
148
|
+
* Receives `{ table, column, sqlType?, dialect? }` and returns a codec.
|
|
149
|
+
* Only used by default snapshot/apply paths when the corresponding
|
|
150
|
+
* transform hook is not provided.
|
|
151
|
+
*/
|
|
152
|
+
columnCodecs?: ColumnCodecSource;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Dialect used for codec dialect overrides.
|
|
156
|
+
* Default: 'sqlite'
|
|
157
|
+
*/
|
|
158
|
+
codecDialect?: ColumnCodecDialect;
|
|
159
|
+
|
|
115
160
|
/**
|
|
116
161
|
* Authorize an operation before applying.
|
|
117
162
|
* Return true to allow, or an error object to reject.
|
|
@@ -168,8 +213,10 @@ export function createServerHandler<
|
|
|
168
213
|
ServerDB extends SyncCoreDb,
|
|
169
214
|
ClientDB,
|
|
170
215
|
TableName extends keyof ServerDB & keyof ClientDB & string,
|
|
216
|
+
ScopeDefs extends
|
|
217
|
+
readonly SimpleScopeDefinition[] = readonly SimpleScopeDefinition[],
|
|
171
218
|
>(
|
|
172
|
-
options: CreateServerHandlerOptions<ServerDB, ClientDB, TableName>
|
|
219
|
+
options: CreateServerHandlerOptions<ServerDB, ClientDB, TableName, ScopeDefs>
|
|
173
220
|
): ServerTableHandler<ServerDB> {
|
|
174
221
|
type OverloadParameters<T> = T extends (...args: infer A) => unknown
|
|
175
222
|
? A
|
|
@@ -192,9 +239,25 @@ export function createServerHandler<
|
|
|
192
239
|
resolveScopes,
|
|
193
240
|
transformInbound,
|
|
194
241
|
transformOutbound,
|
|
242
|
+
columnCodecs,
|
|
243
|
+
codecDialect = 'sqlite',
|
|
195
244
|
authorize,
|
|
196
245
|
extractScopes: customExtractScopes,
|
|
197
246
|
} = options;
|
|
247
|
+
const codecCache = new Map<string, ReturnType<typeof toTableColumnCodecs>>();
|
|
248
|
+
const resolveTableCodecs = (row: Record<string, unknown>) => {
|
|
249
|
+
if (!columnCodecs) return {};
|
|
250
|
+
const columns = Object.keys(row);
|
|
251
|
+
if (columns.length === 0) return {};
|
|
252
|
+
const cacheKey = columns.slice().sort().join('\u0000');
|
|
253
|
+
const cached = codecCache.get(cacheKey);
|
|
254
|
+
if (cached) return cached;
|
|
255
|
+
const resolved = toTableColumnCodecs(table, columnCodecs, columns, {
|
|
256
|
+
dialect: codecDialect,
|
|
257
|
+
});
|
|
258
|
+
codecCache.set(cacheKey, resolved);
|
|
259
|
+
return resolved;
|
|
260
|
+
};
|
|
198
261
|
|
|
199
262
|
// Normalize scopes to pattern map and extract patterns/columns
|
|
200
263
|
const scopeColumnMap = normalizeScopes(scopeDefs);
|
|
@@ -234,6 +297,53 @@ export function createServerHandler<
|
|
|
234
297
|
|
|
235
298
|
const extractScopesImpl = customExtractScopes ?? defaultExtractScopes;
|
|
236
299
|
|
|
300
|
+
const resolveScopesImpl = async (
|
|
301
|
+
ctx: ServerContext<ServerDB>
|
|
302
|
+
): Promise<ScopeValues> => {
|
|
303
|
+
const resolved = await resolveScopes(ctx);
|
|
304
|
+
const normalized: ScopeValues = {};
|
|
305
|
+
for (const [scopeKey, scopeValue] of Object.entries(resolved)) {
|
|
306
|
+
if (typeof scopeValue === 'string' || Array.isArray(scopeValue)) {
|
|
307
|
+
normalized[scopeKey] = scopeValue;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return normalized;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const applyOutboundTransform = (
|
|
314
|
+
row: Selectable<ServerDB[TableName]>
|
|
315
|
+
): ClientDB[TableName] => {
|
|
316
|
+
if (transformOutbound) {
|
|
317
|
+
return transformOutbound(row);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const recordRow = row as Record<string, unknown>;
|
|
321
|
+
const transformed = applyCodecsFromDbRow(
|
|
322
|
+
recordRow,
|
|
323
|
+
resolveTableCodecs(recordRow),
|
|
324
|
+
codecDialect
|
|
325
|
+
);
|
|
326
|
+
return transformed as ClientDB[TableName];
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const applyInboundTransform = (
|
|
330
|
+
row: Record<string, unknown>,
|
|
331
|
+
schemaVersion: number | undefined
|
|
332
|
+
): Updateable<ServerDB[TableName]> => {
|
|
333
|
+
if (transformInbound) {
|
|
334
|
+
return transformInbound(row as ClientDB[TableName], {
|
|
335
|
+
schemaVersion,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const transformed = applyCodecsToDbRow(
|
|
340
|
+
row,
|
|
341
|
+
resolveTableCodecs(row),
|
|
342
|
+
codecDialect
|
|
343
|
+
);
|
|
344
|
+
return transformed as Updateable<ServerDB[TableName]>;
|
|
345
|
+
};
|
|
346
|
+
|
|
237
347
|
// Default snapshot implementation
|
|
238
348
|
const defaultSnapshot = async (
|
|
239
349
|
ctx: ServerSnapshotContext<ServerDB>,
|
|
@@ -290,11 +400,9 @@ export function createServerHandler<
|
|
|
290
400
|
: null;
|
|
291
401
|
|
|
292
402
|
// Transform outbound if provided
|
|
293
|
-
const outputRows =
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
)
|
|
297
|
-
: pageRows;
|
|
403
|
+
const outputRows = pageRows.map((r) =>
|
|
404
|
+
applyOutboundTransform(r as Selectable<ServerDB[TableName]>)
|
|
405
|
+
);
|
|
298
406
|
|
|
299
407
|
return {
|
|
300
408
|
rows: outputRows,
|
|
@@ -390,11 +498,11 @@ export function createServerHandler<
|
|
|
390
498
|
|
|
391
499
|
// Handle upsert
|
|
392
500
|
const rawPayload = op.payload ?? {};
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
501
|
+
const payloadRecord =
|
|
502
|
+
rawPayload !== null && typeof rawPayload === 'object'
|
|
503
|
+
? (rawPayload as Record<string, unknown>)
|
|
504
|
+
: {};
|
|
505
|
+
const payload = applyInboundTransform(payloadRecord, ctx.schemaVersion);
|
|
398
506
|
|
|
399
507
|
// Check for existing row
|
|
400
508
|
const existing = await (
|
|
@@ -423,9 +531,24 @@ export function createServerHandler<
|
|
|
423
531
|
status: 'conflict',
|
|
424
532
|
message: `Version conflict: server=${existingVersion}, base=${op.base_version}`,
|
|
425
533
|
server_version: existingVersion,
|
|
426
|
-
server_row:
|
|
427
|
-
|
|
428
|
-
|
|
534
|
+
server_row: applyOutboundTransform(
|
|
535
|
+
existing as Selectable<ServerDB[TableName]>
|
|
536
|
+
),
|
|
537
|
+
},
|
|
538
|
+
emittedChanges: [],
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// If the client provided a base version, they expected this row to exist.
|
|
543
|
+
// A missing row usually indicates stale local state after a server reset.
|
|
544
|
+
if (!existing && op.base_version != null) {
|
|
545
|
+
return {
|
|
546
|
+
result: {
|
|
547
|
+
opIndex,
|
|
548
|
+
status: 'error',
|
|
549
|
+
error: 'ROW_NOT_FOUND_FOR_BASE_VERSION',
|
|
550
|
+
code: 'ROW_MISSING',
|
|
551
|
+
retriable: false,
|
|
429
552
|
},
|
|
430
553
|
emittedChanges: [],
|
|
431
554
|
};
|
|
@@ -433,69 +556,101 @@ export function createServerHandler<
|
|
|
433
556
|
|
|
434
557
|
const nextVersion = existingVersion + 1;
|
|
435
558
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
559
|
+
let updated: Record<string, unknown> | undefined;
|
|
560
|
+
let constraintError: { message: string; code: string } | null = null;
|
|
561
|
+
|
|
562
|
+
try {
|
|
563
|
+
if (existing) {
|
|
564
|
+
// Update - merge payload with existing
|
|
565
|
+
const updateSet: Record<string, unknown> = {
|
|
566
|
+
...payload,
|
|
567
|
+
[versionColumn]: nextVersion,
|
|
568
|
+
};
|
|
569
|
+
// Don't update primary key or scope columns
|
|
570
|
+
delete updateSet[primaryKey];
|
|
571
|
+
for (const col of Object.values(scopeColumns)) {
|
|
572
|
+
delete updateSet[col];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
await (
|
|
576
|
+
trx.updateTable(table) as UpdateQueryBuilder<
|
|
577
|
+
ServerDB,
|
|
578
|
+
TableName,
|
|
579
|
+
TableName,
|
|
580
|
+
UpdateResult
|
|
581
|
+
>
|
|
582
|
+
)
|
|
583
|
+
.set(updateSet as UpdateSetObject)
|
|
584
|
+
.where(ref<string>(primaryKey), '=', op.row_id)
|
|
585
|
+
.execute();
|
|
586
|
+
} else {
|
|
587
|
+
// Insert
|
|
588
|
+
const insertValues: Record<string, unknown> = {
|
|
589
|
+
...payload,
|
|
590
|
+
[primaryKey]: op.row_id,
|
|
591
|
+
[versionColumn]: 1,
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
await (
|
|
595
|
+
trx.insertInto(table) as InsertQueryBuilder<
|
|
596
|
+
ServerDB,
|
|
597
|
+
TableName,
|
|
598
|
+
InsertResult
|
|
599
|
+
>
|
|
600
|
+
)
|
|
601
|
+
.values(insertValues as Insertable<ServerDB[TableName]>)
|
|
602
|
+
.execute();
|
|
446
603
|
}
|
|
447
604
|
|
|
448
|
-
|
|
449
|
-
|
|
605
|
+
// Read back the updated row
|
|
606
|
+
updated = await (
|
|
607
|
+
trx.selectFrom(table).selectAll() as SelectQueryBuilder<
|
|
450
608
|
ServerDB,
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
UpdateResult
|
|
609
|
+
keyof ServerDB & string,
|
|
610
|
+
Record<string, unknown>
|
|
454
611
|
>
|
|
455
612
|
)
|
|
456
|
-
.set(updateSet as UpdateSetObject)
|
|
457
613
|
.where(ref<string>(primaryKey), '=', op.row_id)
|
|
458
|
-
.
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
614
|
+
.executeTakeFirstOrThrow();
|
|
615
|
+
} catch (err) {
|
|
616
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
617
|
+
if (!isConstraintViolationError(message)) {
|
|
618
|
+
throw err;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
constraintError = {
|
|
622
|
+
message,
|
|
623
|
+
code: classifyConstraintViolationCode(message),
|
|
465
624
|
};
|
|
625
|
+
}
|
|
466
626
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
627
|
+
if (constraintError) {
|
|
628
|
+
return {
|
|
629
|
+
result: {
|
|
630
|
+
opIndex,
|
|
631
|
+
status: 'error',
|
|
632
|
+
error: constraintError.message,
|
|
633
|
+
code: constraintError.code,
|
|
634
|
+
retriable: false,
|
|
635
|
+
},
|
|
636
|
+
emittedChanges: [],
|
|
637
|
+
};
|
|
476
638
|
}
|
|
477
639
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
ServerDB,
|
|
482
|
-
keyof ServerDB & string,
|
|
483
|
-
Record<string, unknown>
|
|
484
|
-
>
|
|
485
|
-
)
|
|
486
|
-
.where(ref<string>(primaryKey), '=', op.row_id)
|
|
487
|
-
.executeTakeFirstOrThrow();
|
|
640
|
+
if (!updated) {
|
|
641
|
+
throw new Error('Updated row is missing after applyOperation');
|
|
642
|
+
}
|
|
488
643
|
|
|
489
|
-
const updatedRow = updated
|
|
644
|
+
const updatedRow = updated;
|
|
490
645
|
const rowVersion = (updatedRow[versionColumn] as number) ?? 1;
|
|
491
646
|
|
|
492
647
|
// Extract scopes from updated row
|
|
493
648
|
const scopes = extractScopesImpl(updatedRow);
|
|
494
649
|
|
|
495
650
|
// Transform outbound for emitted change
|
|
496
|
-
const rowJson =
|
|
497
|
-
|
|
498
|
-
|
|
651
|
+
const rowJson = applyOutboundTransform(
|
|
652
|
+
updated as Selectable<ServerDB[TableName]>
|
|
653
|
+
);
|
|
499
654
|
|
|
500
655
|
const emitted: EmittedChange = {
|
|
501
656
|
table,
|
|
@@ -517,7 +672,7 @@ export function createServerHandler<
|
|
|
517
672
|
scopePatterns,
|
|
518
673
|
dependsOn,
|
|
519
674
|
snapshotChunkTtlMs,
|
|
520
|
-
resolveScopes,
|
|
675
|
+
resolveScopes: resolveScopesImpl,
|
|
521
676
|
extractScopes: extractScopesImpl,
|
|
522
677
|
snapshot: options.snapshot ?? defaultSnapshot,
|
|
523
678
|
applyOperation: options.applyOperation ?? defaultApplyOperation,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ScopePattern,
|
|
3
3
|
ScopeValues,
|
|
4
|
+
ScopeValuesForKeys,
|
|
4
5
|
StoredScopes,
|
|
5
6
|
SyncOp,
|
|
6
7
|
SyncOperation,
|
|
@@ -50,12 +51,14 @@ export interface ServerContext<DB extends SyncCoreDb = SyncCoreDb> {
|
|
|
50
51
|
/**
|
|
51
52
|
* Context passed to snapshot method.
|
|
52
53
|
*/
|
|
53
|
-
export interface ServerSnapshotContext<
|
|
54
|
-
extends
|
|
54
|
+
export interface ServerSnapshotContext<
|
|
55
|
+
DB extends SyncCoreDb = SyncCoreDb,
|
|
56
|
+
ScopeKeys extends string = string,
|
|
57
|
+
> extends ServerContext<DB> {
|
|
55
58
|
/** Database executor for the snapshot */
|
|
56
59
|
db: DbExecutor<DB>;
|
|
57
60
|
/** Effective scope values for this subscription */
|
|
58
|
-
scopeValues:
|
|
61
|
+
scopeValues: ScopeValuesForKeys<ScopeKeys>;
|
|
59
62
|
/** Pagination cursor (row_id for keyset pagination) */
|
|
60
63
|
cursor: string | null;
|
|
61
64
|
/** Max rows to return */
|
|
@@ -120,9 +123,9 @@ interface ServerScopeConfig {
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
/**
|
|
123
|
-
* Server
|
|
126
|
+
* Server handler options - configuration for a table's sync behavior.
|
|
124
127
|
*/
|
|
125
|
-
export interface
|
|
128
|
+
export interface ServerHandlerOptions<
|
|
126
129
|
DB extends SyncCoreDb = SyncCoreDb,
|
|
127
130
|
Scopes extends Record<ScopePattern, Record<string, string>> = Record<
|
|
128
131
|
ScopePattern,
|
|
@@ -132,7 +135,7 @@ export interface ServerShapeOptions<
|
|
|
132
135
|
Params extends ZodSchema = ZodSchema,
|
|
133
136
|
> {
|
|
134
137
|
/**
|
|
135
|
-
* Scope patterns this
|
|
138
|
+
* Scope patterns this handler uses.
|
|
136
139
|
* Array of pattern keys from SharedScopes.
|
|
137
140
|
*/
|
|
138
141
|
scopes: (keyof Scopes)[];
|
|
@@ -225,7 +228,7 @@ export interface ServerTableHandler<DB extends SyncCoreDb = SyncCoreDb> {
|
|
|
225
228
|
/** Table name */
|
|
226
229
|
table: string;
|
|
227
230
|
|
|
228
|
-
/** Scope patterns used by this
|
|
231
|
+
/** Scope patterns used by this handler */
|
|
229
232
|
scopePatterns: ScopePattern[];
|
|
230
233
|
|
|
231
234
|
/**
|
package/src/helpers/conflict.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Helper for building conflict results in server table handlers.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ApplyOperationResult } from '../
|
|
7
|
+
import type { ApplyOperationResult } from '../handlers/types';
|
|
8
8
|
|
|
9
9
|
export interface BuildConflictResultArgs {
|
|
10
10
|
/** Index of the operation in the batch */
|
package/src/index.ts
CHANGED
|
@@ -13,15 +13,16 @@ export * from './blobs';
|
|
|
13
13
|
export * from './clients';
|
|
14
14
|
export * from './compaction';
|
|
15
15
|
export * from './dialect';
|
|
16
|
+
export * from './handlers';
|
|
16
17
|
export * from './helpers';
|
|
17
18
|
export * from './migrate';
|
|
19
|
+
export * from './notify';
|
|
18
20
|
export * from './proxy';
|
|
19
21
|
export * from './prune';
|
|
20
22
|
export * from './pull';
|
|
21
23
|
export * from './push';
|
|
22
24
|
export * from './realtime';
|
|
23
25
|
export * from './schema';
|
|
24
|
-
export * from './shapes';
|
|
25
26
|
export * from './snapshot-chunks';
|
|
26
27
|
export type { SnapshotChunkStorage } from './snapshot-chunks/types';
|
|
27
28
|
export * from './stats';
|