@korajs/core 0.6.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,10 +2,13 @@ import {
2
2
  AppNotReadyError,
3
3
  ClockDriftError,
4
4
  HybridLogicalClock,
5
+ InvalidTimestampError,
5
6
  KORA_ERROR_FIX_SUGGESTIONS,
6
7
  KoraError,
8
+ MAX_LOGICAL,
7
9
  MergeConflictError,
8
10
  OperationError,
11
+ RemoteClockDriftError,
9
12
  SchemaValidationError,
10
13
  StorageError,
11
14
  SyncError,
@@ -14,7 +17,7 @@ import {
14
17
  isValidOperation,
15
18
  topologicalSort,
16
19
  verifyOperationIntegrity
17
- } from "./chunk-3VIOZT7D.js";
20
+ } from "./chunk-5IICSH6H.js";
18
21
 
19
22
  // src/types.ts
20
23
  var MERGE_STRATEGIES = [
@@ -106,6 +109,226 @@ function formatUUID(bytes) {
106
109
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
107
110
  }
108
111
 
112
+ // src/blob/blob-ref.ts
113
+ async function hashBlob(bytes) {
114
+ const view = new Uint8Array(bytes.byteLength);
115
+ view.set(bytes);
116
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", view);
117
+ const out = new Uint8Array(digest);
118
+ return Array.from(out, (b) => b.toString(16).padStart(2, "0")).join("");
119
+ }
120
+ async function createBlobRef(bytes, metadata = {}) {
121
+ const hash = await hashBlob(bytes);
122
+ const ref = { hash, size: bytes.byteLength };
123
+ if (metadata.mimeType !== void 0) {
124
+ ref.mimeType = metadata.mimeType;
125
+ }
126
+ if (metadata.filename !== void 0) {
127
+ ref.filename = metadata.filename;
128
+ }
129
+ return ref;
130
+ }
131
+ function isBlobRef(value) {
132
+ if (typeof value !== "object" || value === null) {
133
+ return false;
134
+ }
135
+ const ref = value;
136
+ if (typeof ref.hash !== "string" || !/^[0-9a-f]{64}$/.test(ref.hash)) {
137
+ return false;
138
+ }
139
+ if (typeof ref.size !== "number" || !Number.isInteger(ref.size) || ref.size < 0) {
140
+ return false;
141
+ }
142
+ if (ref.mimeType !== void 0 && typeof ref.mimeType !== "string") {
143
+ return false;
144
+ }
145
+ if (ref.filename !== void 0 && typeof ref.filename !== "string") {
146
+ return false;
147
+ }
148
+ if (ref.manifestHash !== void 0 && (typeof ref.manifestHash !== "string" || !/^[0-9a-f]{64}$/.test(ref.manifestHash))) {
149
+ return false;
150
+ }
151
+ return true;
152
+ }
153
+
154
+ // src/secret/secret-crypto.ts
155
+ var PBKDF2_ITERATIONS = 21e4;
156
+ var SALT_BYTES = 16;
157
+ var IV_BYTES = 12;
158
+ var HASH_BYTES = 32;
159
+ function toHex(bytes) {
160
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
161
+ }
162
+ function fromHex(hex) {
163
+ if (hex.length % 2 !== 0 || /[^0-9a-f]/i.test(hex)) {
164
+ throw new Error("Invalid hex string");
165
+ }
166
+ const out = new Uint8Array(hex.length / 2);
167
+ for (let i = 0; i < out.length; i++) {
168
+ out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
169
+ }
170
+ return out;
171
+ }
172
+ function randomBytes(n) {
173
+ const out = new Uint8Array(n);
174
+ globalThis.crypto.getRandomValues(out);
175
+ return out;
176
+ }
177
+ function timingSafeEqual(a, b) {
178
+ if (a.length !== b.length) {
179
+ return false;
180
+ }
181
+ let diff = 0;
182
+ for (let i = 0; i < a.length; i++) {
183
+ diff |= a[i] ^ b[i];
184
+ }
185
+ return diff === 0;
186
+ }
187
+ async function deriveAesKey(secretKey, salt) {
188
+ const baseKey = await globalThis.crypto.subtle.importKey(
189
+ "raw",
190
+ new TextEncoder().encode(secretKey),
191
+ "PBKDF2",
192
+ false,
193
+ ["deriveKey"]
194
+ );
195
+ return globalThis.crypto.subtle.deriveKey(
196
+ { name: "PBKDF2", salt, iterations: PBKDF2_ITERATIONS, hash: "SHA-256" },
197
+ baseKey,
198
+ { name: "AES-GCM", length: 256 },
199
+ false,
200
+ ["encrypt", "decrypt"]
201
+ );
202
+ }
203
+ async function encryptSecret(plaintext, secretKey) {
204
+ const salt = randomBytes(SALT_BYTES);
205
+ const iv = randomBytes(IV_BYTES);
206
+ const key = await deriveAesKey(secretKey, salt);
207
+ const ciphertext = await globalThis.crypto.subtle.encrypt(
208
+ { name: "AES-GCM", iv },
209
+ key,
210
+ new TextEncoder().encode(plaintext)
211
+ );
212
+ return `v1.${toHex(salt)}.${toHex(iv)}.${toHex(new Uint8Array(ciphertext))}`;
213
+ }
214
+ async function decryptSecret(encrypted, secretKey) {
215
+ const parts = encrypted.split(".");
216
+ if (parts.length !== 4 || parts[0] !== "v1") {
217
+ throw new Error("Invalid encrypted secret format");
218
+ }
219
+ const salt = fromHex(parts[1]);
220
+ const iv = fromHex(parts[2]);
221
+ const ciphertext = fromHex(parts[3]);
222
+ const key = await deriveAesKey(secretKey, salt);
223
+ const plaintext = await globalThis.crypto.subtle.decrypt(
224
+ { name: "AES-GCM", iv },
225
+ key,
226
+ ciphertext
227
+ );
228
+ return new TextDecoder().decode(plaintext);
229
+ }
230
+ async function hashSecret(plaintext) {
231
+ const salt = randomBytes(SALT_BYTES);
232
+ const hash = await pbkdf2(plaintext, salt);
233
+ return `v1.${toHex(salt)}.${toHex(hash)}`;
234
+ }
235
+ async function verifySecret(plaintext, stored) {
236
+ const parts = stored.split(".");
237
+ if (parts.length !== 3 || parts[0] !== "v1") {
238
+ return false;
239
+ }
240
+ let salt;
241
+ let expected;
242
+ try {
243
+ salt = fromHex(parts[1]);
244
+ expected = fromHex(parts[2]);
245
+ } catch {
246
+ return false;
247
+ }
248
+ const actual = await pbkdf2(plaintext, salt);
249
+ return timingSafeEqual(actual, expected);
250
+ }
251
+ async function pbkdf2(plaintext, salt) {
252
+ const baseKey = await globalThis.crypto.subtle.importKey(
253
+ "raw",
254
+ new TextEncoder().encode(plaintext),
255
+ "PBKDF2",
256
+ false,
257
+ ["deriveBits"]
258
+ );
259
+ const bits = await globalThis.crypto.subtle.deriveBits(
260
+ { name: "PBKDF2", salt, iterations: PBKDF2_ITERATIONS, hash: "SHA-256" },
261
+ baseKey,
262
+ HASH_BYTES * 8
263
+ );
264
+ return new Uint8Array(bits);
265
+ }
266
+
267
+ // src/secret/secret-transform.ts
268
+ var MissingSecretKeyError = class extends Error {
269
+ constructor(field) {
270
+ super(
271
+ `Secret field "${field}" is encrypted but no encryption key is configured. Provide sync.encryption.key (or a key provider) to createApp.`
272
+ );
273
+ this.field = field;
274
+ this.name = "MissingSecretKeyError";
275
+ }
276
+ field;
277
+ };
278
+ async function resolveKey(provider) {
279
+ if (!provider) {
280
+ return void 0;
281
+ }
282
+ return provider();
283
+ }
284
+ async function transformSecretFieldsForWrite(data, collectionDef, keyProvider) {
285
+ const hasSecret = Object.values(collectionDef.fields).some((f) => f.kind === "secret");
286
+ if (!hasSecret) {
287
+ return data;
288
+ }
289
+ const result = { ...data };
290
+ let key;
291
+ let keyResolved = false;
292
+ for (const [field, value] of Object.entries(data)) {
293
+ const descriptor = collectionDef.fields[field];
294
+ if (descriptor?.kind !== "secret") {
295
+ continue;
296
+ }
297
+ if (typeof value !== "string") {
298
+ continue;
299
+ }
300
+ const mode = descriptor.secretMode ?? "encrypted";
301
+ if (mode === "hashed") {
302
+ result[field] = await hashSecret(value);
303
+ continue;
304
+ }
305
+ if (!keyResolved) {
306
+ key = await resolveKey(keyProvider);
307
+ keyResolved = true;
308
+ }
309
+ if (key === void 0) {
310
+ throw new MissingSecretKeyError(field);
311
+ }
312
+ result[field] = await encryptSecret(value, key);
313
+ }
314
+ return result;
315
+ }
316
+ async function revealSecret(storedValue, mode, keyProvider, field = "secret") {
317
+ if (mode === "hashed") {
318
+ throw new Error(
319
+ `Secret field "${field}" is hashed (one-way) and cannot be revealed. Use verifySecretValue to check a candidate.`
320
+ );
321
+ }
322
+ const key = await resolveKey(keyProvider);
323
+ if (key === void 0) {
324
+ throw new MissingSecretKeyError(field);
325
+ }
326
+ return decryptSecret(storedValue, key);
327
+ }
328
+ async function verifySecretValue(candidate, storedValue) {
329
+ return verifySecret(candidate, storedValue);
330
+ }
331
+
109
332
  // src/operations/apply-result.ts
110
333
  var APPLY_RESULTS = ["applied", "duplicate", "skipped", "rejected", "deferred"];
111
334
  var APPLY_FAILURE_CODES = {
@@ -235,6 +458,106 @@ function toAtomicOp(sentinel) {
235
458
  return { type: sentinel.type, value: sentinel.value };
236
459
  }
237
460
 
461
+ // src/operations/op-data-binary.ts
462
+ function isKoraBytesValue(value) {
463
+ if (typeof value !== "object" || value === null) {
464
+ return false;
465
+ }
466
+ const record = value;
467
+ return Object.keys(record).length === 1 && typeof record.$koraBytes === "string";
468
+ }
469
+ function isLegacyNumericByteObject(value) {
470
+ if (typeof value !== "object" || value === null || ArrayBuffer.isView(value)) {
471
+ return false;
472
+ }
473
+ const record = value;
474
+ const keys = Object.keys(record);
475
+ if (keys.length === 0) {
476
+ return false;
477
+ }
478
+ for (let i = 0; i < keys.length; i++) {
479
+ const byte = record[String(i)];
480
+ if (typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255) {
481
+ return false;
482
+ }
483
+ }
484
+ return true;
485
+ }
486
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
487
+ var BASE64_REVERSE = new Map(
488
+ Array.from(BASE64_ALPHABET, (char, index) => [char, index])
489
+ );
490
+ function bytesToBase64(bytes) {
491
+ let out = "";
492
+ for (let i = 0; i < bytes.length; i += 3) {
493
+ const b0 = bytes[i] ?? 0;
494
+ const b1 = bytes[i + 1] ?? 0;
495
+ const b2 = bytes[i + 2] ?? 0;
496
+ const triple = b0 << 16 | b1 << 8 | b2;
497
+ out += BASE64_ALPHABET[triple >> 18 & 63] ?? "";
498
+ out += BASE64_ALPHABET[triple >> 12 & 63] ?? "";
499
+ out += i + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] ?? "" : "=";
500
+ out += i + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] ?? "" : "=";
501
+ }
502
+ return out;
503
+ }
504
+ function base64ToBytes(base64) {
505
+ const cleaned = base64.replace(/=+$/, "");
506
+ const out = new Uint8Array(Math.floor(cleaned.length * 6 / 8));
507
+ let buffer = 0;
508
+ let bits = 0;
509
+ let index = 0;
510
+ for (const char of cleaned) {
511
+ const value = BASE64_REVERSE.get(char);
512
+ if (value === void 0) {
513
+ throw new OperationError(`Invalid base64 character "${char}" in tagged binary value.`, {
514
+ char
515
+ });
516
+ }
517
+ buffer = buffer << 6 | value;
518
+ bits += 6;
519
+ if (bits >= 8) {
520
+ bits -= 8;
521
+ out[index] = buffer >> bits & 255;
522
+ index += 1;
523
+ }
524
+ }
525
+ return out;
526
+ }
527
+ function encodeBytesForOpData(value) {
528
+ if (typeof value === "string") {
529
+ return value;
530
+ }
531
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
532
+ return { $koraBytes: bytesToBase64(bytes) };
533
+ }
534
+ function decodeBytesFromOpData(value) {
535
+ if (typeof value === "string") {
536
+ return value;
537
+ }
538
+ if (value instanceof Uint8Array) {
539
+ return value;
540
+ }
541
+ if (value instanceof ArrayBuffer) {
542
+ return new Uint8Array(value);
543
+ }
544
+ if (isKoraBytesValue(value)) {
545
+ return base64ToBytes(value.$koraBytes);
546
+ }
547
+ if (isLegacyNumericByteObject(value)) {
548
+ const keys = Object.keys(value);
549
+ const bytes = new Uint8Array(keys.length);
550
+ for (let i = 0; i < keys.length; i++) {
551
+ bytes[i] = value[String(i)] ?? 0;
552
+ }
553
+ return bytes;
554
+ }
555
+ throw new OperationError(
556
+ "Binary op-data value must be a string, Uint8Array, ArrayBuffer, tagged { $koraBytes } object, or legacy numeric-key byte object.",
557
+ { receivedType: typeof value }
558
+ );
559
+ }
560
+
238
561
  // src/scopes/sync-scope-bindings.ts
