@syncular/core 0.0.6-96 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/dist/blobs.d.ts +14 -7
  2. package/dist/blobs.d.ts.map +1 -1
  3. package/dist/blobs.js.map +1 -1
  4. package/dist/column-codecs.d.ts +3 -0
  5. package/dist/column-codecs.d.ts.map +1 -1
  6. package/dist/column-codecs.js +32 -4
  7. package/dist/column-codecs.js.map +1 -1
  8. package/dist/conflict.js.map +1 -1
  9. package/dist/database.js +1 -1
  10. package/dist/database.js.map +1 -1
  11. package/dist/error-responses.d.ts +393 -0
  12. package/dist/error-responses.d.ts.map +1 -0
  13. package/dist/error-responses.js +393 -0
  14. package/dist/error-responses.js.map +1 -0
  15. package/dist/http/api-client.d.ts +11 -0
  16. package/dist/http/api-client.d.ts.map +1 -0
  17. package/dist/http/api-client.js +30 -0
  18. package/dist/http/api-client.js.map +1 -0
  19. package/dist/http/blob.d.ts +6 -0
  20. package/dist/http/blob.d.ts.map +1 -0
  21. package/dist/http/blob.js +46 -0
  22. package/dist/http/blob.js.map +1 -0
  23. package/dist/http/generated/api.d.ts +5241 -0
  24. package/dist/http/generated/api.d.ts.map +1 -0
  25. package/dist/http/generated/api.js +6 -0
  26. package/dist/http/generated/api.js.map +1 -0
  27. package/dist/http/index.d.ts +18 -0
  28. package/dist/http/index.d.ts.map +1 -0
  29. package/dist/http/index.js +212 -0
  30. package/dist/http/index.js.map +1 -0
  31. package/dist/http/shared.d.ts +45 -0
  32. package/dist/http/shared.d.ts.map +1 -0
  33. package/dist/http/shared.js +122 -0
  34. package/dist/http/shared.js.map +1 -0
  35. package/dist/http/transport-client.d.ts +31 -0
  36. package/dist/http/transport-client.d.ts.map +1 -0
  37. package/dist/http/transport-client.js +193 -0
  38. package/dist/http/transport-client.js.map +1 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +19 -15
  42. package/dist/index.js.map +1 -1
  43. package/dist/kysely-column-codecs.d.ts +17 -1
  44. package/dist/kysely-column-codecs.d.ts.map +1 -1
  45. package/dist/kysely-column-codecs.js +56 -70
  46. package/dist/kysely-column-codecs.js.map +1 -1
  47. package/dist/logger.js +1 -1
  48. package/dist/logger.js.map +1 -1
  49. package/dist/schemas/blobs.d.ts +7 -1
  50. package/dist/schemas/blobs.d.ts.map +1 -1
  51. package/dist/schemas/blobs.js +1 -0
  52. package/dist/schemas/blobs.js.map +1 -1
  53. package/dist/schemas/common.d.ts +73 -1
  54. package/dist/schemas/common.d.ts.map +1 -1
  55. package/dist/schemas/common.js +38 -0
  56. package/dist/schemas/common.js.map +1 -1
  57. package/dist/schemas/index.js +3 -3
  58. package/dist/schemas/sync.d.ts +712 -38
  59. package/dist/schemas/sync.d.ts.map +1 -1
  60. package/dist/schemas/sync.js +209 -5
  61. package/dist/schemas/sync.js.map +1 -1
  62. package/dist/scopes/index.d.ts +5 -0
  63. package/dist/scopes/index.d.ts.map +1 -1
  64. package/dist/scopes/index.js +27 -0
  65. package/dist/scopes/index.js.map +1 -1
  66. package/dist/sentry.d.ts +31 -0
  67. package/dist/sentry.d.ts.map +1 -0
  68. package/dist/sentry.js +147 -0
  69. package/dist/sentry.js.map +1 -0
  70. package/dist/snapshot-chunks.d.ts +104 -20
  71. package/dist/snapshot-chunks.d.ts.map +1 -1
  72. package/dist/snapshot-chunks.js +665 -72
  73. package/dist/snapshot-chunks.js.map +1 -1
  74. package/dist/sync-packs.d.ts +33 -0
  75. package/dist/sync-packs.d.ts.map +1 -0
  76. package/dist/sync-packs.js +821 -0
  77. package/dist/sync-packs.js.map +1 -0
  78. package/dist/telemetry.d.ts.map +1 -1
  79. package/dist/telemetry.js +3 -2
  80. package/dist/telemetry.js.map +1 -1
  81. package/dist/transforms.js.map +1 -1
  82. package/dist/types.d.ts +48 -7
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/dist/utils/bytes.d.ts +4 -0
  86. package/dist/utils/bytes.d.ts.map +1 -0
  87. package/dist/utils/bytes.js +66 -0
  88. package/dist/utils/bytes.js.map +1 -0
  89. package/dist/utils/compression.d.ts +11 -3
  90. package/dist/utils/compression.d.ts.map +1 -1
  91. package/dist/utils/compression.js +102 -50
  92. package/dist/utils/compression.js.map +1 -1
  93. package/dist/utils/crypto.d.ts +7 -1
  94. package/dist/utils/crypto.d.ts.map +1 -1
  95. package/dist/utils/crypto.js +89 -7
  96. package/dist/utils/crypto.js.map +1 -1
  97. package/dist/utils/id.d.ts.map +1 -1
  98. package/dist/utils/id.js +4 -1
  99. package/dist/utils/id.js.map +1 -1
  100. package/dist/utils/index.d.ts +5 -0
  101. package/dist/utils/index.d.ts.map +1 -1
  102. package/dist/utils/index.js +9 -4
  103. package/dist/utils/index.js.map +1 -1
  104. package/dist/utils/internal-runtime.d.ts +16 -0
  105. package/dist/utils/internal-runtime.d.ts.map +1 -0
  106. package/dist/utils/internal-runtime.js +14 -0
  107. package/dist/utils/internal-runtime.js.map +1 -0
  108. package/dist/utils/object.js.map +1 -1
  109. package/dist/utils/realtime-change-index.d.ts +9 -0
  110. package/dist/utils/realtime-change-index.d.ts.map +1 -0
  111. package/dist/utils/realtime-change-index.js +36 -0
  112. package/dist/utils/realtime-change-index.js.map +1 -0
  113. package/dist/utils/realtime-connection-registry.d.ts +40 -0
  114. package/dist/utils/realtime-connection-registry.d.ts.map +1 -0
  115. package/dist/utils/realtime-connection-registry.js +237 -0
  116. package/dist/utils/realtime-connection-registry.js.map +1 -0
  117. package/dist/utils/table-registry.d.ts +10 -0
  118. package/dist/utils/table-registry.d.ts.map +1 -0
  119. package/dist/utils/table-registry.js +54 -0
  120. package/dist/utils/table-registry.js.map +1 -0
  121. package/dist/utils/url.d.ts +3 -0
  122. package/dist/utils/url.d.ts.map +1 -0
  123. package/dist/utils/url.js +20 -0
  124. package/dist/utils/url.js.map +1 -0
  125. package/package.json +52 -7
  126. package/src/blobs.ts +24 -7
  127. package/src/column-codecs.ts +36 -8
  128. package/src/error-responses.ts +423 -0
  129. package/src/http/api-client.ts +39 -0
  130. package/src/http/blob.ts +95 -0
  131. package/src/http/generated/api.ts +5241 -0
  132. package/src/http/index.ts +365 -0
  133. package/src/http/shared.ts +220 -0
  134. package/src/http/transport-client.ts +314 -0
  135. package/src/index.ts +4 -0
  136. package/src/kysely-column-codecs.ts +81 -86
  137. package/src/schemas/blobs.ts +10 -1
  138. package/src/schemas/common.ts +44 -0
  139. package/src/schemas/sync.ts +298 -7
  140. package/src/scopes/index.ts +42 -0
  141. package/src/sentry.ts +200 -0
  142. package/src/snapshot-chunks.ts +921 -69
  143. package/src/sync-packs.ts +1096 -0
  144. package/src/telemetry.ts +7 -2
  145. package/src/types.ts +63 -8
  146. package/src/utils/bytes.ts +75 -0
  147. package/src/utils/compression.ts +147 -51
  148. package/src/utils/crypto.ts +120 -7
  149. package/src/utils/id.ts +11 -1
  150. package/src/utils/index.ts +5 -0
  151. package/src/utils/internal-runtime.ts +39 -0
  152. package/src/utils/realtime-change-index.ts +44 -0
  153. package/src/utils/realtime-connection-registry.ts +286 -0
  154. package/src/utils/table-registry.ts +67 -0
  155. package/src/utils/url.ts +29 -0
  156. package/src/__tests__/compression.test.ts +0 -54
  157. package/src/__tests__/conflict.test.ts +0 -325
  158. package/src/__tests__/telemetry.test.ts +0 -170
  159. package/src/__tests__/utils.test.ts +0 -27
