@korajs/core 1.0.0-beta.4 → 1.0.0-beta.6

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.
@@ -1,5 +1,5 @@
1
- import { p as KoraEventEmitter } from '../events-DhblJN4r.cjs';
2
- import { S as ScopeMap } from '../build-scope-map-K25AyQXb.cjs';
1
+ import { p as KoraEventEmitter } from '../events-CT9y24ht.cjs';
2
+ import { S as ScopeMap } from '../build-scope-map-Bm6Lqo9F.cjs';
3
3
 
4
4
  /** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
5
5
  interface KoraBindingSyncStatus {
@@ -1,5 +1,5 @@
1
- import { p as KoraEventEmitter } from '../events-DhblJN4r.js';
2
- import { S as ScopeMap } from '../build-scope-map-BvdTzmm4.js';
1
+ import { p as KoraEventEmitter } from '../events-CT9y24ht.js';
2
+ import { S as ScopeMap } from '../build-scope-map-C60lT-4I.js';
3
3
 
4
4
  /** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
5
5
  interface KoraBindingSyncStatus {
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './events-DhblJN4r.cjs';
1
+ import { S as SchemaDefinition } from './events-CT9y24ht.cjs';
2
2
 
3
3
  /**
4
4
  * Per-collection scope map: `{ collectionName: { field: value, ... } }`
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './events-DhblJN4r.js';
1
+ import { S as SchemaDefinition } from './events-CT9y24ht.js';
2
2
 
3
3
  /**
4
4
  * Per-collection scope map: `{ collectionName: { field: value, ... } }`
@@ -912,6 +912,18 @@ type KoraEvent = {
912
912
  */
913
913
  reason: 'lock-conflict' | 'timeout' | 'unsupported';
914
914
  message: string;
