@syncular/core 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 (72) hide show
  1. package/dist/blobs.d.ts +137 -0
  2. package/dist/blobs.d.ts.map +1 -0
  3. package/dist/blobs.js +47 -0
  4. package/dist/blobs.js.map +1 -0
  5. package/dist/conflict.d.ts +22 -0
  6. package/dist/conflict.d.ts.map +1 -0
  7. package/dist/conflict.js +81 -0
  8. package/dist/conflict.js.map +1 -0
  9. package/dist/index.d.ts +21 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +30 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/kysely-serialize.d.ts +22 -0
  14. package/dist/kysely-serialize.d.ts.map +1 -0
  15. package/dist/kysely-serialize.js +147 -0
  16. package/dist/kysely-serialize.js.map +1 -0
  17. package/dist/logger.d.ts +46 -0
  18. package/dist/logger.d.ts.map +1 -0
  19. package/dist/logger.js +48 -0
  20. package/dist/logger.js.map +1 -0
  21. package/dist/proxy/index.d.ts +5 -0
  22. package/dist/proxy/index.d.ts.map +1 -0
  23. package/dist/proxy/index.js +5 -0
  24. package/dist/proxy/index.js.map +1 -0
  25. package/dist/proxy/types.d.ts +54 -0
  26. package/dist/proxy/types.d.ts.map +1 -0
  27. package/dist/proxy/types.js +7 -0
  28. package/dist/proxy/types.js.map +1 -0
  29. package/dist/schemas/blobs.d.ts +76 -0
  30. package/dist/schemas/blobs.d.ts.map +1 -0
  31. package/dist/schemas/blobs.js +63 -0
  32. package/dist/schemas/blobs.js.map +1 -0
  33. package/dist/schemas/common.d.ts +28 -0
  34. package/dist/schemas/common.d.ts.map +1 -0
  35. package/dist/schemas/common.js +26 -0
  36. package/dist/schemas/common.js.map +1 -0
  37. package/dist/schemas/index.d.ts +7 -0
  38. package/dist/schemas/index.d.ts.map +1 -0
  39. package/dist/schemas/index.js +7 -0
  40. package/dist/schemas/index.js.map +1 -0
  41. package/dist/schemas/sync.d.ts +391 -0
  42. package/dist/schemas/sync.d.ts.map +1 -0
  43. package/dist/schemas/sync.js +156 -0
  44. package/dist/schemas/sync.js.map +1 -0
  45. package/dist/scopes/index.d.ts +65 -0
  46. package/dist/scopes/index.d.ts.map +1 -0
  47. package/dist/scopes/index.js +67 -0
  48. package/dist/scopes/index.js.map +1 -0
  49. package/dist/transforms.d.ts +146 -0
  50. package/dist/transforms.d.ts.map +1 -0
  51. package/dist/transforms.js +155 -0
  52. package/dist/transforms.js.map +1 -0
  53. package/dist/types.d.ts +129 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +20 -0
  56. package/dist/types.js.map +1 -0
  57. package/package.json +56 -0
  58. package/src/__tests__/conflict.test.ts +325 -0
  59. package/src/blobs.ts +187 -0
  60. package/src/conflict.ts +92 -0
  61. package/src/index.ts +30 -0
  62. package/src/kysely-serialize.ts +214 -0
  63. package/src/logger.ts +80 -0
  64. package/src/proxy/index.ts +10 -0
  65. package/src/proxy/types.ts +57 -0
  66. package/src/schemas/blobs.ts +101 -0
  67. package/src/schemas/common.ts +45 -0
  68. package/src/schemas/index.ts +7 -0
  69. package/src/schemas/sync.ts +222 -0
  70. package/src/scopes/index.ts +122 -0
  71. package/src/transforms.ts +256 -0
  72. package/src/types.ts +158 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @syncular/core - Scope types, patterns, and utilities