@@ -0,0 +1,54 @@
1
+ export function registerTableOrThrow(registeredTables, table, duplicateTableError) {
2
+ if (registeredTables.has(table))
3
+ throw new Error(duplicateTableError(table));
4
+ registeredTables.add(table);
5
+ }
6
+ export function createTableLookup(handlers, duplicateTableError) {
7
+ const byTable = new Map();
8
+ for (const handler of handlers) {
9
+ if (byTable.has(handler.table)) {
10
+ throw new Error(duplicateTableError(handler.table));
11
+ }
12
+ byTable.set(handler.table, handler);
13
+ }
14
+ return byTable;
15
+ }
16
+ export function assertKnownTableDependencies(handlers, byTable, unknownDependencyError) {
17
+ for (const handler of handlers) {
18
+ for (const dependency of handler.dependsOn ?? []) {
19
+ if (!byTable.has(dependency)) {
20
+ throw new Error(unknownDependencyError(handler.table, dependency));
21
+ }
22
+ }
23
+ }
24
+ }
25
+ export function topologicallySortTablesByDependencies(byTable, targetTable) {
26
+ const visited = new Set();
27
+ const visiting = new Set();
28
+ const sorted = [];
29
+ const visit = (table) => {
30
+ if (visited.has(table))
31
+ return;
32
+ if (visiting.has(table)) {
33
+ throw new Error(`Circular dependency detected involving table "${table}"`);
34
+ }
35
+ const handler = byTable.get(table);
36
+ if (!handler)
37
+ throw new Error(`Unknown table: ${table}`);
38
+ visiting.add(table);
39
+ for (const dependency of handler.dependsOn ?? [])
40
+ visit(dependency);
41
+ visiting.delete(table);
42
+ visited.add(table);
43
+ sorted.push(handler);
44
+ };
45
+ if (targetTable) {
46
+ visit(targetTable);
47
+ }
48
+ else {
49
+ for (const table of byTable.keys())
50
+ visit(table);
51
+ }
52
+ return sorted;
53
+ }
54
+ //# sourceMappingURL=table-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-registry.js","sourceRoot":"","sources":["../../src/utils/table-registry.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,oBAAoB,CAClC,gBAA6B,EAC7B,KAAa,EACb,mBAA8C;IAE9C,IAAI,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7E,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AACD,MAAM,UAAU,iBAAiB,CAC/B,QAAsB,EACtB,mBAA8C;IAE9C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;IACrC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AACD,MAAM,UAAU,4BAA4B,CAC1C,QAAsB,EACtB,OAA+B,EAC/B,sBAAqE;IAErE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;YACjD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;YACrE,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AACD,MAAM,UAAU,qCAAqC,CACnD,OAA+B,EAC/B,WAAoB;IAEpB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE;QAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO;QAC/B,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,iDAAiD,KAAK,GAAG,CAC1D,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,EAAE,CAAC,CAAC;QACzD,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACpB,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,SAAS,IAAI,EAAE;YAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QACpE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,CAAC;IACF,IAAI,WAAW,EAAE,CAAC;QAChB,KAAK,CAAC,WAAW,CAAC,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE;YAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function normalizeSyncBaseUrl(url: string): string;
2
+ export declare function resolveUrlFromBase(baseUrl: string, path: string, origin?: string): string;
3
+ //# sourceMappingURL=url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKxD;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAYR"}
@@ -0,0 +1,20 @@
1
+ const ABSOLUTE_URL_SCHEME_REGEX = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
2
+ const trimTrailingSlashes = (value) => value.replace(/\/+$/, '');
3
+ export function normalizeSyncBaseUrl(url) {
4
+ const trimmed = trimTrailingSlashes(url.trim());
5
+ if (!trimmed.endsWith('/sync'))
6
+ return trimmed;
7
+ const baseUrl = trimmed.slice(0, -'/sync'.length);
8
+ return baseUrl.length > 0 ? baseUrl : '/';
9
+ }
10
+ export function resolveUrlFromBase(baseUrl, path, origin) {
11
+ const normalizedPath = path.replace(/^\/+/, '');
12
+ const normalizedBaseUrl = trimTrailingSlashes(baseUrl);
13
+ if (ABSOLUTE_URL_SCHEME_REGEX.test(baseUrl)) {
14
+ return new URL(normalizedPath, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`).toString();
15
+ }
16
+ if (origin)
17
+ return new URL(`${normalizedBaseUrl}/${normalizedPath}`, origin).toString();
18
+ return `${normalizedBaseUrl}/${normalizedPath}`;
19
+ }
20
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA,MAAM,yBAAyB,GAAG,2BAA2B,CAAC;AAE9D,MAAM,mBAAmB,GAAG,CAAC,KAAa,EAAU,EAAE,CACpD,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAE5B,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,IAAY,EACZ,MAAe;IAEf,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChD,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACvD,IAAI,yBAAyB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5C,OAAO,IAAI,GAAG,CACZ,cAAc,EACd,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAChD,CAAC,QAAQ,EAAE,CAAC;IACf,CAAC;IACD,IAAI,MAAM;QACR,OAAO,IAAI,GAAG,CAAC,GAAG,iBAAiB,IAAI,cAAc,EAAE,EAAE,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC9E,OAAO,GAAG,iBAAiB,IAAI,cAAc,EAAE,CAAC;AAClD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/core",
3
- "version": "0.0.6-96",
3
+ "version": "0.1.0",
4
4
  "description": "Core protocol types and shared utilities for the Syncular sync framework",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -21,6 +21,7 @@
21
21
  "typescript"
22
22
  ],
23
23
  "private": false,
24
+ "sideEffects": false,
24
25
  "publishConfig": {
25
26
  "access": "public"
26
27
  },
@@ -33,25 +34,69 @@
33
34
  "types": "./dist/index.d.ts",
34
35
  "default": "./dist/index.js"
35
36
  }
37
+ },
38
+ "./http": {
39
+ "bun": "./src/http/index.ts",
40
+ "browser": "./src/http/index.ts",
41
+ "import": {
42
+ "types": "./dist/http/index.d.ts",
43
+ "default": "./dist/http/index.js"
44
+ }
45
+ },
46
+ "./http/blob": {
47
+ "bun": "./src/http/blob.ts",
48
+ "browser": "./src/http/blob.ts",
49
+ "import": {
50
+ "types": "./dist/http/blob.d.ts",
51
+ "default": "./dist/http/blob.js"
52
+ }
53
+ },
54
+ "./sentry": {
55
+ "bun": "./src/sentry.ts",
56
+ "browser": "./src/sentry.ts",
57
+ "import": {
58
+ "types": "./dist/sentry.d.ts",
59
+ "default": "./dist/sentry.js"
60
+ }
36
61
  }
37
62
  },
38
63
  "scripts": {
64
+ "fixtures:protocol": "bun scripts/generate-protocol-fixtures.ts",
39
65
  "test": "bun test src/__tests__",
40
66
  "tsgo": "tsgo --noEmit",
41
67
  "build": "tsgo",
42
- "release": "bunx syncular-publish"
68
+ "release": "syncular-publish",
69
+ "generate:http-types": "openapi-typescript ../server/openapi.json -o src/http/generated/api.ts"
43
70
  },
44
71
  "peerDependencies": {
45
72
  "kysely": "^0.28.0",
46
- "zod": "^4.0.0"
73
+ "zod": "^4.0.0",
74
+ "openapi-fetch": "^0.17.0"
47
75
  },
48
76
  "devDependencies": {
49
- "@syncular/config": "0.0.0",
77
+ "@syncular/config": "workspace:*",
50
78
  "kysely": "*",
51
- "zod": "*"
79
+ "zod": "*",
80
+ "openapi-fetch": "^0.17.0",
81
+ "openapi-typescript": "^7.13.0"
52
82
  },
53
83
  "files": [
54
84
  "dist",
55
- "src"
56
- ]
85
+ "src",
86
+ "!src/**/*.test.ts",
87
+ "!src/**/*.test.tsx",
88
+ "!src/**/__tests__/**",
89
+ "!dist/**/*.test.*",
90
+ "!dist/**/__tests__/**",
91
+ "!test/**",
92
+ "!tests/**"
93
+ ],
94
+ "dependencies": {
95
+ "fflate": "^0.8.2"
96
+ },
97
+ "peerDependenciesMeta": {
98
+ "openapi-fetch": {
99
+ "optional": true
100
+ }
101
+ }
57
102
  }
package/src/blobs.ts CHANGED
@@ -56,6 +56,8 @@ export interface BlobTransport {
56
56
  export interface BlobSignUploadOptions {
57
57
  /** SHA-256 hash (for naming and checksum validation) */
58
58
  hash: string;
59
+ /** Optional tenant/partition namespace for storage lookups */
60
+ partitionId?: string;
59
61
  /** Content size in bytes */
60
62
  size: number;
61
63
  /** MIME type */
@@ -82,10 +84,16 @@ export interface BlobSignedUpload {
82
84
  export interface BlobSignDownloadOptions {
83
85
  /** SHA-256 hash */
84
86
  hash: string;
87
+ /** Optional tenant/partition namespace for storage lookups */
88
+ partitionId?: string;
85
89
  /** URL expiration in seconds */
86
90
  expiresIn: number;
87
91
  }
88
92
 
93
+ export interface BlobStoragePartitionOptions {
94
+ partitionId?: string;
95
+ }
96
+
89
97
  /**
90
98
  * Adapter for blob storage backends (S3, R2, custom).
91
99
  * Implementations handle actual storage; the sync server orchestrates.
@@ -108,19 +116,20 @@ export interface BlobStorageAdapter {
108
116
  /**
109
117
  * Check if a blob exists in storage.
110
118
  */
111
- exists(hash: string): Promise<boolean>;
119
+ exists(hash: string, options?: BlobStoragePartitionOptions): Promise<boolean>;
112
120
 
113
121
  /**
114
122
  * Delete a blob (for garbage collection).
115
123
  */
116
- delete(hash: string): Promise<void>;
124
+ delete(hash: string, options?: BlobStoragePartitionOptions): Promise<void>;
117
125
 
118
126
  /**
119
127
  * Get blob metadata from storage (optional).
120
128
  * Used to verify uploads completed successfully.
121
129
  */
122
130
  getMetadata?(
123
- hash: string
131
+ hash: string,
132
+ options?: BlobStoragePartitionOptions
124
133
  ): Promise<{ size: number; mimeType?: string } | null>;
125
134
 
126
135
  /**
@@ -130,7 +139,8 @@ export interface BlobStorageAdapter {
130
139
  put?(
131
140
  hash: string,
132
141
  data: Uint8Array,
133
- metadata?: Record<string, unknown>
142
+ metadata?: Record<string, unknown>,
143
+ options?: BlobStoragePartitionOptions
134
144
  ): Promise<void>;
135
145
 
136
146
  /**
@@ -140,18 +150,25 @@ export interface BlobStorageAdapter {
140
150
  putStream?(
141
151
  hash: string,
142
152
  stream: ReadableStream<Uint8Array>,
143
- metadata?: Record<string, unknown>
153
+ metadata?: Record<string, unknown>,
154
+ options?: BlobStoragePartitionOptions
144
155
  ): Promise<void>;
145
156
 
146
157
  /**
147
158
  * Get blob data directly (for adapters that support direct retrieval).
148
159
  */
149
- get?(hash: string): Promise<Uint8Array | null>;
160
+ get?(
161
+ hash: string,
162
+ options?: BlobStoragePartitionOptions
163
+ ): Promise<Uint8Array | null>;
150
164
 
151
165
  /**
152
166
  * Get blob data directly as a stream (for adapters that support stream retrieval).
153
167
  */
154
- getStream?(hash: string): Promise<ReadableStream<Uint8Array> | null>;
168
+ getStream?(
169
+ hash: string,
170
+ options?: BlobStoragePartitionOptions
171
+ ): Promise<ReadableStream<Uint8Array> | null>;
155
172
  }
156
173
 
157
174
  // ============================================================================
@@ -88,6 +88,35 @@ export function toTableColumnCodecs(
88
88
  return out;
89
89
  }
90
90
 
91
+ export function createTableColumnCodecsResolver(
92
+ codecSource: ColumnCodecSource | undefined,
93
+ options: { dialect?: ColumnCodecDialect } = {}
94
+ ): (table: string, row: Record<string, unknown>) => TableColumnCodecs {
95
+ const tableCaches = new Map<string, Map<string, TableColumnCodecs>>();
96
+
97
+ return (table, row) => {
98
+ if (!codecSource) return {};
99
+ const columns = Object.keys(row);
100
+ if (columns.length === 0) return {};
101
+
102
+ const cacheKey = columns.slice().sort().join('\u0000');
103
+ let perTableCache = tableCaches.get(table);
104
+ if (!perTableCache) {
105
+ perTableCache = new Map<string, TableColumnCodecs>();
106
+ tableCaches.set(table, perTableCache);
107
+ }
108
+
109
+ const cached = perTableCache.get(cacheKey);
110
+ if (cached) return cached;
111
+
112
+ const resolved = toTableColumnCodecs(table, codecSource, columns, {
113
+ dialect: options.dialect,
114
+ });
115
+ perTableCache.set(cacheKey, resolved);
116
+ return resolved;
117
+ };
118
+ }
119
+
91
120
  export function applyCodecToDbValue(
92
121
  codec: AnyColumnCodec,
93
122
  value: unknown,
@@ -134,16 +163,15 @@ export function applyCodecsFromDbRow(
134
163
  ): Record<string, unknown> {
135
164
  if (!hasCodecs(tableCodecs)) return { ...row };
136
165
 
137
- const transformed: Record<string, unknown> = { ...row };
166
+ let transformed: Record<string, unknown> | null = null;
138
167
  for (const [column, codec] of Object.entries(tableCodecs)) {
139
- if (!(column in transformed)) continue;
140
- transformed[column] = applyCodecFromDbValue(
141
- codec,
142
- transformed[column],
143
- dialect
144
- );
168
+ if (!(column in row)) continue;
169
+ const value = row[column];
170
+ if (value === null || value === undefined) continue;
171
+ transformed ??= { ...row };
172
+ transformed[column] = applyCodecFromDbValue(codec, value, dialect);
145
173
  }
146
- return transformed;
174
+ return transformed ?? row;
147
175
  }
148
176
 
149
177
  function parseBooleanValue(value: unknown): boolean {