@evolu/common 7.2.3 → 7.4.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 (92) hide show
  1. package/dist/src/Buffer.d.ts +2 -1
  2. package/dist/src/Buffer.d.ts.map +1 -1
  3. package/dist/src/Buffer.js +1 -0
  4. package/dist/src/Callbacks.d.ts +1 -1
  5. package/dist/src/Callbacks.d.ts.map +1 -1
  6. package/dist/src/Callbacks.js +1 -1
  7. package/dist/src/Console.d.ts +1 -9
  8. package/dist/src/Console.d.ts.map +1 -1
  9. package/dist/src/Console.js +1 -41
  10. package/dist/src/Function.d.ts +61 -0
  11. package/dist/src/Function.d.ts.map +1 -1
  12. package/dist/src/Function.js +19 -0
  13. package/dist/src/Object.d.ts +16 -0
  14. package/dist/src/Object.d.ts.map +1 -1
  15. package/dist/src/Object.js +16 -0
  16. package/dist/src/Redacted.d.ts +89 -0
  17. package/dist/src/Redacted.d.ts.map +1 -0
  18. package/dist/src/Redacted.js +48 -0
  19. package/dist/src/Result.d.ts +36 -29
  20. package/dist/src/Result.d.ts.map +1 -1
  21. package/dist/src/Sqlite.d.ts +9 -7
  22. package/dist/src/Sqlite.d.ts.map +1 -1
  23. package/dist/src/Sqlite.js +8 -0
  24. package/dist/src/Task.d.ts.map +1 -1
  25. package/dist/src/Task.js +6 -4
  26. package/dist/src/Type.d.ts +34 -10
  27. package/dist/src/Type.d.ts.map +1 -1
  28. package/dist/src/Type.js +82 -202
  29. package/dist/src/index.d.ts +1 -0
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/index.js +1 -0
  32. package/dist/src/local-first/Db.d.ts +1 -5
  33. package/dist/src/local-first/Db.d.ts.map +1 -1
  34. package/dist/src/local-first/Db.js +109 -108
  35. package/dist/src/local-first/Evolu.d.ts +3 -0
  36. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  37. package/dist/src/local-first/Evolu.js +6 -7
  38. package/dist/src/local-first/LocalAuth.d.ts +2 -2
  39. package/dist/src/local-first/Owner.d.ts +7 -0
  40. package/dist/src/local-first/Owner.d.ts.map +1 -1
  41. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  42. package/dist/src/local-first/Protocol.js +10 -10
  43. package/dist/src/local-first/Public.d.ts +6 -1
  44. package/dist/src/local-first/Public.d.ts.map +1 -1
  45. package/dist/src/local-first/Public.js +6 -1
  46. package/dist/src/local-first/PublicKysely.js +1 -1
  47. package/dist/src/local-first/Query.d.ts +54 -6
  48. package/dist/src/local-first/Query.d.ts.map +1 -1
  49. package/dist/src/local-first/Query.js +130 -11
  50. package/dist/src/local-first/Relay.d.ts.map +1 -1
  51. package/dist/src/local-first/Relay.js +17 -18
  52. package/dist/src/local-first/Schema.d.ts +15 -14
  53. package/dist/src/local-first/Schema.d.ts.map +1 -1
  54. package/dist/src/local-first/Schema.js +43 -51
  55. package/dist/src/local-first/Storage.d.ts +2 -2
  56. package/dist/src/local-first/Storage.d.ts.map +1 -1
  57. package/dist/src/local-first/Storage.js +2 -6
  58. package/dist/src/local-first/Sync.d.ts +9 -4
  59. package/dist/src/local-first/Sync.d.ts.map +1 -1
  60. package/dist/src/local-first/Sync.js +132 -59
  61. package/dist/src/local-first/index.d.ts +9 -2
  62. package/dist/src/local-first/index.d.ts.map +1 -1
  63. package/dist/src/local-first/index.js +9 -2
  64. package/package.json +3 -4
  65. package/src/Buffer.ts +2 -1
  66. package/src/Callbacks.ts +1 -1
  67. package/src/Console.ts +1 -10
  68. package/src/Function.ts +74 -0
  69. package/src/Object.ts +20 -0
  70. package/src/Redacted.ts +118 -0
  71. package/src/Result.ts +38 -29
  72. package/src/Sqlite.ts +18 -7
  73. package/src/Task.ts +6 -4
  74. package/src/Type.ts +148 -20
  75. package/src/index.ts +1 -0
  76. package/src/local-first/Db.ts +181 -209
  77. package/src/local-first/Evolu.ts +9 -6
  78. package/src/local-first/LocalAuth.ts +2 -2
  79. package/src/local-first/Owner.ts +8 -0
  80. package/src/local-first/Protocol.ts +10 -16
  81. package/src/local-first/Public.ts +6 -1
  82. package/src/local-first/PublicKysely.ts +1 -1
  83. package/src/local-first/Query.ts +216 -21
  84. package/src/local-first/Relay.ts +20 -24
  85. package/src/local-first/Schema.ts +83 -70
  86. package/src/local-first/Storage.ts +3 -8
  87. package/src/local-first/Sync.ts +190 -66
  88. package/src/local-first/index.ts +9 -2
  89. package/dist/src/local-first/Diff.d.ts +0 -43
  90. package/dist/src/local-first/Diff.d.ts.map +0 -1
  91. package/dist/src/local-first/Diff.js +0 -97
  92. package/src/local-first/Diff.ts +0 -144