239
562
  function hasSchemaSyncRules(schema) {
240
563
  return schema.sync !== void 0 && Object.keys(schema.sync).length > 0;
@@ -664,6 +987,7 @@ function generateSQL(collectionName, collection, relations) {
664
987
  columns.push("_created_at INTEGER NOT NULL");
665
988
  columns.push("_updated_at INTEGER NOT NULL");
666
989
  columns.push("_version TEXT NOT NULL DEFAULT ''");
990
+ columns.push("_field_versions TEXT NOT NULL DEFAULT '{}'");
667
991
  columns.push("_deleted INTEGER NOT NULL DEFAULT 0");
668
992
  statements.push(`CREATE TABLE IF NOT EXISTS ${collectionName} (
669
993
  ${columns.join(",\n ")}
@@ -676,6 +1000,10 @@ ALTER TABLE ${collectionName} ADD COLUMN ${colDef}`);
676
1000
  statements.push(
677
1001
  `--kora:safe-alter
678
1002
  ALTER TABLE ${collectionName} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
1003
+ );
1004
+ statements.push(
1005
+ `--kora:safe-alter
1006
+ ALTER TABLE ${collectionName} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '{}'`
679
1007
  );
680
1008
  for (const indexField of collection.indexes) {
681
1009
  statements.push(
@@ -703,6 +1031,9 @@ ALTER TABLE ${collectionName} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
703
1031
  schema_version INTEGER NOT NULL
704
1032
  )`
705
1033
  );
1034
+ statements.push(
1035
+ `CREATE INDEX IF NOT EXISTS idx_kora_ops_${collectionName}_record_id ON _kora_ops_${collectionName} (record_id)`
1036
+ );
706
1037
  return statements;
707
1038
  }
708
1039
  function generateFullDDL(schema) {
@@ -763,6 +1094,17 @@ function mapFieldType(descriptor) {
763
1094
  case "array":
764
1095
  return "TEXT";
765
1096
  // JSON-serialized
1097
+ case "object":
1098
+ return "TEXT";
1099
+ // JSON-serialized structured object
1100
+ case "json":
1101
+ return "TEXT";
1102
+ // JSON-serialized dynamic-key value
1103
+ case "blob":
1104
+ return "TEXT";
1105
+ // JSON-serialized content-addressed BlobRef (bytes stored out of band)
1106
+ case "secret":
1107
+ return "TEXT";
766
1108
  case "richtext":
767
1109
  return "BLOB";
768
1110
  }
@@ -996,6 +1338,160 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
996
1338
  };
997
1339
  }