915
+ } | {
916
+ /**
917
+ * A preferred storage backend could not provide durable storage, so Kora
918
+ * promoted the app to another durable backend before user code observed the
919
+ * store. This is informational: data still survives reloads.
920
+ */
921
+ type: 'store:storage-fallback';
922
+ dbName: string;
923
+ from: 'opfs' | 'sqlite-wasm';
924
+ to: 'indexeddb';
925
+ reason: 'lock-conflict' | 'timeout' | 'unsupported';
926
+ message: string;
915
927
  } | {
916
928
  /**
917
929
  * Another runtime on this origin was already using this database name, so
@@ -912,6 +912,18 @@ type KoraEvent = {
912
912
  */
913
913
  reason: 'lock-conflict' | 'timeout' | 'unsupported';
914
914
  message: string;
915
+ } | {
916
+ /**
917
+ * A preferred storage backend could not provide durable storage, so Kora
918
+ * promoted the app to another durable backend before user code observed the
919
+ * store. This is informational: data still survives reloads.
920
+ */
921
+ type: 'store:storage-fallback';
922
+ dbName: string;
923
+ from: 'opfs' | 'sqlite-wasm';
924
+ to: 'indexeddb';
925
+ reason: 'lock-conflict' | 'timeout' | 'unsupported';
926
+ message: string;
915
927
  } | {
916
928
  /**
917
929
  * Another runtime on this origin was already using this database name, so
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  StorageError: () => StorageError,
47
47
  SyncError: () => SyncError,
48
48
  advanceVector: () => advanceVector,
49
+ applyAtomicOp: () => applyAtomicOp,
49
50
  applyOperationTransforms: () => applyOperationTransforms,
50
51
  base64ToBytes: () => base64ToBytes,
51
52
  buildScopeMap: () => buildScopeMap,
@@ -95,6 +96,7 @@ __export(index_exports, {
95
96
  migrationStepsToSQL: () => migrationStepsToSQL,
96
97
  op: () => op,
97
98
  quoteIdent: () => quoteIdent,
99
+ replayOperationsForRecord: () => replayOperationsForRecord,
98
100
  resolveAtomicOp: () => resolveAtomicOp,
99
101
  revealSecret: () => revealSecret,
100
102
  rollbackStepsToSQL: () => rollbackStepsToSQL,
@@ -968,32 +970,36 @@ var KORA_ATOMIC_OP = /* @__PURE__ */ Symbol.for("kora.atomic_op");
968
970
  function isAtomicOp(value) {
969
971
  return typeof value === "object" && value !== null && KORA_ATOMIC_OP in value && value[KORA_ATOMIC_OP] === true;
970
972
  }
971
- function resolveAtomicOp(currentValue, sentinel) {
972
- switch (sentinel.type) {
973
+ function applyAtomicOp(currentValue, atomicOp) {
974
+ switch (atomicOp.type) {
973
975
  case "increment": {
974
976
  const current = typeof currentValue === "number" ? currentValue : 0;
975
- const operand = sentinel.value;
977
+ const operand = atomicOp.value;
976
978
  return current + operand;
977
979
  }
978
980
  case "max": {
979
981
  const current = typeof currentValue === "number" ? currentValue : Number.NEGATIVE_INFINITY;
980
- return Math.max(current, sentinel.value);
982
+ return Math.max(current, atomicOp.value);
981
983
  }
982
984
  case "min": {
983
985
  const current = typeof currentValue === "number" ? currentValue : Number.POSITIVE_INFINITY;
984
- return Math.min(current, sentinel.value);
986
+ return Math.min(current, atomicOp.value);
985
987
  }
986
988
  case "append": {
987
989
  const current = Array.isArray(currentValue) ? [...currentValue] : [];
988
- current.push(sentinel.value);
990
+ current.push(atomicOp.value);
989
991
  return current;
990
992
  }
991
993
  case "remove": {
992
994
  if (!Array.isArray(currentValue)) return [];
993
- return currentValue.filter((item) => item !== sentinel.value);
995
+ const target = JSON.stringify(atomicOp.value);
996
+ return currentValue.filter((item) => JSON.stringify(item) !== target);
994
997
  }
995
998
  }
996
999
  }
1000
+ function resolveAtomicOp(currentValue, sentinel) {
1001
+ return applyAtomicOp(currentValue, { type: sentinel.type, value: sentinel.value });
1002
+ }
997
1003
  function createSentinel(type, value) {
998
1004
  return Object.freeze({
999
1005
  [KORA_ATOMIC_OP]: true,
@@ -1061,6 +1067,46 @@ function toAtomicOp(sentinel) {
1061
1067
  return { type: sentinel.type, value: sentinel.value };
1062
1068
  }
1063
1069
 
1070
+ // src/operations/replay-record.ts
1071
+ function replayOperationsForRecord(ops) {
1072
+ let record = null;
1073
+ let deleted = false;
1074
+ const lastWriterKind = {};
1075
+ for (const op2 of ops) {
1076
+ switch (op2.type) {
1077
+ case "insert":
1078
+ if (op2.data) {
1079
+ record = { ...op2.data };
1080
+ deleted = false;
1081
+ for (const field of Object.keys(op2.data)) {
1082
+ lastWriterKind[field] = "lww";
1083
+ }
1084
+ }
1085
+ break;
1086
+ case "update":
1087
+ if (op2.data) {
1088
+ const next = { ...record ?? {} };
1089
+ for (const [field, value] of Object.entries(op2.data)) {
1090
+ const atomicOp = op2.atomicOps?.[field];
1091
+ if (atomicOp && lastWriterKind[field] === `atomic:${atomicOp.type}`) {
1092
+ next[field] = applyAtomicOp(next[field], atomicOp);
1093
+ } else {
1094
+ next[field] = value;
1095
+ }
1096
+ lastWriterKind[field] = atomicOp ? `atomic:${atomicOp.type}` : "lww";
1097
+ }
1098
+ record = next;
1099
+ deleted = false;
1100
+ }
1101
+ break;
1102
+ case "delete":
1103
+ deleted = true;
1104
+ break;
1105
+ }
1106
+ }
1107
+ return deleted ? null : record;
1108
+ }
1109
+
1064
1110
  // src/operations/op-data-binary.ts
1065
1111
  function isKoraBytesValue(value) {
1066
1112
  if (typeof value !== "object" || value === null) {
@@ -2332,10 +2378,17 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
2332
2378
  break;
2333
2379
  }
2334
2380
  case "json": {
2335
- if (!isJsonSerializable(value)) {
2381
+ const nonJson = findNonJsonSerializable(value, fieldName);
2382
+ if (nonJson) {
2336
2383
  throw new SchemaValidationError(
2337
- `Field "${fieldName}" in collection "${collection}" must be JSON-serializable, got ${describeType(value)}`,
2338
- { collection, field: fieldName, expectedType: "json", receivedType: describeType(value) }
2384
+ `Field "${fieldName}" in collection "${collection}" must be JSON-serializable; found ${nonJson.receivedType} at "${nonJson.path}"`,
2385
+ {
2386
+ collection,
2387
+ field: fieldName,
2388
+ expectedType: "json",
2389
+ receivedType: nonJson.receivedType,
2390
+ path: nonJson.path
2391
+ }
2339
2392
  );
2340
2393
  }
2341
2394
  break;
@@ -2372,21 +2425,41 @@ function isPlainObject(value) {
2372
2425
  const proto = Object.getPrototypeOf(value);
2373
2426
  return proto === null || proto === Object.prototype;
2374
2427
  }
2375
- function isJsonSerializable(value) {
2428
+ function findNonJsonSerializable(value, path, seen = /* @__PURE__ */ new WeakSet()) {
2376
2429
  if (value === null) {
2377
- return true;
2430
+ return null;
2378
2431
  }
2379
2432
  const type = typeof value;
2380
2433
  if (type === "string" || type === "number" || type === "boolean") {
2381
- return Number.isFinite(value) || type !== "number";
2434
+ if (type === "number" && !Number.isFinite(value)) {
2435
+ return { path, receivedType: describeType(value) };
2436
+ }
2437
+ return null;
2438
+ }
2439
+ if (type === "undefined" || type === "function" || type === "symbol" || type === "bigint") {
2440
+ return { path, receivedType: describeType(value) };
2382
2441
  }
2383
2442
  if (Array.isArray(value)) {
2384
- return value.every(isJsonSerializable);
2443
+ for (let index = 0; index < value.length; index++) {
2444
+ const invalid = findNonJsonSerializable(value[index], `${path}[${index}]`, seen);
2445
+ if (invalid) return invalid;
2446
+ }
2447
+ return null;
2385
2448
  }
2386
2449
  if (type === "object") {
2387
- return Object.values(value).every(isJsonSerializable);
2450
+ const objectValue = value;
2451
+ if (seen.has(objectValue)) {
2452
+ return { path, receivedType: "circular object" };
2453
+ }
2454
+ seen.add(objectValue);
2455
+ for (const [key, nestedValue] of Object.entries(objectValue)) {
2456
+ const invalid = findNonJsonSerializable(nestedValue, `${path}.${key}`, seen);
2457
+ if (invalid) return invalid;
2458
+ }
2459
+ seen.delete(objectValue);
2460
+ return null;
2388
2461
  }
2389
- return false;
2462
+ return { path, receivedType: describeType(value) };
2390
2463
  }
2391
2464
  function describeType(value) {
2392
2465
  if (value === null) {
@@ -3283,6 +3356,7 @@ function generateProtoDefinitions(schema) {
3283
3356
  StorageError,
3284
3357
  SyncError,
3285
3358
  advanceVector,
3359
+ applyAtomicOp,
3286
3360
  applyOperationTransforms,
3287
3361
  base64ToBytes,
3288
3362
  buildScopeMap,
@@ -3332,6 +3406,7 @@ function generateProtoDefinitions(schema) {
3332
3406
  migrationStepsToSQL,
3333
3407
  op,
3334
3408
  quoteIdent,
3409
+ replayOperationsForRecord,
3335
3410
  resolveAtomicOp,
3336
3411
  revealSecret,
3337
3412
  rollbackStepsToSQL,