@evolu/common 8.15.1 → 8.17.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 (41) hide show
  1. package/dist/src/Console.d.ts +24 -46
  2. package/dist/src/Console.d.ts.map +1 -1
  3. package/dist/src/Console.js +34 -29
  4. package/dist/src/Crypto.d.ts +21 -3
  5. package/dist/src/Crypto.d.ts.map +1 -1
  6. package/dist/src/Crypto.js +18 -2
  7. package/dist/src/Error.d.ts +10 -6
  8. package/dist/src/Error.d.ts.map +1 -1
  9. package/dist/src/Error.js +52 -20
  10. package/dist/src/Sqlite.d.ts.map +1 -1
  11. package/dist/src/Sqlite.js +16 -12
  12. package/dist/src/Task.d.ts +3 -1
  13. package/dist/src/Task.d.ts.map +1 -1
  14. package/dist/src/Task.js +15 -13
  15. package/dist/src/Worker.d.ts +3 -0
  16. package/dist/src/Worker.d.ts.map +1 -1
  17. package/dist/src/Worker.js +6 -1
  18. package/dist/src/local-first/Db.d.ts.map +1 -1
  19. package/dist/src/local-first/Db.js +7 -7
  20. package/dist/src/local-first/Owner.d.ts +20 -8
  21. package/dist/src/local-first/Owner.d.ts.map +1 -1
  22. package/dist/src/local-first/Owner.js +23 -10
  23. package/dist/src/local-first/Schema.d.ts +1 -1
  24. package/dist/src/local-first/Shared.d.ts +55 -39
  25. package/dist/src/local-first/Shared.d.ts.map +1 -1
  26. package/dist/src/local-first/Shared.js +264 -318
  27. package/package.json +1 -1
  28. package/src/Console.test.ts +75 -247
  29. package/src/Console.ts +68 -110
  30. package/src/Crypto.ts +23 -3
  31. package/src/Error.test.ts +85 -0
  32. package/src/Error.ts +48 -22
  33. package/src/Sqlite.ts +19 -11
  34. package/src/Task.test.ts +46 -0
  35. package/src/Task.ts +17 -13
  36. package/src/Worker.ts +6 -1
  37. package/src/local-first/Db.ts +12 -8
  38. package/src/local-first/Owner.test.ts +52 -1
  39. package/src/local-first/Owner.ts +26 -7
  40. package/src/local-first/Shared.test.ts +414 -39
  41. package/src/local-first/Shared.ts +386 -438
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "8.15.1",
3
+ "version": "8.17.0",
4
4
  "description": "TypeScript library and local-first platform",