998
1340
  };
1341
+ var ObjectFieldBuilder = class _ObjectFieldBuilder extends FieldBuilder {
1342
+ _fields;
1343
+ constructor(fields, required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
1344
+ super("object", required, defaultValue, auto, mergeStrategy);
1345
+ this._fields = fields;
1346
+ }
1347
+ optional() {
1348
+ return new _ObjectFieldBuilder(
1349
+ this._fields,
1350
+ false,
1351
+ this._defaultValue,
1352
+ this._auto,
1353
+ this._mergeStrategy
1354
+ );
1355
+ }
1356
+ default(value) {
1357
+ return new _ObjectFieldBuilder(this._fields, false, value, this._auto, this._mergeStrategy);
1358
+ }
1359
+ auto() {
1360
+ return new _ObjectFieldBuilder(this._fields, false, void 0, true, this._mergeStrategy);
1361
+ }
1362
+ merge(strategy) {
1363
+ return new _ObjectFieldBuilder(
1364
+ this._fields,
1365
+ this._required,
1366
+ this._defaultValue,
1367
+ this._auto,
1368
+ strategy
1369
+ );
1370
+ }
1371
+ _build() {
1372
+ const nestedFields = {};
1373
+ for (const [key, builder] of Object.entries(this._fields)) {
1374
+ nestedFields[key] = builder._build();
1375
+ }
1376
+ return {
1377
+ kind: "object",
1378
+ required: this._required,
1379
+ defaultValue: this._defaultValue,
1380
+ auto: this._auto,
1381
+ enumValues: null,
1382
+ itemKind: null,
1383
+ mergeStrategy: this._mergeStrategy,
1384
+ transitions: null,
1385
+ nestedFields
1386
+ };
1387
+ }
1388
+ };
1389
+ var JsonFieldBuilder = class _JsonFieldBuilder extends FieldBuilder {
1390
+ optional() {
1391
+ return new _JsonFieldBuilder(
1392
+ "json",
1393
+ false,
1394
+ this._defaultValue,
1395
+ this._auto,
1396
+ this._mergeStrategy
1397
+ );
1398
+ }
1399
+ default(value) {
1400
+ return new _JsonFieldBuilder(
1401
+ "json",
1402
+ false,
1403
+ value,
1404
+ this._auto,
1405
+ this._mergeStrategy
1406
+ );
1407
+ }
1408
+ auto() {
1409
+ return new _JsonFieldBuilder("json", false, void 0, true, this._mergeStrategy);
1410
+ }
1411
+ merge(strategy) {
1412
+ return new _JsonFieldBuilder(
1413
+ "json",
1414
+ this._required,
1415
+ this._defaultValue,
1416
+ this._auto,
1417
+ strategy
1418
+ );
1419
+ }
1420
+ _build() {
1421
+ return {
1422
+ kind: "json",
1423
+ required: this._required,
1424
+ defaultValue: this._defaultValue,
1425
+ auto: this._auto,
1426
+ enumValues: null,
1427
+ itemKind: null,
1428
+ mergeStrategy: this._mergeStrategy,
1429
+ transitions: null,
1430
+ nestedFields: null
1431
+ };
1432
+ }
1433
+ };
1434
+ var SecretFieldBuilder = class _SecretFieldBuilder extends FieldBuilder {
1435
+ _secretMode;
1436
+ constructor(secretMode = "encrypted", required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
1437
+ super("secret", required, defaultValue, auto, mergeStrategy);
1438
+ this._secretMode = secretMode;
1439
+ }
1440
+ /** Store this secret as a one-way salted hash (passwords: verify, never read back). */
1441
+ hashed() {
1442
+ return new _SecretFieldBuilder(
1443
+ "hashed",
1444
+ this._required,
1445
+ this._defaultValue,
1446
+ this._auto,
1447
+ this._mergeStrategy
1448
+ );
1449
+ }
1450
+ /** Store this secret as reversible ciphertext (tokens/keys: decrypt to use). */
1451
+ encrypted() {
1452
+ return new _SecretFieldBuilder(
1453
+ "encrypted",
1454
+ this._required,
1455
+ this._defaultValue,
1456
+ this._auto,
1457
+ this._mergeStrategy
1458
+ );
1459
+ }
1460
+ optional() {
1461
+ return new _SecretFieldBuilder(
1462
+ this._secretMode,
1463
+ false,
1464
+ this._defaultValue,
1465
+ this._auto,
1466
+ this._mergeStrategy
1467
+ );
1468
+ }
1469
+ auto() {
1470
+ return new _SecretFieldBuilder(this._secretMode, false, void 0, true, this._mergeStrategy);
1471
+ }
1472
+ merge(strategy) {
1473
+ return new _SecretFieldBuilder(
1474
+ this._secretMode,
1475
+ this._required,
1476
+ this._defaultValue,
1477
+ this._auto,
1478
+ strategy
1479
+ );
1480
+ }
1481
+ _build() {
1482
+ return {
1483
+ kind: "secret",
1484
+ required: this._required,
1485
+ defaultValue: this._defaultValue,
1486
+ auto: this._auto,
1487
+ enumValues: null,
1488
+ itemKind: null,
1489
+ mergeStrategy: this._mergeStrategy,
1490
+ transitions: null,
1491
+ secretMode: this._secretMode
1492
+ };
1493
+ }
1494
+ };
999
1495
  var t = {
1000
1496
  string() {
1001
1497
  return new FieldBuilder("string", true, void 0, false);
@@ -1017,6 +1513,18 @@ var t = {
1017
1513
  },
1018
1514
  array(itemBuilder) {
1019
1515
  return new ArrayFieldBuilder(itemBuilder, true, void 0, false);
1516
+ },
1517
+ object(fields) {
1518
+ return new ObjectFieldBuilder(fields, true, void 0, false);
1519
+ },
1520
+ json() {
1521
+ return new JsonFieldBuilder("json", true, void 0, false);
1522
+ },
1523
+ blob() {
1524
+ return new FieldBuilder("blob", true, void 0, false);
1525
+ },
1526
+ secret() {
1527
+ return new SecretFieldBuilder("encrypted", true, void 0, false);
1020
1528
  }
1021
1529
  };
1022
1530
 
@@ -1168,9 +1676,9 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1168
1676
  break;
1169
1677
  }