@@ -1 +1 @@
1
- {"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/local-first/Storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/local-first/Storage.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ import { decrement } from "../Number.js";
6
6
  import { err, ok } from "../Result.js";
7
7
  import { sql, SqliteValue } from "../Sqlite.js";
8
8
  import { Boolean, brand, Id, NonNegativeInt, nullOr, object, PositiveInt, record, String, } from "../Type.js";
9
- import { systemColumns } from "./Schema.js";
9
+ import { systemColumnsWithId } from "./Schema.js";
10
10
  import { orderTimestampBytes } from "./Timestamp.js";
11
11
  export const fingerprintSize = NonNegativeInt.orThrow(12);
12
12
  /** A fingerprint of an empty range. */
@@ -18,12 +18,8 @@ export const RangeType = {
18
18
  Timestamps: 2,
19
19
  };
20
20
  export const DbChangeValues = record(String, SqliteValue);
21
- // "createdAt" and "updatedAt" are derived from Timestamp.
22
- // "id" and "isDeleted" are encoded separately to save bytes
23
- // "ownerId" is part of the protocol message
24
- const forbiddenSystemColumns = systemColumns.concat("id");
25
21
  export const ValidDbChangeValues = brand("ValidDbChangeValues", DbChangeValues, (value) => {
26
- const invalidColumns = forbiddenSystemColumns.filter((key) => key in value);
22
+ const invalidColumns = systemColumnsWithId.filter((key) => key in value);
27
23
  if (invalidColumns.length > 0)
28
24
  return err({
29
25
  type: "ValidDbChangeValues",
@@ -7,10 +7,9 @@ import { Result } from "../Result.js";
7
7
  import { SqliteDep, SqliteError } from "../Sqlite.js";
8
8
  import { TimeDep } from "../Time.js";
9
9
  import { CreateWebSocketDep } from "../WebSocket.js";
10
- import type { AppOwnerDep, PostMessageDep } from "./Db.js";
11
- import { AppOwner, Owner, OwnerTransport, ReadonlyOwner } from "./Owner.js";
10
+ import { AppOwner, AppOwnerDep, Owner, OwnerTransport, ReadonlyOwner } from "./Owner.js";
12
11
  import { ProtocolError, ProtocolInvalidDataError, ProtocolTimestampMismatchError } from "./Protocol.js";
13
- import { MutationChange } from "./Schema.js";
12
+ import { DbSchemaDep, MutationChange } from "./Schema.js";
14
13
  import { BaseSqliteStorage, Storage } from "./Storage.js";
15
14
  import { Millis, Timestamp, TimestampConfigDep, TimestampCounterOverflowError, TimestampDriftError, TimestampTimeOutOfRangeError } from "./Timestamp.js";
16
15
  export interface Sync extends Disposable {
@@ -48,7 +47,7 @@ export interface SyncConfig {
48
47
  readonly onError: (error: ProtocolError | ProtocolInvalidDataError | ProtocolTimestampMismatchError | SqliteError | SymmetricCryptoDecryptError | TimestampCounterOverflowError | TimestampDriftError | TimestampTimeOutOfRangeError | TransferableError) => void;
49
48
  readonly onReceive: () => void;
50
49
  }
51
- export declare const createSync: (deps: ClockDep & ConsoleDep & CreateWebSocketDep & PostMessageDep & RandomBytesDep & RandomDep & SqliteDep & SymmetricCryptoDep & TimeDep & TimestampConfigDep) => (config: SyncConfig) => Result<Sync, SqliteError>;
50
+ export declare const createSync: (deps: ClockDep & ConsoleDep & CreateWebSocketDep & DbSchemaDep & RandomBytesDep & RandomDep & SqliteDep & SymmetricCryptoDep & TimeDep & TimestampConfigDep) => (config: SyncConfig) => Result<Sync, SqliteError>;
52
51
  export interface ClockDep {
53
52
  readonly clock: Clock;
54
53
  }
@@ -63,6 +62,12 @@ export interface ClientStorageDep {
63
62
  readonly storage: ClientStorage;
64
63
  }
65
64
  export declare const applyLocalOnlyChange: (deps: SqliteDep & TimeDep & AppOwnerDep) => (change: MutationChange) => Result<void, SqliteError>;
65
+ /**
66
+ * Attempts to apply quarantined messages that may now be valid after a schema
67
+ * update. Messages are quarantined when they reference tables or columns that
68
+ * don't exist in the current schema (e.g., from a newer app version).
69
+ */
70
+ export declare const tryApplyQuarantinedMessages: (deps: DbSchemaDep & SqliteDep) => () => Result<void, SqliteError>;
66
71
  /**
67
72
  * TODO: Rework for the new owners API.
68
73
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Sync.d.ts","sourceRoot":"","sources":["../../../src/local-first/Sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Sync.d.ts","sourceRoot":"","sources":["../../../src/local-first/Sync.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import { assert, assertNonEmptyReadonlyArray } from "../Assert.js";
3
3
  import { eqArrayNumber } from "../Eq.js";
4
4
  import { createTransferableError } from "../Error.js";
5
5
  import { constFalse, constTrue } from "../Function.js";
6
- import { createRecord, objectToEntries } from "../Object.js";
6
+ import { createRecord, getProperty, objectToEntries } from "../Object.js";
7
7
  import { createResources } from "../Resources.js";
8
8
  import { err, ok } from "../Result.js";
9
9
  import { booleanToSqliteBoolean, sql, SqliteBoolean, sqliteBooleanToBoolean, } from "../Sqlite.js";
@@ -11,6 +11,7 @@ import { createMutex } from "../Task.js";
11
11
  import { Boolean, idBytesToId, idToIdBytes, } from "../Type.js";
12
12
  import { ownerIdBytesToOwnerId, ownerIdToOwnerIdBytes, } from "./Owner.js";
13
13
  import { applyProtocolMessageAsClient, createProtocolMessageForSync, createProtocolMessageForUnsubscribe, createProtocolMessageFromCrdtMessages, decryptAndDecodeDbChange, encodeAndEncryptDbChange, SubscriptionFlags, } from "./Protocol.js";
14
+ import { systemColumns } from "./Schema.js";
14
15
  import { createBaseSqliteStorage, DbChange, getOwnerUsage, getTimestampInsertStrategy, updateOwnerUsage, } from "./Storage.js";
15
16
  import { createInitialTimestamp, receiveTimestamp, sendTimestamp, timestampBytesToTimestamp, timestampToDateIso, timestampToTimestampBytes, } from "./Timestamp.js";
16
17
  export const createSync = (deps) => (config) => {
@@ -237,7 +238,7 @@ const createClientStorage = (deps) => (config) => {
237
238
  writeMessages: async (ownerIdBytes, encryptedMessages) => {
238
239
  const ownerId = ownerIdBytesToOwnerId(ownerIdBytes);
239
240
  // Everything is sync now, but we will need async crypto in the future.
240
- const writeResult = await mutex.withLock(async () => {
241
+ const result = await mutex.withLock(async () => {
241
242
  const owner = deps.getSyncOwner(ownerId);
242
243
  // Owner can be removed during syncing.
243
244
  // `ok(true)` means success, we just skipped the write.
@@ -267,9 +268,9 @@ const createClientStorage = (deps) => (config) => {
267
268
  clockTimestamp = nextTimestamp.value;
268
269
  }
269
270
  if (isNonEmptyReadonlyArray(messages)) {
270
- const applyMessagesResult = applyMessages({ ...deps, storage })(owner.id, messages);
271
- if (!applyMessagesResult.ok)
272
- return applyMessagesResult;
271
+ const result = applyMessages({ ...deps, storage })(owner.id, messages);
272
+ if (!result.ok)
273
+ return result;
273
274
  }
274
275
  return deps.clock.save(clockTimestamp);
275
276
  });
@@ -277,9 +278,9 @@ const createClientStorage = (deps) => (config) => {
277
278
  return transaction;
278
279
  return ok(true);
279
280
  })();
280
- if (!writeResult.ok) {
281
- if (writeResult.error.type !== "AbortError") {
282
- config.onError(writeResult.error);
281
+ if (!result.ok) {
282
+ if (result.error.type !== "AbortError") {
283
+ config.onError(result.error);
283
284
  }
284
285
  return err({ type: "StorageWriteError", ownerId });
285
286
  }
@@ -294,6 +295,10 @@ const createClientStorage = (deps) => (config) => {
294
295
  const result = deps.sqlite.exec(sql `
295
296
  select "table", "id", "column", "value"
296
297
  from evolu_history
298
+ where "ownerId" = ${ownerId} and "timestamp" = ${timestamp}
299
+ union all
300
+ select "table", "id", "column", "value"
301
+ from evolu_message_quarantine
297
302
  where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
298
303
  `);
299
304
  if (!result.ok) {
@@ -301,7 +306,7 @@ const createClientStorage = (deps) => (config) => {
301
306
  return null;
302
307
  }
303
308
  const { rows } = result.value;
304
- assertNonEmptyReadonlyArray(rows, "Every timestamp must have rows");
309
+ assertNonEmptyReadonlyArray(rows, "Every timestamp must have rows in evolu_history or quarantine");
305
310
  const { table, id } = firstInArray(rows);
306
311
  const values = createRecord();
307
312
  let isInsert;
@@ -392,65 +397,133 @@ const applyMessages = (deps) => (ownerId, messages) => {
392
397
  return usage;
393
398
  let { firstTimestamp, lastTimestamp } = usage.value;
394
399
  for (const { timestamp, change } of messages) {
395
- const timestampBytes = timestampToTimestampBytes(timestamp);
396
- const idBytes = idToIdBytes(change.id);
397
400
  const columns = dbChangeToColumns(change, timestampToDateIso(timestamp));
401
+ const idBytes = idToIdBytes(change.id);
402
+ const timestampBytes = timestampToTimestampBytes(timestamp);
398
403
  for (const [column, value] of columns) {
399
- const updateAppTable = deps.sqlite.exec(sql.prepared `
400
- with
401
- existingTimestamp as (
402
- select 1
403
- from evolu_history
404
- where
405
- "ownerId" = ${ownerIdBytes}
406
- and "table" = ${change.table}
407
- and "id" = ${idBytes}
408
- and "column" = ${column}
409
- and "timestamp" >= ${timestampBytes}
410
- limit 1
411
- )
412
- insert into ${sql.identifier(change.table)}
413
- ("ownerId", "id", ${sql.identifier(column)})
414
- select ${ownerId}, ${change.id}, ${value}
415
- where not exists (select 1 from existingTimestamp)
416
- on conflict ("ownerId", "id") do update
417
- set ${sql.identifier(column)} = ${value}
418
- where not exists (select 1 from existingTimestamp);
419
- `);
420
- if (!updateAppTable.ok)
421
- return updateAppTable;
422
- const insertHistory = deps.sqlite.exec(sql.prepared `
423
- insert into evolu_history
424
- ("ownerId", "table", "id", "column", "value", "timestamp")
425
- values
426
- (
427
- ${ownerIdBytes},
428
- ${change.table},
429
- ${idBytes},
430
- ${column},
431
- ${value},
432
- ${timestampBytes}
433
- )
434
- on conflict do nothing;
435
- `);
436
- if (!insertHistory.ok)
437
- return insertHistory;
404
+ if (validateColumnValue(deps)(change.table, column, value)) {
405
+ const result = applyColumnChange(deps)(ownerIdBytes, ownerId, change.table, idBytes, change.id, column, value, timestampBytes);
406
+ if (!result.ok)
407
+ return result;
408
+ }
409
+ else {
410
+ const result = deps.sqlite.exec(sql.prepared `
411
+ insert into evolu_message_quarantine
412
+ ("ownerId", "timestamp", "table", "id", "column", "value")
413
+ values
414
+ (
415
+ ${ownerIdBytes},
416
+ ${timestampBytes},
417
+ ${change.table},
418
+ ${idBytes},
419
+ ${column},
420
+ ${value}
421
+ )
422
+ on conflict do nothing;
423
+ `);
424
+ if (!result.ok)
425
+ return result;
426
+ }
438
427
  }
439
428
  let strategy;
440
429
  [strategy, firstTimestamp, lastTimestamp] = getTimestampInsertStrategy(timestampBytes, firstTimestamp, lastTimestamp);
441
- const insertTimestamp = deps.storage.insertTimestamp(ownerIdBytes, timestampBytes, strategy);
442
- if (!insertTimestamp.ok)
443
- return insertTimestamp;
430
+ const result = deps.storage.insertTimestamp(ownerIdBytes, timestampBytes, strategy);
431
+ if (!result.ok)
432
+ return result;
444
433
  }
445
434
  /**
446
- * TODO: Implement proper storedBytes tracking for client using encrypted
447
- * message sizes (need to figure out how to reuse received or postpone
448
- * client...).
435
+ * TODO: Implement proper storedBytes tracking for client using received and
436
+ * sent encrypted message sizes.
449
437
  */
450
- const updateUsage = updateOwnerUsage(deps)(ownerIdBytes, 1, // Placeholder until proper tracking implemented
438
+ return updateOwnerUsage(deps)(ownerIdBytes, 1, // Placeholder until proper tracking implemented
451
439
  firstTimestamp, lastTimestamp);
452
- if (!updateUsage.ok)
453
- return updateUsage;
440
+ };
441
+ /**
442
+ * System columns that can appear in sync messages. Excludes `ownerId` because
443
+ * it's handled separately (stored per-row, not per-column in messages).
444
+ */
445
+ const systemColumnsWithoutOwnerId = systemColumns.difference(new Set(["ownerId"]));
446
+ const validateColumnValue = (deps) => (table, column, _value) => {
447
+ const schemaColumns = getProperty(deps.dbSchema.tables, table);
448
+ return (schemaColumns != null &&
449
+ (systemColumnsWithoutOwnerId.has(column) || schemaColumns.has(column)));
450
+ };
451
+ const applyColumnChange = (deps) => (ownerIdBytes, ownerId, table, idBytes, id, column, value, timestampBytes) => {
452
+ const result = deps.sqlite.exec(sql.prepared `
453
+ with
454
+ existingTimestamp as (
455
+ select 1
456
+ from evolu_history
457
+ where
458
+ "ownerId" = ${ownerIdBytes}
459
+ and "table" = ${table}
460
+ and "id" = ${idBytes}
461
+ and "column" = ${column}
462
+ and "timestamp" >= ${timestampBytes}
463
+ limit 1
464
+ )
465
+ insert into ${sql.identifier(table)}
466
+ ("ownerId", "id", ${sql.identifier(column)})
467
+ select ${ownerId}, ${id}, ${value}
468
+ where not exists (select 1 from existingTimestamp)
469
+ on conflict ("ownerId", "id") do update
470
+ set ${sql.identifier(column)} = ${value}
471
+ where not exists (select 1 from existingTimestamp);
472
+ `);
473
+ if (!result.ok)
474
+ return result;
475
+ {
476
+ const result = deps.sqlite.exec(sql.prepared `
477
+ insert into evolu_history
478
+ ("ownerId", "table", "id", "column", "value", "timestamp")
479
+ values
480
+ (
481
+ ${ownerIdBytes},
482
+ ${table},
483
+ ${idBytes},
484
+ ${column},
485
+ ${value},
486
+ ${timestampBytes}
487
+ )
488
+ on conflict do nothing;
489
+ `);
490
+ if (!result.ok)
491
+ return result;
492
+ }
493
+ return ok();
494
+ };
495
+ /**
496
+ * Attempts to apply quarantined messages that may now be valid after a schema
497
+ * update. Messages are quarantined when they reference tables or columns that
498
+ * don't exist in the current schema (e.g., from a newer app version).
499
+ */
500
+ export const tryApplyQuarantinedMessages = (deps) => () => {
501
+ const rows = deps.sqlite.exec(sql `
502
+ select "ownerId", "timestamp", "table", "id", "column", "value"
503
+ from evolu_message_quarantine;
504
+ `);
505
+ if (!rows.ok)
506
+ return rows;
507
+ for (const row of rows.value.rows) {
508
+ if (!validateColumnValue(deps)(row.table, row.column, row.value))
509
+ continue;
510
+ const result = applyColumnChange(deps)(row.ownerId, ownerIdBytesToOwnerId(row.ownerId), row.table, row.id, idBytesToId(row.id), row.column, row.value, row.timestamp);
511
+ if (!result.ok)
512
+ return result;
513
+ {
514
+ const result = deps.sqlite.exec(sql `
515
+ delete from evolu_message_quarantine
516
+ where
517
+ "ownerId" = ${row.ownerId}
518
+ and "timestamp" = ${row.timestamp}
519
+ and "table" = ${row.table}
520
+ and "id" = ${row.id}
521
+ and "column" = ${row.column};
522
+ `);
523
+ if (!result.ok)
524
+ return result;
525
+ }
526
+ }
454
527
  return ok();
455
528
  };
456
529
  export const initialSyncState = { type: "SyncStateInitial" };
@@ -1,10 +1,17 @@
1
1
  /**
2
- * Local-first code to be imported from "@evolu/common/local-first"
2
+ * Internal local-first modules re-exported from "@evolu/common/local-first"
3
+ *
4
+ * These exports expose the internal building blocks of Evolu's local-first
5
+ * implementation (Db, Relay, Sync, Query, Storage, Timestamp, etc.). They are
6
+ * primarily intended for use within Evolu packages and for advanced use-cases
7
+ * that require direct access to local-first internals.
8
+ *
9
+ * Public consumers should us top-level exports from `@evolu/common` unless you
10
+ * have a specific need to import these internals.
3
11
  *
4
12
  * @module
5
13
  */
6
14
  export * from "./Db.js";
7
- export * from "./Diff.js";
8
15
  export * from "./Evolu.js";
9
16
  export * from "./Owner.js";
10
17
  export * from "./Platform.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/local-first/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/local-first/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC"}
@@ -1,10 +1,17 @@
1
1
  /**
2
- * Local-first code to be imported from "@evolu/common/local-first"
2
+ * Internal local-first modules re-exported from "@evolu/common/local-first"
3
+ *
4
+ * These exports expose the internal building blocks of Evolu's local-first
5
+ * implementation (Db, Relay, Sync, Query, Storage, Timestamp, etc.). They are
6
+ * primarily intended for use within Evolu packages and for advanced use-cases
7
+ * that require direct access to local-first internals.
8
+ *
9
+ * Public consumers should us top-level exports from `@evolu/common` unless you
10
+ * have a specific need to import these internals.
3
11
  *
4
12
  * @module
5
13
  */
6
14
  export * from "./Db.js";
7
- export * from "./Diff.js";
8
15
  export * from "./Evolu.js";
9
16
  export * from "./Owner.js";
10
17
  export * from "./Platform.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "7.2.3",
3
+ "version": "7.4.0",
4
4
  "description": "TypeScript library and local-first platform",
5
5
  "keywords": [
6
6
  "evolu",
@@ -56,7 +56,6 @@
56
56
  "@types/ws": "^8.18.1",
57
57
  "better-sqlite3": "^12.1.1",
58
58
  "fast-check": "^4.2.0",
59
- "shx": "^0.4.0",
60
59
  "typescript": "^5.9.2",
61
60
  "vitest": "^4.0.4",
62
61
  "ws": "^8.18.2",
@@ -71,10 +70,10 @@
71
70
  "sideEffects": [],
72
71
  "scripts": {
73
72
  "dev": "tsc --watch",
74
- "build": "shx rm -rf dist ./README.md && tsc && shx cp ../../README.md ./",
73
+ "build": "rimraf dist && tsc",
75
74
  "test": "vitest run --disableConsoleIntercept",
76
75
  "test:watch": "vitest --disableConsoleIntercept",
77
- "clean": "shx rm -rf .turbo node_modules dist",
76
+ "clean": "rimraf .turbo node_modules dist",
78
77
  "format": "prettier --write \"src/*.{ts,tsx,md}\"",
79
78
  "bench": "vitest bench"
80
79
  }
package/src/Buffer.ts CHANGED
@@ -63,7 +63,7 @@ export class BufferError extends Error {
63
63
  * https://blog.mozilla.org/nnethercote/2014/11/04/please-grow-your-buffers-exponentially
64
64
  */
65
65
  export interface Buffer {
66
- /** Returns the total allocated size of the buffer. */
66
+ /** Returns the current capacity of the buffer. */
67
67
  getCapacity: () => NonNegativeInt;
68
68
 
69
69
  /** Returns the current number of bytes stored in the buffer. */
@@ -110,6 +110,7 @@ export interface Buffer {
110
110
  unwrap: () => Uint8Array;
111
111
  }
112
112
 
113
+ /** Creates a {@link Buffer} for efficient byte operations. */
113
114
  export const createBuffer = (
114
115
  arrayLike?: Uint8Array | ArrayLike<number>,
115
116
  ): Buffer => {
package/src/Callbacks.ts CHANGED
@@ -55,7 +55,7 @@ export interface Callbacks<T = undefined> {
55
55
  /** Unique identifier for a callback in {@link Callbacks}. */
56
56
  export type CallbackId = Id & Brand<"Callback">;
57
57
 
58
- /** Creates a new {@link Callbacks}. */
58
+ /** Creates a {@link Callbacks} registry for managing callbacks. */
59
59
  export const createCallbacks = <T = undefined>(
60
60
  deps: RandomBytesDep,
61
61
  ): Callbacks<T> => {
package/src/Console.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  /**
2
- * Cross-platform console
3
- *
4
2
  * Console abstraction for Chrome 123+, Firefox 125+, Safari 18.1+, Node.js
5
3
  * 22.x+, and React Native 0.75+. Includes methods guaranteed to be available in
6
4
  * these environments and expected to remain compatible in future versions.
@@ -35,13 +33,6 @@
35
33
  * }
36
34
  * }
37
35
  * ```
38
- *
39
- * @module
40
- */
41
-
42
- /**
43
- * Cross-platform Console interface for Chrome 123+, Firefox 125+, Safari 18.1+,
44
- * Node.js 22.x+, and React Native 0.75+
45
36
  */
46
37
  export interface Console {
47
38
  /** Controls whether console methods produce output (default: true) */
@@ -105,7 +96,7 @@ export interface ConsoleConfig {
105
96
  readonly enableLogging?: boolean;
106
97
  }
107
98
 
108
- /** Creates a console instance using the global console. */
99
+ /** Creates a {@link Console} for logging with configurable output. */
109
100
  export const createConsole = (config: ConsoleConfig = {}): Console => {
110
101
  const instance: Console = {
111
102
  enabled: config.enableLogging ?? false,
package/src/Function.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.js";
2
+ import { ReadonlyRecord } from "./Object.js";
3
+
1
4
  /**
2
5
  * Helper function to ensure exhaustive matching in a switch statement. Throws
3
6
  * an error if an unhandled case is encountered.
@@ -31,8 +34,79 @@ export const exhaustiveCheck = (value: never): never => {
31
34
  throw new Error(`exhaustiveCheck unhandled case: ${JSON.stringify(value)}`);
32
35
  };
33
36
 
37
+ /**
38
+ * Returns the input value unchanged.
39
+ *
40
+ * Useful as a default transformation, placeholder callback, or when a function
41
+ * is required but no transformation is needed.
42
+ *
43
+ * ### Example
44
+ *
45
+ * ```ts
46
+ * const values = [1, 2, 3];
47
+ * const same = values.map(identity); // [1, 2, 3]
48
+ *
49
+ * const getTransform = (shouldDouble: boolean) =>
50
+ * shouldDouble ? (x: number) => x * 2 : identity;
51
+ * ```
52
+ */
34
53
  export const identity = <A>(a: A): A => a;
35
54
 
55
+ /**
56
+ * Casts an array, set, record, or map to its readonly counterpart.
57
+ *
58
+ * Zero runtime cost — returns the same value with a readonly type. Use this to
59
+ * enforce immutability at the type level. Preserves {@link NonEmptyArray} as
60
+ * {@link NonEmptyReadonlyArray}.
61
+ *
62
+ * ### Example
63
+ *
64
+ * ```ts
65
+ * // Array literals become NonEmptyReadonlyArray
66
+ * const items = readonly([1, 2, 3]);
67
+ * // Type: NonEmptyReadonlyArray<number>
68
+ *
69
+ * // NonEmptyArray is preserved as NonEmptyReadonlyArray
70
+ * const nonEmpty: NonEmptyArray<number> = [1, 2, 3];
71
+ * const readonlyNonEmpty = readonly(nonEmpty);
72
+ * // Type: NonEmptyReadonlyArray<number>
73
+ *
74
+ * // Regular arrays become ReadonlyArray
75
+ * const arr: Array<number> = getNumbers();
76
+ * const readonlyArr = readonly(arr);
77
+ * // Type: ReadonlyArray<number>
78
+ *
79
+ * // Sets, Records, and Maps
80
+ * const ids = readonly(new Set(["a", "b"]));
81
+ * // Type: ReadonlySet<string>
82
+ *
83
+ * const users: Record<UserId, string> = { ... };
84
+ * const readonlyUsers = readonly(users);
85
+ * // Type: ReadonlyRecord<UserId, string>
86
+ *
87
+ * const lookup = readonly(new Map([["key", "value"]]));
88
+ * // Type: ReadonlyMap<string, string>
89
+ * ```
90
+ *
91
+ * @experimental
92
+ */
93
+ export function readonly<T>(array: NonEmptyArray<T>): NonEmptyReadonlyArray<T>;
94
+ export function readonly<T>(array: Array<T>): ReadonlyArray<T>;
95
+ export function readonly<T>(set: Set<T>): ReadonlySet<T>;
96
+ export function readonly<K, V>(map: Map<K, V>): ReadonlyMap<K, V>;
97
+ export function readonly<K extends keyof any, V>(
98
+ record: Record<K, V>,
99
+ ): ReadonlyRecord<K, V>;
100
+ export function readonly<T, K extends keyof any, V>(
101
+ value: Array<T> | Set<T> | Map<K, V> | Record<K, V>,
102
+ ):
103
+ | ReadonlyArray<T>
104
+ | ReadonlySet<T>
105
+ | ReadonlyMap<K, V>
106
+ | ReadonlyRecord<K, V> {
107
+ return value;
108
+ }
109
+
36
110
  /**
37
111
  * A function that delays computation and returns a value of type T.
38
112
  *
package/src/Object.ts CHANGED
@@ -89,3 +89,23 @@ export const createRecord = <K extends string = string, V = unknown>(): Record<
89
89
  K,
90
90
  V
91
91
  > => Object.create(null) as Record<K, V>;
92
+
93
+ /**
94
+ * Safely gets a property from a record, returning `undefined` if the key
95
+ * doesn't exist.
96
+ *
97
+ * TypeScript's `Record<K, V>` type assumes all keys exist, but at runtime
98
+ * accessing a non-existent key returns `undefined`. This helper provides proper
99
+ * typing for that case without needing a type assertion.
100
+ *
101
+ * ### Example
102
+ *
103
+ * ```ts
104
+ * const users: Record<string, User> = { alice: { name: "Alice" } };
105
+ * const user = getProperty(users, "bob"); // User | undefined
106
+ * ```
107
+ */
108
+ export const getProperty = <K extends string, V>(
109
+ record: ReadonlyRecord<K, V>,
110
+ key: string,
111
+ ): V | undefined => (key in record ? record[key as K] : undefined);