5
5
  "keywords": [
6
6
  "evolu",
@@ -1,4 +1,4 @@
1
- import { describe, it, mock } from "node:test";
1
+ import { describe, it } from "node:test";
2
2
  import {
3
3
  assertEqual,
4
4
  assertFalse,
@@ -114,19 +114,35 @@ describe("createConsole", () => {
114
114
  );
115
115
  });
116
116
 
117
- it("child inherits level at creation (static)", () => {
117
+ it("child without own level follows its parent's level", () => {
118
118
  const output = createTestOutput();
119
119
  const console = createConsole({ output, level: "info" });
120
- const child = console.child("relay");
121
-
122
- // Child inherits "info" level
123
- child.debug("ignored");
124
- child.info("logged");
120
+ const grandchild = console.child("relay").child("db");
125
121
 
126
- // Parent change doesn't affect child (static inheritance)
122
+ grandchild.debug("ignored");
127
123
  console.setLevel("debug");
128
- child.debug("still ignored");
124
+ grandchild.debug("logged");
129
125
 
126
+ assertEqual(grandchild.getLevel(), "debug");
127
+ assertFalse(grandchild.hasOwnLevel());
128
+ assertEqual(
129
+ output.entries.map((e) => e.entry.args[0]),
130
+ ["logged"],
131
+ );
132
+ });
133
+
134
+ it("grandchild follows its parent's own level", () => {
135
+ const output = createTestOutput();
136
+ const console = createConsole({ output, level: "info" });
137
+ const relay = console.child("relay");
138
+ const db = relay.child("db");
139
+
140
+ relay.setLevel("debug");
141
+ db.debug("logged");
142
+ assertEqual(db.getLevel(), "debug");
143
+
144
+ relay.setLevel(null);
145
+ assertEqual(db.getLevel(), "info");
130
146
  assertEqual(
131
147
  output.entries.map((e) => e.entry.args[0]),
132
148
  ["logged"],
@@ -139,9 +155,9 @@ describe("createConsole", () => {
139
155
  const child = console.child("relay");
140
156
 
141
157
  child.setLevel("debug");
158
+ console.setLevel("error");
142
159
  child.debug("logged");
143
- // parent still at "info"
144
- console.debug("ignored");
160
+ console.warn("ignored");
145
161
 
146
162
  assertEqual(
147
163
  output.entries.map((e) => e.entry.args[0]),
@@ -160,6 +176,9 @@ describe("createConsole", () => {
160
176
  child.setLevel(null);
161
177
  assertFalse(child.hasOwnLevel());
162
178
  assertEqual(child.getLevel(), "info");
179
+
180
+ console.setLevel("warn");
181
+ assertEqual(child.getLevel(), "warn");
163
182
  });
164
183
 
165
184
  it("child adds path", () => {
@@ -186,89 +205,19 @@ describe("createConsole", () => {
186
205
  assertEqual(output.entries[0].formattedArgs, ["prefix", "message"]);
187
206
  });
188
207
 
189
- it("debug-level methods use debug level", () => {
208
+ it("write filters replayed entries by level", () => {
190
209
  const output = createTestOutput();
191
- const console = createConsole({
192
- output,
193
- level: "log",
194
- });
195
-
196
- console.time("timer");
197
- console.timeLog("timer");
198
- console.timeEnd("timer");
199
- console.dir({ foo: 1 });
200
- console.table([1, 2, 3]);
201
- console.count("counter");
202
- console.countReset("counter");
203
-
204
- assertLength(output.entries, 0);
205
-
206
- console.setLevel("debug");
210
+ const console = createConsole({ output, level: "info" });
207
211
 
208
- console.time("timer");
209
- console.dir({ foo: 1 });
212
+ console.write({ method: "debug", path: ["worker"], args: ["dropped"] });
213
+ console.write({ method: "error", path: ["worker"], args: ["replayed"] });
210
214
 
211
215
  assertEqual(
212
- output.entries.map((e) => e.entry.method),
213
- ["time", "dir"],
216
+ output.entries.map((e) => e.entry.args[0]),
217
+ ["replayed"],
214
218
  );
215
219
  });
216
220
 
217
- it("debug-level methods skip formatter", () => {
218
- const output = createTestOutput();
219
- const formatter = mock.fn((entry: ConsoleEntry) => [
220
- "formatted",
221
- ...entry.args,
222
- ]);
223
- const console = createConsole({
224
- output,
225
- level: "debug",
226
- formatter,
227
- });
228
-
229
- console.info("info message");
230
- console.dir({ foo: 1 });
231
-
232
- assertEqual(output.entries[0].formattedArgs, ["formatted", "info message"]);
233
- assertEqual(output.entries[1].formattedArgs, [{ foo: 1 }]);
234
- });
235
-
236
- it("children tracking", () => {
237
- const console = createConsole();
238
- const child1 = console.child("a");
239
- const child2 = console.child("b");
240
- const grandchild = child1.child("c");
241
-
242
- assertEqual(console.children.size, 2);
243
- assertTrue(console.children.has(child1));
244
- assertTrue(console.children.has(child2));
245
- assertEqual(child1.children.size, 1);
246
- assertTrue(child1.children.has(grandchild));
247
- });
248
-
249
- it("name property", () => {
250
- const console = createConsole({ name: "root" });
251
- const child = console.child("relay");
252
-
253
- assertEqual(console.name, "root");
254
- assertEqual(child.name, "relay");
255
- });
256
-
257
- it("write bypasses level filtering", () => {
258
- const output = createTestOutput();
259
- const console = createConsole({ output, level: "silent" });
260
-
261
- const entry: ConsoleEntry = {
262
- method: "debug",
263
- path: ["worker"],
264
- args: ["replayed"],
265
- };
266
- console.write(entry);
267
-
268
- assertLength(output.entries, 1);
269
- assertEqual(output.entries[0].entry, entry);
270
- });
271
-
272
221
  it("write passes formatter to output", () => {
273
222
  const output = createTestOutput();
274
223
  const formatter = (entry: ConsoleEntry) => ["fmt", ...entry.args];
@@ -303,6 +252,18 @@ describe("createNativeConsoleOutput", () => {
303
252
  assertSame(logSpy.mock.calls[0].this, nativeConsole);
304
253
  });
305
254
 
255
+ it("writes trace entries with native debug", (t) => {
256
+ const debugSpy = t.mock.method(nativeConsole, "debug", () => undefined);
257
+ const traceSpy = t.mock.method(nativeConsole, "trace", () => undefined);
258
+ const output = createNativeConsoleOutput();
259
+
260
+ output.write({ method: "trace", path: [], args: ["query"] });
261
+
262
+ assertEqual(debugSpy.mock.calls[0].arguments, ["query"]);
263
+ assertSame(debugSpy.mock.calls[0].this, nativeConsole);
264
+ assertLength(traceSpy.mock.calls, 0);
265
+ });
266
+
306
267
  it("applies formatter", (t) => {
307
268
  const logSpy = t.mock.method(nativeConsole, "info", () => undefined);
308
269
  const output = createNativeConsoleOutput();
@@ -426,7 +387,7 @@ describe("createConsoleFormatter", () => {
426
387
 
427
388
  it("absolute timestamp", () => {
428
389
  const time = testCreateTime({
429
- startAt: Date.UTC(2026, 0, 28, 14, 30, 15, 123) as Millis,
390
+ startAt: new Date(2026, 0, 28, 14, 30, 15, 123).getTime() as Millis,
430
391
  });
431
392
  const formatter = createConsoleFormatter({ time })({
432
393
  timestampFormat: "absolute",
@@ -438,13 +399,23 @@ describe("createConsoleFormatter", () => {
438
399
  args: ["message"],
439
400
  };
440
401
 
441
- const result = formatter(entry);
402
+ assertEqual(formatter(entry), ["14:30:15.123", "message"]);
403
+ });
442
404
 
443
- // Result includes local time formatted as HH:MM:SS.mmm
444
- assertLength(result, 2);
445
- assertType(String, result[0]);
446
- assertTrue(/^\d{2}:\d{2}:\d{2}\.\d{3}$/u.test(result[0]));
447
- assertEqual(result[1], "message");
405
+ it("relative timestamp is never negative", () => {
406
+ const time = testCreateTime({ startAt: 1000 as Millis });
407
+ const formatter = createConsoleFormatter({ time })({
408
+ timestampFormat: "relative",
409
+ startTime: 1500 as Millis,
410
+ });
411
+
412
+ const entry: ConsoleEntry = {
413
+ method: "info",
414
+ path: [],
415
+ args: ["message"],
416
+ };
417
+
418
+ assertEqual(formatter(entry), ["+0.000s", "message"]);
448
419
  });
449
420
 
450
421
  it("combines timestamp and path", () => {
@@ -462,61 +433,6 @@ describe("createConsoleFormatter", () => {
462
433
 
463
434
  assertEqual(result, ["+0.000s [relay]", "message"]);
464
435
  });
465
-
466
- it("createConsoleFormatter example", () => {
467
- const time = testCreateTime({ startAt: 0 as Millis });
468
- const output = createTestOutput();
469
-
470
- // Relative timestamps
471
- const root = createConsole({
472
- output,
473
- formatter: createConsoleFormatter({ time })({
474
- timestampFormat: "relative",
475
- }),
476
- });
477
-
478
- const relay = root.child("relay");
479
- relay.log("connected");
480
- time.advance("1.5s");
481
- relay.log("synced");
482
-
483
- assertEqual(
484
- output.entries.map((entry) => entry.formattedArgs),
485
- [
486
- ["+0.000s [relay]", "connected"],
487
- ["+1.500s [relay]", "synced"],
488
- ],
489
- );
490
-
491
- // Nested children
492
- const db = relay.child("db");
493
- db.log("opened");
494
-
495
- assertEqual(output.entries[2].formattedArgs, [
496
- "+1.500s [relay] [db]",
497
- "opened",
498
- ]);
499
-
500
- // Absolute timestamps (local clock time HH:MM:SS.mmm)
501
- const absoluteOutput = createTestOutput();
502
- const absoluteTime = testCreateTime({
503
- startAt: Date.UTC(2026, 0, 28, 14, 30, 15, 123) as Millis,
504
- });
505
- const absoluteRoot = createConsole({
506
- output: absoluteOutput,
507
- formatter: createConsoleFormatter({ time: absoluteTime })({
508
- timestampFormat: "absolute",
509
- }),
510
- });
511
- const absoluteRelay = absoluteRoot.child("relay");
512
-
513
- absoluteRelay.log("connected");
514
-
515
- const [timestamp, message] = absoluteOutput.entries[0].formattedArgs;
516
- assertType(String, timestamp);
517
- assertTrue(/^\d{2}:\d{2}:\d{2}\.\d{3} \[relay\]$/u.test(timestamp));
518
- assertEqual(message, "connected");
519
- });
520
436
  });
521
437
 
522
438
  describe("createConsoleStoreOutput", () => {
@@ -635,19 +551,18 @@ describe("createMultiOutput", () => {
635
551
  assertEqual(entries1[0], entries2[0]);
636
552
  });
637
553
 
638
- it("combines native and store outputs", () => {
639
- const storeOutput = createConsoleStoreOutput();
640
- const entries: Array<ConsoleEntry> = [];
641
- const output = createMultiOutput([
642
- createConsoleArrayOutput(entries),
643
- storeOutput,
644
- ]);
645
- const console = createConsole({ output });
554
+ it("passes the formatter to each output", () => {
555
+ const first = createTestOutput();
556
+ const second = createTestOutput();
557
+ const console = createConsole({
558
+ output: createMultiOutput([first, second]),
559
+ formatter: (entry) => ["fmt", ...entry.args],
560
+ });
646
561
 
647
- console.error("fail");
562
+ console.info("msg");
648
563
 
649
- assertLength(entries, 1);
650
- assertEqual(storeOutput.entry.get()?.args, ["fail"]);
564
+ assertEqual(first.entries[0].formattedArgs, ["fmt", "msg"]);
565
+ assertEqual(second.entries[0].formattedArgs, ["fmt", "msg"]);
651
566
  });
652
567
  });
653
568
 
@@ -719,91 +634,4 @@ describe("testCreateConsole", () => {
719
634
 
720
635
  assertEqual(console.getEntriesSnapshot()[0].path, ["relay", "db"]);
721
636
  });
722
-
723
- it("child inherits level at creation (static)", () => {
724
- const console = testCreateConsole({ level: "info" });
725
- const child = console.child("relay");
726
-
727
- // Child inherits "info" level
728
- child.debug("ignored");
729
- child.info("logged");
730
-
731
- // Parent change doesn't affect child
732
- console.setLevel("debug");
733
- child.debug("still ignored");
734
-
735
- assertEqual(
736
- console.getEntriesSnapshot().map((e) => e.args[0]),
737
- ["logged"],
738
- );
739
- });
740
-
741
- it("child can override level independently", () => {
742
- const console = testCreateConsole({ level: "info" });
743
- const child = console.child("relay");
744
-
745
- child.setLevel("debug");
746
- child.debug("logged");
747
-
748
- assertEqual(
749
- console.getEntriesSnapshot().map((e) => e.args[0]),
750
- ["logged"],
751
- );
752
- });
753
-
754
- it("hasOwnLevel tracks level override", () => {
755
- const console = testCreateConsole({ level: "info" });
756
-
757
- assertFalse(console.hasOwnLevel());
758
-
759
- console.setLevel("debug");
760
- assertTrue(console.hasOwnLevel());
761
-
762
- console.setLevel(null);
763
- assertFalse(console.hasOwnLevel());
764
- });
765
-
766
- it("debug-level methods use debug level", () => {
767
- const console = testCreateConsole({ level: "log" });
768
-
769
- console.time("timer");
770
- console.dir({ foo: 1 });
771
- console.table([1, 2]);
772
- console.count("counter");
773
-
774
- assertLength(console.getEntriesSnapshot(), 0);
775
-
776
- console.setLevel("debug");
777
-
778
- console.time("timer");
779
- console.timeLog("timer", "extra");
780
- console.timeEnd("timer");
781
- console.dir({ foo: 1 });
782
- console.table([1, 2]);
783
- console.count("counter");
784
- console.countReset("counter");
785
-
786
- assertEqual(
787
- console.getEntriesSnapshot().map((e) => e.method),
788
- ["time", "timeLog", "timeEnd", "dir", "table", "count", "countReset"],
789
- );
790
- });
791
-
792
- it("children tracking", () => {
793
- const console = testCreateConsole();
794
- const child1 = console.child("a");
795
- const child2 = console.child("b");
796
-
797
- assertEqual(console.children.size, 2);
798
- assertTrue(console.children.has(child1));
799
- assertTrue(console.children.has(child2));
800
- });
801
-
802
- it("name property", () => {
803
- const console = testCreateConsole();
804
- const child = console.child("relay");
805
-
806
- assertEqual(console.name, "");
807
- assertEqual(child.name, "relay");
808
- });
809
637
  });