memorio 4.9.8 → 4.9.10

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 (4) hide show
  1. package/README.md +18 -2
  2. package/index.cjs +58 -32
  3. package/index.js +58 -32
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -285,6 +285,18 @@ memorio.registerSchema('user', {
285
285
  state.user = { name: 'Sara' } // rejected — missing required "email"
286
286
  ```
287
287
 
288
+ Array item validation is supported per-element with the `items` schema field:
289
+
290
+ ```ts
291
+ memorio.registerSchema('tags', {
292
+ type: 'array',
293
+ items: { type: 'string' },
294
+ })
295
+
296
+ state.tags = ['a', 'b'] // accepted
297
+ state.tags = [1, 2, 3] // rejected — items must be strings
298
+ ```
299
+
288
300
  ## Local-first sync
289
301
 
290
302
  The local application owns its data; the cloud is optional transport.
@@ -302,7 +314,9 @@ memorio.memory.configure({
302
314
  })
303
315
  ```
304
316
 
305
- Conflict resolution defaults to *higher confidence wins, then more recent `lastConfirmedAt`* — override it when you need a different rule:
317
+ When `replay()` pulls remote operations, entries are ordered causally by HLC timestamp. For
318
+ *concurrent* writes to the same key (an HLC tie) the **remote entry wins by default**; provide a
319
+ `resolveConflict` to override:
306
320
 
307
321
  ```ts
308
322
  memorio.memory.configure({
@@ -313,7 +327,9 @@ memorio.memory.configure({
313
327
  })
314
328
  ```
315
329
 
316
- This resolver only settles *client-side* divergence between what memorio has seen locally — the provider is still responsible for the final server-side policy.
330
+ When no resolver is supplied, Memorio logs a warn (dev only) and applies the remote entry on a
331
+ tie. The resolver only settles *client-side* divergence between what memorio has seen locally —
332
+ the provider is still responsible for the final server-side policy.
317
333
 
318
334
  ## Cross-platform support
319
335
 
package/index.cjs CHANGED
@@ -1820,6 +1820,14 @@ var _validateValue = /* @__PURE__ */ __name((value, schema2, path) => {
1820
1820
  }
1821
1821
  }
1822
1822
  }
1823
+ if (schema2.type === "array" && Array.isArray(value) && schema2.items) {
1824
+ for (let i = 0; i < value.length; i++) {
1825
+ const itemResult = _validateValue(value[i], schema2.items, `${path}[${i}]`);
1826
+ if (!itemResult.valid) {
1827
+ errors.push(...itemResult.errors || []);
1828
+ }
1829
+ }
1830
+ }
1823
1831
  return errors.length === 0 ? { valid: true } : { valid: false, errors };
1824
1832
  }, "_validateValue");
1825
1833
  var validate = /* @__PURE__ */ __name((path, value) => {
@@ -1880,7 +1888,8 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
1880
1888
  const event = /* @__PURE__ */ __name((name) => {
1881
1889
  try {
1882
1890
  dispatch.set(name, { detail: { name } });
1883
- } catch (_) {
1891
+ } catch (err) {
1892
+ if (exports.DEV) console.warn("[memorio state] event dispatch failed:", err);
1884
1893
  }
1885
1894
  return;
1886
1895
  }, "event");
@@ -1899,7 +1908,7 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
1899
1908
  }
1900
1909
  if (prop === "list") {
1901
1910
  message_default('Some state could be hidden when you use "state.list". Use "state" to see the complete list ');
1902
- return JSON.parse(JSON.stringify(target));
1911
+ return deepRaw(target, /* @__PURE__ */ new WeakSet());
1903
1912
  }
1904
1913
  if (typeof prop === "string") {
1905
1914
  const path = tree.length > 0 ? "state." + tree.concat(prop).join(".") : "state." + prop;
@@ -2035,7 +2044,8 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
2035
2044
  message_default(`State '${key}' is now unlocked.`);
2036
2045
  }, "value")