1170
1678
  case "richtext": {
1171
- if (!(value instanceof Uint8Array) && typeof value !== "string") {
1679
+ if (!(value instanceof Uint8Array) && !(value instanceof ArrayBuffer) && typeof value !== "string") {
1172
1680
  throw new SchemaValidationError(
1173
- `Field "${fieldName}" in collection "${collection}" must be a Uint8Array or string for richtext, got ${typeof value}`,
1681
+ `Field "${fieldName}" in collection "${collection}" must be a Uint8Array, ArrayBuffer, or string for richtext, got ${typeof value}`,
1174
1682
  {
1175
1683
  collection,
1176
1684
  field: fieldName,
@@ -1181,7 +1689,98 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1181
1689
  }
1182
1690
  break;
1183
1691
  }
1692
+ case "object": {
1693
+ if (!isPlainObject(value)) {
1694
+ throw new SchemaValidationError(
1695
+ `Field "${fieldName}" in collection "${collection}" must be a plain object, got ${describeType(value)}`,
1696
+ {
1697
+ collection,
1698
+ field: fieldName,
1699
+ expectedType: "object",
1700
+ receivedType: describeType(value)
1701
+ }
1702
+ );
1703
+ }
1704
+ if (descriptor.nestedFields) {
1705
+ for (const [nestedName, nestedDescriptor] of Object.entries(descriptor.nestedFields)) {
1706
+ const nestedValue = value[nestedName];
1707
+ if (nestedValue !== void 0 && nestedValue !== null) {
1708
+ validateFieldValue(
1709
+ collection,
1710
+ `${fieldName}.${nestedName}`,
1711
+ nestedDescriptor,
1712
+ nestedValue
1713
+ );
1714
+ }
1715
+ }
1716
+ }
1717
+ break;
1718
+ }
1719
+ case "json": {
1720
+ if (!isJsonSerializable(value)) {
1721
+ throw new SchemaValidationError(
1722
+ `Field "${fieldName}" in collection "${collection}" must be JSON-serializable, got ${describeType(value)}`,
1723
+ { collection, field: fieldName, expectedType: "json", receivedType: describeType(value) }
1724
+ );
1725
+ }
1726
+ break;
1727
+ }
1728
+ case "blob": {
1729
+ if (!isBlobRef(value)) {
1730
+ throw new SchemaValidationError(
1731
+ `Field "${fieldName}" in collection "${collection}" must be a BlobRef (from the blob store), got ${describeType(value)}`,
1732
+ { collection, field: fieldName, expectedType: "blob", receivedType: describeType(value) }
1733
+ );
1734
+ }
1735
+ break;
1736
+ }
1737
+ case "secret": {
1738
+ if (typeof value !== "string") {
1739
+ throw new SchemaValidationError(
1740
+ `Field "${fieldName}" in collection "${collection}" must be a string, got ${describeType(value)}`,
1741
+ {
1742
+ collection,
1743
+ field: fieldName,
1744
+ expectedType: "secret",
1745
+ receivedType: describeType(value)
1746
+ }
1747
+ );
1748
+ }
1749
+ break;
1750
+ }
1751
+ }
1752
+ }
1753
+ function isPlainObject(value) {
1754
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1755
+ return false;
1184
1756
  }
1757
+ const proto = Object.getPrototypeOf(value);
1758
+ return proto === null || proto === Object.prototype;
1759
+ }
1760
+ function isJsonSerializable(value) {
1761
+ if (value === null) {
1762
+ return true;
1763
+ }
1764
+ const type = typeof value;
1765
+ if (type === "string" || type === "number" || type === "boolean") {
1766
+ return Number.isFinite(value) || type !== "number";
1767
+ }
1768
+ if (Array.isArray(value)) {
1769
+ return value.every(isJsonSerializable);
1770
+ }
1771
+ if (type === "object") {
1772
+ return Object.values(value).every(isJsonSerializable);
1773
+ }
1774
+ return false;
1775
+ }
1776
+ function describeType(value) {
1777
+ if (value === null) {
1778
+ return "null";
1779
+ }
1780
+ if (Array.isArray(value)) {
1781
+ return "array";
1782
+ }
1783
+ return typeof value;
1185
1784
  }