3
+ *
4
+ * Scope patterns define how data is partitioned for sync.
5
+ * Scopes are stored as JSONB on changes for flexible filtering.
6
+ * Patterns use `{placeholder}` syntax to extract or inject values.
7
+ */
8
+ // ── Pattern parsing (internal helpers) ───────────────────────────────
9
+ /**
10
+ * Extract the placeholder name from a pattern.
11
+ * Returns null if the pattern doesn't contain a valid placeholder.
12
+ */
13
+ function extractPlaceholder(pattern) {
14
+ const match = pattern.match(/^(.*?)\{(\w+)\}(.*)$/);
15
+ if (!match)
16
+ return null;
17
+ return {
18
+ prefix: match[1],
19
+ placeholder: match[2],
20
+ suffix: match[3],
21
+ };
22
+ }
23
+ /**
24
+ * Extract the placeholder name from a pattern.
25
+ */
26
+ function getPlaceholderName(pattern) {
27
+ const parsed = extractPlaceholder(pattern);
28
+ return parsed?.placeholder ?? null;
29
+ }
30
+ /**
31
+ * Normalize scope definitions to a pattern-to-column map.
32
+ *
33
+ * @example
34
+ * normalizeScopes(['user:{user_id}'])
35
+ * // → { 'user:{user_id}': 'user_id' }
36
+ */
37
+ export function normalizeScopes(scopes) {
38
+ const result = {};
39
+ for (const scope of scopes) {
40
+ if (typeof scope === 'string') {
41
+ const placeholder = getPlaceholderName(scope);
42
+ if (!placeholder) {
43
+ throw new Error(`Scope pattern "${scope}" must contain a placeholder like {column_name}`);
44
+ }
45
+ result[scope] = placeholder;
46
+ }
47
+ else {
48
+ result[scope.pattern] = scope.column;
49
+ }
50
+ }
51
+ return result;
52
+ }
53
+ // ── Value operations (public) ────────────────────────────────────────
54
+ /**
55
+ * Extract variable names from a scope pattern.
56
+ *
57
+ * @example
58
+ * extractScopeVars('project:{project_id}') // ['project_id']
59
+ * extractScopeVars('event_date:{year}:{month}') // ['year', 'month']
60
+ */
61
+ export function extractScopeVars(pattern) {
62
+ const matches = pattern.match(/\{([^}]+)\}/g);
63
+ if (!matches)
64
+ return [];
65
+ return matches.map((m) => m.slice(1, -1));
66
+ }
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scopes/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA8CH,0IAAwE;AAExE;;;GAGG;AACH,SAAS,kBAAkB,CAAC,OAAe,EAIlC;IACP,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,CAAC,CAAE;QACjB,WAAW,EAAE,KAAK,CAAC,CAAC,CAAE;QACtB,MAAM,EAAE,KAAK,CAAC,CAAC,CAAE;KAClB,CAAC;AAAA,CACH;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,OAAe,EAAiB;IAC1D,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC3C,OAAO,MAAM,EAAE,WAAW,IAAI,IAAI,CAAC;AAAA,CACpC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAyB,EACD;IACxB,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,WAAW,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC9C,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CACb,kBAAkB,KAAK,iDAAiD,CACzE,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;QACvC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACf;AAED,4JAAwE;AAExE;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAqB,EAAY;IAChE,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC9C,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAAA,CAC3C"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @syncular/core - Data transformation hooks
3
+ *
4
+ * Provides interfaces for field-level transformations (e.g., encryption/decryption)
5
+ * that can be applied during sync operations.
6
+ */
7
+ /**
8
+ * Direction of the transformation.
9
+ * - 'toClient': Server → Client (e.g., decrypt for client)
10
+ * - 'toServer': Client → Server (e.g., encrypt for server)
11
+ */
12
+ export type TransformDirection = 'toClient' | 'toServer';
13
+ /**
14
+ * Context passed to transform functions.
15
+ */
16
+ export interface TransformContext {
17
+ /** Direction of transformation */
18
+ direction: TransformDirection;
19
+ /** Scope name */
20
+ scope: string;
21
+ /** Table name */
22
+ table: string;
23
+ /** Row ID */
24
+ rowId: string;
25
+ /** User ID performing the operation */
26
+ userId: string;
27
+ }
28
+ /**
29
+ * A field transformer handles transformation of a single field.
30
+ *
31
+ * @example
32
+ * const secretNotesTransformer: FieldTransformer = {
33
+ * field: 'secret_notes',
34
+ * async transform(value, ctx) {
35
+ * const key = await getUserEncryptionKey(ctx.userId);
36
+ * return ctx.direction === 'toClient'
37
+ * ? decrypt(value as string, key)
38
+ * : encrypt(value as string, key);
39
+ * }
40
+ * };
41
+ */
42
+ export interface FieldTransformer {
43
+ /** Field name to transform */
44
+ field: string;
45
+ /**
46
+ * Transform the field value.
47
+ * @param value - Current field value
48
+ * @param ctx - Transform context
49
+ * @returns Transformed value
50
+ */
51
+ transform(value: unknown, ctx: TransformContext): Promise<unknown> | unknown;
52
+ }
53
+ /**
54
+ * Configuration for transforms on a scope.
55
+ */
56
+ export interface ScopeTransformConfig {
57
+ /** Scope name this config applies to */
58
+ scope: string;
59
+ /** Field transformers for this scope */
60
+ fields?: FieldTransformer[];
61
+ }
62
+ /**
63
+ * Registry for managing data transforms.
64
+ *
65
+ * @example
66
+ * const transforms = new TransformRegistry();
67
+ *
68
+ * transforms.register({
69
+ * scope: 'tasks',
70
+ * fields: [{
71
+ * field: 'secret_notes',
72
+ * async transform(value, ctx) {
73
+ * const key = await getUserEncryptionKey(ctx.userId);
74
+ * return ctx.direction === 'toClient'
75
+ * ? decrypt(value as string, key)
76
+ * : encrypt(value as string, key);
77
+ * }
78
+ * }]
79
+ * });
80
+ *
81
+ * // Apply transforms to data
82
+ * const transformed = await transforms.apply(
83
+ * [{ id: '1', secret_notes: 'encrypted...' }],
84
+ * { direction: 'toClient', scope: 'tasks', ... }
85
+ * );
86
+ */
87
+ export declare class TransformRegistry {
88
+ private configs;
89
+ /**
90
+ * Register transform config for a scope.
91
+ * @throws If config for this scope is already registered
92
+ */
93
+ register(config: ScopeTransformConfig): void;
94
+ /**
95
+ * Unregister transform config by scope.
96
+ * @returns true if config was found and removed
97
+ */
98
+ unregister(scope: string): boolean;
99
+ /**
100
+ * Get config for a scope.
101
+ */
102
+ get(scope: string): ScopeTransformConfig | undefined;
103
+ /**
104
+ * Check if any transforms are registered for a scope.
105
+ */
106
+ hasTransforms(scope: string): boolean;
107
+ /**
108
+ * Get all registered configs.
109
+ */
110
+ getAll(): ScopeTransformConfig[];
111
+ /**
112
+ * Apply transforms to a single row.
113
+ *
114
+ * @param row - Row data to transform
115
+ * @param ctx - Transform context (without rowId, will be extracted from row)
116
+ * @param rowIdField - Field name for row ID (default: 'id')
117
+ * @returns Transformed row
118
+ */
119
+ applyToRow<T extends Record<string, unknown>>(row: T, ctx: Omit<TransformContext, 'rowId'>, rowIdField?: string): Promise<T>;
120
+ /**
121
+ * Apply transforms to multiple rows.
122
+ *
123
+ * @param rows - Array of rows to transform
124
+ * @param ctx - Transform context (without rowId)
125
+ * @param rowIdField - Field name for row ID (default: 'id')
126
+ * @returns Transformed rows
127
+ */
128
+ apply<T extends Record<string, unknown>>(rows: T[], ctx: Omit<TransformContext, 'rowId'>, rowIdField?: string): Promise<T[]>;
129
+ /**
130
+ * Apply transforms to a mutation payload.
131
+ *
132
+ * @param payload - Mutation payload (may be partial row)
133
+ * @param ctx - Full transform context
134
+ * @returns Transformed payload
135
+ */
136
+ applyToPayload<T extends Record<string, unknown>>(payload: T | null, ctx: TransformContext): Promise<T | null>;
137
+ /**
138
+ * Clear all registered configs.
139
+ */
140
+ clear(): void;
141
+ }
142
+ /**
143
+ * Create a new transform registry.
144
+ */
145
+ export declare function createTransformRegistry(): TransformRegistry;
146
+ //# sourceMappingURL=transforms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../src/transforms.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,UAAU,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,SAAS,EAAE,kBAAkB,CAAC;IAC9B,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa;IACb,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC9E;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAgD;IAE/D;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAO3C;IAED;;;OAGG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjC;IAED;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAEnD;IAED;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGpC;IAED;;OAEG;IACH,MAAM,IAAI,oBAAoB,EAAE,CAE/B;IAED;;;;;;;OAOG;IACG,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChD,GAAG,EAAE,CAAC,EACN,GAAG,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,EACpC,UAAU,SAAO,GAChB,OAAO,CAAC,CAAC,CAAC,CA4BZ;IAED;;;;;;;OAOG;IACG,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3C,IAAI,EAAE,CAAC,EAAE,EACT,GAAG,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,EACpC,UAAU,SAAO,GAChB,OAAO,CAAC,CAAC,EAAE,CAAC,CASd;IAED;;;;;;OAMG;IACG,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpD,OAAO,EAAE,CAAC,GAAG,IAAI,EACjB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CA4BnB;IAED;;OAEG;IACH,KAAK,IAAI,IAAI,CAEZ;CACF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,iBAAiB,CAE3D"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @syncular/core - Data transformation hooks
3
+ *
4
+ * Provides interfaces for field-level transformations (e.g., encryption/decryption)
5
+ * that can be applied during sync operations.
6
+ */
7
+ /**
8
+ * Registry for managing data transforms.
9
+ *
10
+ * @example
11
+ * const transforms = new TransformRegistry();
12
+ *
13
+ * transforms.register({
14
+ * scope: 'tasks',
15
+ * fields: [{
16
+ * field: 'secret_notes',
17
+ * async transform(value, ctx) {
18
+ * const key = await getUserEncryptionKey(ctx.userId);
19
+ * return ctx.direction === 'toClient'
20
+ * ? decrypt(value as string, key)
21
+ * : encrypt(value as string, key);
22
+ * }
23
+ * }]
24
+ * });
25
+ *
26
+ * // Apply transforms to data
27
+ * const transformed = await transforms.apply(
28
+ * [{ id: '1', secret_notes: 'encrypted...' }],
29
+ * { direction: 'toClient', scope: 'tasks', ... }
30
+ * );
31
+ */
32
+ export class TransformRegistry {
33
+ configs = new Map();
34
+ /**
35
+ * Register transform config for a scope.
36
+ * @throws If config for this scope is already registered
37
+ */
38
+ register(config) {
39
+ if (this.configs.has(config.scope)) {
40
+ throw new Error(`Transform config for scope "${config.scope}" is already registered`);
41
+ }
42
+ this.configs.set(config.scope, config);
43
+ }
44
+ /**
45
+ * Unregister transform config by scope.
46
+ * @returns true if config was found and removed
47
+ */
48
+ unregister(scope) {
49
+ return this.configs.delete(scope);
50
+ }
51
+ /**
52
+ * Get config for a scope.
53
+ */
54
+ get(scope) {
55
+ return this.configs.get(scope);
56
+ }
57
+ /**
58
+ * Check if any transforms are registered for a scope.
59
+ */
60
+ hasTransforms(scope) {
61
+ const config = this.configs.get(scope);
62
+ return config !== undefined && (config.fields?.length ?? 0) > 0;
63
+ }
64
+ /**
65
+ * Get all registered configs.
66
+ */
67
+ getAll() {
68
+ return Array.from(this.configs.values());
69
+ }
70
+ /**
71
+ * Apply transforms to a single row.
72
+ *
73
+ * @param row - Row data to transform
74
+ * @param ctx - Transform context (without rowId, will be extracted from row)
75
+ * @param rowIdField - Field name for row ID (default: 'id')
76
+ * @returns Transformed row
77
+ */
78
+ async applyToRow(row, ctx, rowIdField = 'id') {
79
+ const config = this.configs.get(ctx.scope);
80
+ if (!config?.fields?.length) {
81
+ return row;
82
+ }
83
+ const rowId = String(row[rowIdField] ?? '');
84
+ const fullCtx = { ...ctx, rowId };
85
+ const result = { ...row };
86
+ for (const transformer of config.fields) {
87
+ if (transformer.field in result) {
88
+ try {
89
+ result[transformer.field] = (await transformer.transform(result[transformer.field], fullCtx));
90
+ }
91
+ catch (err) {
92
+ console.error(`[transforms] Error transforming field "${transformer.field}" for ${ctx.scope}:${rowId}:`, err);
93
+ // Keep original value on error
94
+ }
95
+ }
96
+ }
97
+ return result;
98
+ }
99
+ /**
100
+ * Apply transforms to multiple rows.
101
+ *
102
+ * @param rows - Array of rows to transform
103
+ * @param ctx - Transform context (without rowId)
104
+ * @param rowIdField - Field name for row ID (default: 'id')
105
+ * @returns Transformed rows
106
+ */
107
+ async apply(rows, ctx, rowIdField = 'id') {
108
+ const config = this.configs.get(ctx.scope);
109
+ if (!config?.fields?.length) {
110
+ return rows;
111
+ }
112
+ return Promise.all(rows.map((row) => this.applyToRow(row, ctx, rowIdField)));
113
+ }
114
+ /**
115
+ * Apply transforms to a mutation payload.
116
+ *
117
+ * @param payload - Mutation payload (may be partial row)
118
+ * @param ctx - Full transform context
119
+ * @returns Transformed payload
120
+ */
121
+ async applyToPayload(payload, ctx) {
122
+ if (!payload)
123
+ return null;
124
+ const config = this.configs.get(ctx.scope);
125
+ if (!config?.fields?.length) {
126
+ return payload;
127
+ }
128
+ const result = { ...payload };
129
+ for (const transformer of config.fields) {
130
+ if (transformer.field in result) {
131
+ try {
132
+ result[transformer.field] = (await transformer.transform(result[transformer.field], ctx));
133
+ }
134
+ catch (err) {
135
+ console.error(`[transforms] Error transforming field "${transformer.field}" for ${ctx.scope}:${ctx.rowId}:`, err);
136
+ // Keep original value on error
137
+ }
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+ /**
143
+ * Clear all registered configs.
144
+ */
145
+ clear() {
146
+ this.configs.clear();
147
+ }
148
+ }
149
+ /**
150
+ * Create a new transform registry.
151
+ */
152
+ export function createTransformRegistry() {
153
+ return new TransformRegistry();
154
+ }
155
+ //# sourceMappingURL=transforms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transforms.js","sourceRoot":"","sources":["../src/transforms.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @syncular/core - Shared types for sync infrastructure
3
+ *
4
+ * Non-protocol types: conflict detection, transport interfaces.
5
+ * Protocol types (SyncOp, SyncPushRequest, etc.) live in ./schemas/sync.ts
6
+ */
7
+ import type { SyncCombinedRequest, SyncCombinedResponse } from './schemas/sync';
8
+ /**
9
+ * Result of a conflict check - no conflict
10
+ */
11
+ interface ConflictCheckResultOk {
12
+ hasConflict: false;
13
+ }
14
+ /**
15
+ * Result of a conflict check - conflict detected
16
+ */
17
+ interface ConflictCheckResultConflict {
18
+ hasConflict: true;
19
+ /** Fields with conflicting changes */
20
+ conflictingFields: string[];
21
+ /** Current server row state */
22
+ serverRow: Record<string, unknown>;
23
+ /** Current server version */
24
+ serverVersion: number;
25
+ }
26
+ /**
27
+ * Union type for conflict check results
28
+ */
29
+ export type ConflictCheckResult = ConflictCheckResultOk | ConflictCheckResultConflict;
30
+ /**
31
+ * Result of a field-level merge - can merge
32
+ */
33
+ export interface MergeResultOk {
34
+ canMerge: true;
35
+ /** Merged payload combining client and server changes */
36
+ mergedPayload: Record<string, unknown>;
37
+ }
38
+ /**
39
+ * Result of a field-level merge - cannot merge
40
+ */
41
+ export interface MergeResultConflict {
42
+ canMerge: false;
43
+ /** Fields that cannot be auto-merged */
44
+ conflictingFields: string[];
45
+ }
46
+ /**
47
+ * Union type for merge results
48
+ */
49
+ export type MergeResult = MergeResultOk | MergeResultConflict;
50
+ /**
51
+ * Options for transport operations.
52
+ * Provides hooks for auth errors and cancellation support.
53
+ */
54
+ export interface SyncTransportOptions {
55
+ /**
56
+ * Called when auth fails (401/403).
57
+ * Return true to retry the request after refreshing auth.
58
+ */
59
+ onAuthError?: () => Promise<boolean>;
60
+ /**
61
+ * Abort signal for cancellation support.
62
+ */
63
+ signal?: AbortSignal;
64
+ }
65
+ /**
66
+ * Blob transport operations (optional extension to SyncTransport).
67
+ * When present, enables blob upload/download through the same transport.
68
+ */
69
+ export interface SyncTransportBlobs {
70
+ /**
71
+ * Initiate a blob upload.
72
+ * Returns presigned URL info or indicates blob already exists (dedup).
73
+ */
74
+ initiateUpload(args: {
75
+ hash: string;
76
+ size: number;
77
+ mimeType: string;
78
+ }): Promise<{
79
+ exists: boolean;
80
+ uploadUrl?: string;
81
+ uploadMethod?: 'PUT' | 'POST';
82
+ uploadHeaders?: Record<string, string>;
83
+ }>;
84
+ /**
85
+ * Complete a blob upload.
86
+ * Call this after uploading to the presigned URL.
87
+ */
88
+ completeUpload(hash: string): Promise<{
89
+ ok: boolean;
90
+ error?: string;
91
+ }>;
92
+ /**
93
+ * Get a presigned download URL.
94
+ */
95
+ getDownloadUrl(hash: string): Promise<{
96
+ url: string;
97
+ expiresAt: string;
98
+ }>;
99
+ }
100
+ /**
101
+ * Transport interface for sync operations.
102
+ */
103
+ export interface SyncTransport {
104
+ /**
105
+ * Combined push+pull in a single round-trip.
106
+ */
107
+ sync(request: SyncCombinedRequest, options?: SyncTransportOptions): Promise<SyncCombinedResponse>;
108
+ /**
109
+ * Download an encoded bootstrap snapshot chunk.
110
+ */
111
+ fetchSnapshotChunk(request: {
112
+ chunkId: string;
113
+ }, options?: SyncTransportOptions): Promise<Uint8Array>;
114
+ /**
115
+ * Optional blob operations.
116
+ * When present, enables blob upload/download functionality.
117
+ */
118
+ blobs?: SyncTransportBlobs;
119
+ }
120
+ /**
121
+ * Transport error with additional context
122
+ */
123
+ export declare class SyncTransportError extends Error {
124
+ readonly status?: number | undefined;
125
+ readonly code?: string | undefined;
126
+ constructor(message: string, status?: number | undefined, code?: string | undefined);
127
+ }
128
+ export {};
129
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAMhF;;GAEG;AACH,UAAU,qBAAqB;IAC7B,WAAW,EAAE,KAAK,CAAC;CACpB;AAED;;GAEG;AACH,UAAU,2BAA2B;IACnC,WAAW,EAAE,IAAI,CAAC;IAClB,sCAAsC;IACtC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAC3B,qBAAqB,GACrB,2BAA2B,CAAC;AAEhC;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,IAAI,CAAC;IACf,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,KAAK,CAAC;IAChB,wCAAwC;IACxC,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,mBAAmB,CAAC;AAM9D;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QACV,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;QAC9B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACxC,CAAC,CAAC;IAEH;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEvE;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QACpC,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,IAAI,CACF,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC;;OAEG;IACH,kBAAkB,CAChB,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,EAC5B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvB;;;OAGG;IACH,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;aAGzB,MAAM,CAAC;aACP,IAAI,CAAC;IAHvB,YACE,OAAO,EAAE,MAAM,EACC,MAAM,CAAC,oBAAQ,EACf,IAAI,CAAC,oBAAQ,EAI9B;CACF"}
package/dist/types.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @syncular/core - Shared types for sync infrastructure
3
+ *
4
+ * Non-protocol types: conflict detection, transport interfaces.
5
+ * Protocol types (SyncOp, SyncPushRequest, etc.) live in ./schemas/sync.ts
6
+ */
7
+ /**
8
+ * Transport error with additional context
9
+ */
10
+ export class SyncTransportError extends Error {
11
+ status;
12
+ code;
13
+ constructor(message, status, code) {
14
+ super(message);
15
+ this.status = status;
16
+ this.code = code;
17
+ this.name = 'SyncTransportError';
18
+ }
19
+ }
20
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA4IH;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAGzB,MAAM;IACN,IAAI;IAHtB,YACE,OAAe,EACC,MAAe,EACf,IAAa,EAC7B;QACA,KAAK,CAAC,OAAO,CAAC,CAAC;sBAHC,MAAM;oBACN,IAAI;QAGpB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IAAA,CAClC;CACF"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@syncular/core",
3
+ "version": "0.0.1-60",
4
+ "description": "Core protocol types and shared utilities for the Syncular sync framework",
5
+ "license": "MIT",
6
+ "author": "Benjamin Kniffler",
7
+ "homepage": "https://syncular.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/syncular/syncular.git",
11
+ "directory": "packages/core"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/syncular/syncular/issues"
15
+ },
16
+ "keywords": [
17
+ "sync",
18
+ "offline-first",
19
+ "realtime",
20
+ "database",
21
+ "typescript"
22
+ ],
23
+ "private": false,
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "bun": "./src/index.ts",
31
+ "import": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ }
35
+ }
36
+ },
37
+ "scripts": {
38
+ "test": "bun test src/__tests__",
39
+ "tsgo": "tsgo --noEmit",
40
+ "build": "rm -rf dist && tsgo",
41
+ "release": "bun pm pack --destination . && npm publish ./*.tgz --tag latest && rm -f ./*.tgz"
42
+ },
43
+ "peerDependencies": {
44
+ "kysely": "^0.28.0",
45
+ "zod": "^4.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@syncular/config": "0.0.0",
49
+ "kysely": "*",
50
+ "zod": "*"
51
+ },
52
+ "files": [
53
+ "dist",
54
+ "src"
55
+ ]
56
+ }