2037
2046
  });
2038
- } catch (_) {
2047
+ } catch (err) {
2048
+ if (exports.DEV) console.warn("[memorio state] lock/unlock defineProperty failed:", err);
2039
2049
  }
2040
2050
  }
2041
2051
  }
@@ -3263,7 +3273,7 @@ init_internal();
3263
3273
 
3264
3274
  // package.json
3265
3275
  var package_default = {
3266
- version: "4.9.8"};
3276
+ version: "4.9.10"};
3267
3277
 
3268
3278
  // core/global.ts
3269
3279
  init_env();
@@ -3282,20 +3292,20 @@ __name(typed, "typed");
3282
3292
  // functions/history/index.ts
3283
3293
  init_cjs_shims();
3284
3294
  init_internal();
3295
+ init_env();
3285
3296
  init_message();
3286
3297
  function _clone(value) {
3287
3298
  if (value === null || typeof value !== "object") return value;
3288
- const ctor = value.constructor?.name;
3289
- if (ctor !== "Object" && ctor !== "Array") return value;
3290
3299
  try {
3291
- return JSON.parse(JSON.stringify(value));
3292
- } catch {
3300
+ return deepRaw(value, /* @__PURE__ */ new WeakSet());
3301
+ } catch (err) {
3302
+ if (exports.DEV) console.warn("[memorio history] deep clone failed:", err);
3293
3303
  return value;
3294
3304
  }
3295
3305
  }
3296
3306
  __name(_clone, "_clone");
3297
3307
  var snapshot = /* @__PURE__ */ __name(() => {
3298
- return _clone(deepRaw(exports.state));
3308
+ return deepRaw(exports.state, /* @__PURE__ */ new WeakSet());
3299
3309
  }, "snapshot");