1186
1785
  function jsTypeForKind(kind) {
1187
1786
  switch (kind) {
@@ -1662,6 +2261,14 @@ function mapFieldType2(descriptor) {
1662
2261
  return "INTEGER";
1663
2262
  case "array":
1664
2263
  return "TEXT";
2264
+ case "object":
2265
+ return "TEXT";
2266
+ case "json":
2267
+ return "TEXT";
2268
+ case "blob":
2269
+ return "TEXT";
2270
+ case "secret":
2271
+ return "TEXT";
1665
2272
  case "richtext":
1666
2273
  return "BLOB";
1667
2274
  }
@@ -1680,7 +2287,14 @@ var SCALAR_TYPE_MAP = {
1680
2287
  number: "double",
1681
2288
  boolean: "bool",
1682
2289
  timestamp: "int64",
1683
- richtext: "bytes"
2290
+ richtext: "bytes",
2291
+ // object/json values travel as JSON-serialized strings on the proto wire.
2292
+ object: "string",
2293
+ json: "string",
2294
+ // A blob field carries a JSON-serialized content-addressed reference, not bytes.
2295
+ blob: "string",
2296
+ // A secret field's stored value is a JSON string (ciphertext or hash).
2297
+ secret: "string"
1684
2298
  };
1685
2299
  var ARRAY_ITEM_TYPE_MAP = {
1686
2300
  string: "string",
@@ -1915,33 +2529,44 @@ export {
1915
2529
  EnumFieldBuilder,
1916
2530
  FieldBuilder,
1917
2531
  HybridLogicalClock,
2532
+ InvalidTimestampError,
1918
2533
  KORA_ERROR_FIX_SUGGESTIONS,
1919
2534
  KoraError,
2535
+ MAX_LOGICAL,
1920
2536
  MERGE_STRATEGIES,
1921
2537
  MergeConflictError,
1922
2538
  MigrationBuilder,
1923
2539
  MigrationRollbackError,
2540
+ MissingSecretKeyError,
1924
2541
  OperationError,
2542
+ RemoteClockDriftError,
1925
2543
  RollbackBuilder,
1926
2544
  SchemaValidationError,
1927
2545
  StorageError,
1928
2546
  SyncError,
1929
2547
  advanceVector,
1930
2548
  applyOperationTransforms,
2549
+ base64ToBytes,
1931
2550
  buildScopeMap,
1932
2551
  buildStateMachineConstraints,
2552
+ bytesToBase64,
1933
2553
  canAutoRollback,
1934
2554
  collectSchemaScopeFields,
1935
2555
  collectSchemaScopeValueKeys,
1936
2556
  computeDelta,
2557
+ createBlobRef,
1937
2558
  createOperation,
1938
2559
  createReversibleMigration,
1939
2560
  createVersionVector,
2561
+ decodeBytesFromOpData,
2562
+ decryptSecret,
1940
2563
  defaultApplyFailureReason,
1941
2564
  defaultSequenceFormat,
1942
2565
  defineSchema,
1943
2566
  deserializeVector,
1944
2567
  dominates,
2568
+ encodeBytesForOpData,
2569
+ encryptSecret,
1945
2570
  extractScopeValuesFromClaims,
1946
2571
  extractTimestamp,
1947
2572
  formatSequenceValue,
@@ -1954,9 +2579,14 @@ export {
1954
2579
  getKoraErrorFix,
1955
2580
  getTransitionMap,
1956
2581
  hasSchemaSyncRules,
2582
+ hashBlob,
2583
+ hashSecret,
1957
2584
  isApplyFailure,
1958
2585
  isAtomicOp,
2586
+ isBlobRef,
1959
2587
  isCollectionSyncScoped,
2588
+ isKoraBytesValue,
2589
+ isLegacyNumericByteObject,
1960
2590
  isValidOperation,
1961
2591
  isValidUUIDv7,
1962
2592
  mergeVectors,
@@ -1964,13 +2594,17 @@ export {
1964
2594
  migrationStepsToSQL,
1965
2595
  op,
1966
2596
  resolveAtomicOp,
2597
+ revealSecret,
1967
2598
  rollbackStepsToSQL,
1968
2599
  serializeVector,
1969
2600
  t,
1970
2601
  toAtomicOp,
2602
+ transformSecretFieldsForWrite,
1971
2603
  validateRecord,
1972
2604
  validateTransition,
1973
2605
  vectorsEqual,
1974
- verifyOperationIntegrity
2606
+ verifyOperationIntegrity,
2607
+ verifySecret,
2608
+ verifySecretValue
1975
2609
  };
1976
2610
  //# sourceMappingURL=index.js.map