3300
3310
  var diff = /* @__PURE__ */ __name((snap) => {
3301
3311
  const changes = [];
@@ -3445,9 +3455,11 @@ var clearRedo = /* @__PURE__ */ __name(() => {
3445
3455
  init_cjs_shims();
3446
3456
  init_store();
3447
3457
  init_idb();
3458
+ init_env();
3448
3459
 
3449
3460
  // functions/memory/journal.ts
3450
3461
  init_cjs_shims();
3462
+ init_env();
3451
3463
  init_store();
3452
3464
  var g2 = globalThis;
3453
3465
  function namespace() {
@@ -3468,7 +3480,8 @@ function parseEntry(raw) {
3468
3480
  try {
3469
3481
  if (typeof raw === "string") return JSON.parse(raw);
3470
3482
  return raw;
3471
- } catch {
3483
+ } catch (err) {
3484
+ if (exports.DEV) console.warn("[memorio memory] failed to parse journal entry:", err);
3472
3485
  return null;
3473
3486
  }
3474
3487
  }
@@ -3552,10 +3565,12 @@ var journal = {
3552
3565
  * local operations, then optionally pulls newer remote operations and applies
3553
3566
  * them. No-op (empty ack) when no provider is configured.
3554
3567
  *
3555
- * When pulling remote entries, HLC timestamps are used to order operations
3556
- * causally. Concurrent writes to the same key are resolved via the provider's
3557
- * `resolveConflict` method (if provided), falling back to HLC timestamp
3558
- * comparison.
3568
+ * When pulling remote entries, HLC timestamps are used to order operations
3569
+ * causally. Concurrent writes to the same key (HLC tie) are resolved via the
3570
+ * provider's `resolveConflict` method (if provided), otherwise the REMOTE
3571
+ * entry wins by default (logged at warn level in dev). Provide a resolver on
3572
+ * the sync provider to override this behaviour and prevent silent local data
3573
+ * loss.
3559
3574
  */
3560
3575
  async replay() {
3561
3576
  const ack = { synced: [], resolved: [] };
@@ -3591,7 +3606,11 @@ var journal = {
3591
3606
  if (ha && hb) {
3592
3607
  const cmp = hlc.compare(ha, hb);
3593
3608
  if (cmp === 0) {
3594
- const resolution = typeof provider.resolveConflict === "function" ? await provider.resolveConflict(localEntry, e) : "remote";
3609
+ const hasResolver = typeof provider.resolveConflict === "function";
3610
+ const resolution = hasResolver ? await provider.resolveConflict(localEntry, e) : "remote";
3611
+ if (!hasResolver && exports.DEV) {
3612
+ console.warn(`[memorio memory] replay() conflict for "${e.id}": no resolveConflict handler provided, defaulting to remote-wins`);
3613
+ }
3595
3614
  ack.resolved?.push({ id: e.id, resolution });
3596
3615
  if (resolution === "local") continue;
3597
3616
  } else if (cmp > 0) {
@@ -3602,7 +3621,8 @@ var journal = {
3602
3621
  await this.append(e, e.operation || "confirm");
3603
3622
  await this.markSynced([e.id]);
3604
3623
  }
3605
- } catch (_) {
3624
+ } catch (err) {
3625
+ if (exports.DEV) console.warn("[memorio memory] sync pull failed:", err);
3606
3626
  }
3607
3627
  }
3608
3628
  return ack;
@@ -3641,7 +3661,8 @@ async function recordJournal(entry, operation) {
3641
3661
  if (entry.scope === "hot") return;
3642
3662
  try {
3643
3663
  await journal_default.append(entry, operation);
3644
- } catch (_) {
3664
+ } catch (err) {
3665
+ if (exports.DEV) console.warn("[memorio memory] journal append failed:", err);
3645
3666
  }
3646
3667
  }
3647
3668
  __name(recordJournal, "recordJournal");
@@ -3804,7 +3825,8 @@ async function loadFromScope(id, scope, isSuperseded = false) {
3804
3825
  const entry = JSON.parse(raw);
3805
3826
  entry.id = entry.id || id;
3806
3827
  return entry;
3807
- } catch {
3828
+ } catch (err) {
3829
+ if (exports.DEV) console.warn("[memorio memory] failed to parse memory entry:", err);
3808
3830
  return null;
3809
3831
  }
3810
3832
  }
@@ -3850,7 +3872,8 @@ async function getAllIndexes() {
3850
3872
  const raw = exports.store.get(MEMORY_INDEX_KEY);
3851
3873
  if (!raw) return {};
3852
3874
  return JSON.parse(raw);
3853
- } catch {
3875
+ } catch (err) {
3876
+ if (exports.DEV) console.warn("[memorio memory] failed to parse memory index:", err);
3854
3877
  return {};
3855
3878
  }
3856
3879
  }
@@ -4121,6 +4144,7 @@ var memory = {
4121
4144
 
4122
4145
  // functions/inspect/index.ts
4123
4146
  init_cjs_shims();
4147
+ init_env();
4124
4148
  var stateKeys = /* @__PURE__ */ __name(() => {
4125
4149
  const raw = deepRaw(exports.state);
4126
4150
  return Object.keys(raw).filter(
@@ -4187,11 +4211,10 @@ function _getValue(path) {
4187
4211
  __name(_getValue, "_getValue");
4188
4212
  function _clone2(value) {
4189
4213
  if (value === null || typeof value !== "object") return value;
4190
- const ctor = value.constructor?.name;
4191
- if (ctor !== "Object" && ctor !== "Array") return value;
4192
4214
  try {
4193
- return JSON.parse(JSON.stringify(value));
4194
- } catch {
4215
+ return deepRaw(value, /* @__PURE__ */ new WeakSet());
4216
+ } catch (err) {
4217
+ if (exports.DEV) console.warn("[memorio inspect] deep clone failed:", err);
4195
4218
  return value;
4196
4219
  }
4197
4220
  }
@@ -4218,15 +4241,18 @@ if (!("memorio" in globalThis)) {
4218
4241
  enumerable: false
4219
4242
  }
4220
4243
  );
4221
- Object.defineProperty(
4222
- globalThis,
4223
- "events",
4224
- {
4225
- value: {},
4226
- writable: true,
4227
- enumerable: false
4228
- }
4229
- );
4244
+ if (!("events" in globalThis)) {
4245
+ Object.defineProperty(
4246
+ globalThis,
4247
+ "events",
4248
+ {
4249
+ value: {},
4250
+ writable: true,
4251
+ enumerable: false,
4252
+ configurable: true
4253
+ }
4254
+ );
4255
+ }
4230
4256
  if (typeof globalThis.addEventListener === "function") {
4231
4257
  dispatch.listen("memorio:memory:cleanup", async () => memory.forgetExpired?.());
4232
4258
  }
package/index.js CHANGED
@@ -1817,6 +1817,14 @@ var _validateValue = /* @__PURE__ */ __name((value, schema2, path2) => {
1817
1817
  }
1818
1818
  }
1819
1819
  }
1820
+ if (schema2.type === "array" && Array.isArray(value) && schema2.items) {
1821
+ for (let i = 0; i < value.length; i++) {
1822
+ const itemResult = _validateValue(value[i], schema2.items, `${path2}[${i}]`);
1823
+ if (!itemResult.valid) {
1824
+ errors.push(...itemResult.errors || []);
1825
+ }
1826
+ }
1827
+ }
1820
1828
  return errors.length === 0 ? { valid: true } : { valid: false, errors };
1821
1829
  }, "_validateValue");
1822
1830
  var validate = /* @__PURE__ */ __name((path2, value) => {
@@ -1877,7 +1885,8 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
1877
1885
  const event = /* @__PURE__ */ __name((name) => {
1878
1886
  try {
1879
1887
  dispatch.set(name, { detail: { name } });
1880
- } catch (_) {
1888
+ } catch (err) {
1889
+ if (DEV) console.warn("[memorio state] event dispatch failed:", err);
1881
1890
  }
1882
1891
  return;
1883
1892
  }, "event");
@@ -1896,7 +1905,7 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
1896
1905
  }
1897
1906
  if (prop === "list") {
1898
1907
  message_default('Some state could be hidden when you use "state.list". Use "state" to see the complete list ');
1899
- return JSON.parse(JSON.stringify(target));
1908
+ return deepRaw(target, /* @__PURE__ */ new WeakSet());
1900
1909
  }
1901
1910
  if (typeof prop === "string") {
1902
1911
  const path2 = tree.length > 0 ? "state." + tree.concat(prop).join(".") : "state." + prop;
@@ -2032,7 +2041,8 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
2032
2041
  message_default(`State '${key}' is now unlocked.`);
2033
2042
  }, "value")
2034
2043
  });
2035
- } catch (_) {
2044
+ } catch (err) {
2045
+ if (DEV) console.warn("[memorio state] lock/unlock defineProperty failed:", err);
2036
2046
  }
2037
2047
  }
2038
2048
  }
@@ -3260,7 +3270,7 @@ init_internal();
3260
3270
 
3261
3271
  // package.json
3262
3272
  var package_default = {
3263
- version: "4.9.8"};
3273
+ version: "4.9.10"};
3264
3274
 
3265
3275
  // core/global.ts
3266
3276
  init_env();
@@ -3279,20 +3289,20 @@ __name(typed, "typed");
3279
3289
  // functions/history/index.ts
3280
3290
  init_esm_shims();
3281
3291
  init_internal();
3292
+ init_env();
3282
3293
  init_message();
3283
3294
  function _clone(value) {
3284
3295
  if (value === null || typeof value !== "object") return value;
3285
- const ctor = value.constructor?.name;
3286
- if (ctor !== "Object" && ctor !== "Array") return value;
3287
3296
  try {
3288
- return JSON.parse(JSON.stringify(value));
3289
- } catch {
3297
+ return deepRaw(value, /* @__PURE__ */ new WeakSet());
3298
+ } catch (err) {
3299
+ if (DEV) console.warn("[memorio history] deep clone failed:", err);
3290
3300
  return value;
3291
3301
  }
3292
3302
  }
3293
3303
  __name(_clone, "_clone");
3294
3304
  var snapshot = /* @__PURE__ */ __name(() => {
3295
- return _clone(deepRaw(state2));
3305
+ return deepRaw(state2, /* @__PURE__ */ new WeakSet());
3296
3306
  }, "snapshot");
3297
3307
  var diff = /* @__PURE__ */ __name((snap) => {
3298
3308
  const changes = [];
@@ -3442,9 +3452,11 @@ var clearRedo = /* @__PURE__ */ __name(() => {
3442
3452
  init_esm_shims();
3443
3453
  init_store();
3444
3454
  init_idb();
3455
+ init_env();
3445
3456
 
3446
3457
  // functions/memory/journal.ts
3447
3458
  init_esm_shims();
3459
+ init_env();
3448
3460
  init_store();
3449
3461
  var g2 = globalThis;
3450
3462
  function namespace() {
@@ -3465,7 +3477,8 @@ function parseEntry(raw) {
3465
3477
  try {
3466
3478
  if (typeof raw === "string") return JSON.parse(raw);
3467
3479
  return raw;
3468
- } catch {
3480
+ } catch (err) {
3481
+ if (DEV) console.warn("[memorio memory] failed to parse journal entry:", err);
3469
3482
  return null;
3470
3483
  }
3471
3484
  }
@@ -3549,10 +3562,12 @@ var journal = {
3549
3562
  * local operations, then optionally pulls newer remote operations and applies
3550
3563
  * them. No-op (empty ack) when no provider is configured.
3551
3564
  *
3552
- * When pulling remote entries, HLC timestamps are used to order operations
3553
- * causally. Concurrent writes to the same key are resolved via the provider's
3554
- * `resolveConflict` method (if provided), falling back to HLC timestamp
3555
- * comparison.
3565
+ * When pulling remote entries, HLC timestamps are used to order operations
3566
+ * causally. Concurrent writes to the same key (HLC tie) are resolved via the
3567
+ * provider's `resolveConflict` method (if provided), otherwise the REMOTE
3568
+ * entry wins by default (logged at warn level in dev). Provide a resolver on
3569
+ * the sync provider to override this behaviour and prevent silent local data
3570
+ * loss.
3556
3571
  */
3557
3572
  async replay() {
3558
3573
  const ack = { synced: [], resolved: [] };
@@ -3588,7 +3603,11 @@ var journal = {
3588
3603
  if (ha && hb) {
3589
3604
  const cmp = hlc.compare(ha, hb);
3590
3605
  if (cmp === 0) {
3591
- const resolution = typeof provider.resolveConflict === "function" ? await provider.resolveConflict(localEntry, e) : "remote";
3606
+ const hasResolver = typeof provider.resolveConflict === "function";
3607
+ const resolution = hasResolver ? await provider.resolveConflict(localEntry, e) : "remote";
3608
+ if (!hasResolver && DEV) {
3609
+ console.warn(`[memorio memory] replay() conflict for "${e.id}": no resolveConflict handler provided, defaulting to remote-wins`);
3610
+ }
3592
3611
  ack.resolved?.push({ id: e.id, resolution });
3593
3612
  if (resolution === "local") continue;
3594
3613
  } else if (cmp > 0) {
@@ -3599,7 +3618,8 @@ var journal = {
3599
3618
  await this.append(e, e.operation || "confirm");
3600
3619
  await this.markSynced([e.id]);
3601
3620
  }
3602
- } catch (_) {
3621
+ } catch (err) {
3622
+ if (DEV) console.warn("[memorio memory] sync pull failed:", err);
3603
3623
  }
3604
3624
  }
3605
3625
  return ack;
@@ -3638,7 +3658,8 @@ async function recordJournal(entry, operation) {
3638
3658
  if (entry.scope === "hot") return;
3639
3659
  try {
3640
3660
  await journal_default.append(entry, operation);
3641
- } catch (_) {
3661
+ } catch (err) {
3662
+ if (DEV) console.warn("[memorio memory] journal append failed:", err);
3642
3663
  }
3643
3664
  }
3644
3665
  __name(recordJournal, "recordJournal");
@@ -3801,7 +3822,8 @@ async function loadFromScope(id, scope, isSuperseded = false) {
3801
3822
  const entry = JSON.parse(raw);
3802
3823
  entry.id = entry.id || id;
3803
3824
  return entry;
3804
- } catch {
3825
+ } catch (err) {
3826
+ if (DEV) console.warn("[memorio memory] failed to parse memory entry:", err);
3805
3827
  return null;
3806
3828
  }
3807
3829
  }
@@ -3847,7 +3869,8 @@ async function getAllIndexes() {
3847
3869
  const raw = store.get(MEMORY_INDEX_KEY);
3848
3870
  if (!raw) return {};
3849
3871
  return JSON.parse(raw);
3850
- } catch {
3872
+ } catch (err) {
3873
+ if (DEV) console.warn("[memorio memory] failed to parse memory index:", err);
3851
3874
  return {};
3852
3875
  }
3853
3876
  }
@@ -4118,6 +4141,7 @@ var memory = {
4118
4141
 
4119
4142
  // functions/inspect/index.ts
4120
4143
  init_esm_shims();
4144
+ init_env();
4121
4145
  var stateKeys = /* @__PURE__ */ __name(() => {
4122
4146
  const raw = deepRaw(state2);
4123
4147
  return Object.keys(raw).filter(
@@ -4184,11 +4208,10 @@ function _getValue(path2) {
4184
4208
  __name(_getValue, "_getValue");
4185
4209
  function _clone2(value) {
4186
4210
  if (value === null || typeof value !== "object") return value;
4187
- const ctor = value.constructor?.name;
4188
- if (ctor !== "Object" && ctor !== "Array") return value;
4189
4211
  try {
4190
- return JSON.parse(JSON.stringify(value));
4191
- } catch {
4212
+ return deepRaw(value, /* @__PURE__ */ new WeakSet());
4213
+ } catch (err) {
4214
+ if (DEV) console.warn("[memorio inspect] deep clone failed:", err);
4192
4215
  return value;
4193
4216
  }
4194
4217
  }
@@ -4215,15 +4238,18 @@ if (!("memorio" in globalThis)) {
4215
4238
  enumerable: false
4216
4239
  }
4217
4240
  );
4218
- Object.defineProperty(
4219
- globalThis,
4220
- "events",
4221
- {
4222
- value: {},
4223
- writable: true,
4224
- enumerable: false
4225
- }
4226
- );
4241
+ if (!("events" in globalThis)) {
4242
+ Object.defineProperty(
4243
+ globalThis,
4244
+ "events",
4245
+ {
4246
+ value: {},
4247
+ writable: true,
4248
+ enumerable: false,
4249
+ configurable: true
4250
+ }
4251
+ );
4252
+ }
4227
4253
  if (typeof globalThis.addEventListener === "function") {
4228
4254
  dispatch.listen("memorio:memory:cleanup", async () => memory.forgetExpired?.());
4229
4255
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memorio",
3
3
  "codeName": "memorio",
4
- "version": "4.9.8",
4
+ "version": "4.9.10",
5
5
  "description": "Memorio, State + Observer, Store and iDB for an easy life - Cross-platform compatible",
6
6
  "main": "./index.cjs",
7
7
  "browser": {