@evolu/common 6.0.1-preview.21 → 6.0.1-preview.23

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.
@@ -6,7 +6,6 @@ import { StoreSubscribe } from "../Store.js";
6
6
  import { TimeDep } from "../Time.js";
7
7
  import { Mnemonic } from "../Type.js";
8
8
  import { CreateDbWorkerDep, DbConfig } from "./Db.js";
9
- import { LocalAuthDep } from "./LocalAuth.js";
10
9
  import { AppOwner } from "./Owner.js";
11
10
  import { FlushSyncDep, ReloadAppDep } from "./Platform.js";
12
11
  import { ProtocolError, ProtocolUnsupportedVersionError } from "./Protocol.js";
@@ -92,13 +91,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
92
91
  * reason why loading should fail. All data are local, and the query is typed.
93
92
  * Unexpected errors are handled with {@link Evolu#subscribeError}.
94
93
  *
95
- * Loading is batched, and returned promises are cached, so there is no need
96
- * for an additional cache. Evolu's internal cache is invalidated on mutation.
97
- * Unsubscribed queries are removed from the cache, so loading them again will
98
- * return a new pending promise. Subscribed queries remain in the cache to
99
- * prevent unnecessary Suspense boundaries from activating. Their promises are
100
- * replaced with `Promise.resolve(rows)`, allowing React to synchronously
101
- * unwrap the updated data without suspending.
94
+ * Loading is batched, and returned promises are cached until resolved to
95
+ * prevent redundant database queries and to support React Suspense (which
96
+ * requires stable promise references while pending).
102
97
  *
103
98
  * To subscribe a query for automatic updates, use
104
99
  * {@link Evolu#subscribeQuery}.
@@ -154,6 +149,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
154
149
  /**
155
150
  * Promise that resolves to {@link AppOwner} when available.
156
151
  *
152
+ * Note: With web-only deps, this promise will not resolve during SSR because
153
+ * there is no AppOwner on the server.
154
+ *
157
155
  * ### Example
158
156
  *
159
157
  * ```ts
@@ -365,26 +363,11 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
365
363
  }
366
364
  /** Represents errors that can occur in Evolu. */
367
365
  export type EvoluError = ProtocolError | ProtocolUnsupportedVersionError | SqliteError | SymmetricCryptoDecryptError | TimestampError | TransferableError;
368
- export type EvoluDeps = ConsoleDep & CreateDbWorkerDep & LocalAuthDep & Partial<FlushSyncDep> & RandomBytesDep & ReloadAppDep & TimeDep;
366
+ export type EvoluDeps = ConsoleDep & CreateDbWorkerDep & Partial<FlushSyncDep> & RandomBytesDep & ReloadAppDep & TimeDep;
369
367
  /**
370
- * Creates an {@link Evolu} instance configured with the specified
371
- * {@link EvoluSchema} and optional configuration.
372
- *
373
- * This function returns a configured Evolu instance, providing a typed
374
- * interface for querying, mutating, and syncing your application's data. The
375
- * returned instance includes:
376
- *
377
- * - Subscription methods for receiving updates on queries, the owner, errors, and
378
- * sync state.
379
- * - Methods for creating, updating, or deleting rows in a type-safe manner.
380
- * - Methods for querying data using Evolu's typed SQL queries, leveraging Kysely
381
- * under the hood.
382
- * - Built-in support for local-first and offline-first data with automatic sync
383
- * and merging.
384
- * - Automatic schema evolution that updates the underlying database with new
385
- * columns or tables.
386
- * - Managing owner data with {@link Evolu#resetAppOwner} and
387
- * {@link Evolu#restoreAppOwner}.
368
+ * Creates an {@link Evolu} instance for a platform configured with the specified
369
+ * {@link EvoluSchema} and optional {@link EvoluConfig} providing a typed
370
+ * interface for querying, mutating, and syncing your application's data.
388
371
  *
389
372
  * ### Example
390
373
  *
@@ -413,6 +396,18 @@ export type EvoluDeps = ConsoleDep & CreateDbWorkerDep & LocalAuthDep & Partial<
413
396
  *
414
397
  * const evolu = createEvolu(evoluReactDeps)(Schema);
415
398
  * ```
399
+ *
400
+ * ### Instance Caching
401
+ *
402
+ * Evolu caches instances by {@link EvoluConfig} name to enable hot reloading and
403
+ * multitenancy. Multiple calls to `createEvolu` with the same name return the
404
+ * same instance, preserving database connections and state across module
405
+ * reloads during development. This ensures a seamless developer experience
406
+ * where edits don't interrupt ongoing sync or lose in-memory state.
407
+ *
408
+ * For testing, either dispose of instances after each test (TODO: implement
409
+ * dispose method) or use unique instance names to ensure proper isolation
410
+ * between test cases.
416
411
  */
417
412
  export declare const createEvolu: (deps: EvoluDeps) => <S extends EvoluSchema>(schema: ValidateSchema<S> extends never ? S : ValidateSchema<S>, config?: EvoluConfig) => Evolu<S>;
418
413
  export declare const createQuery: <R extends Row>(queryCallback: Parameters<CreateQuery<EvoluSchema>>[0], options?: Parameters<CreateQuery<EvoluSchema>>[1]) => Query<R>;
@@ -1 +1 @@
1
- {"version":3,"file":"Evolu.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Evolu.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Evolu.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Evolu.ts"],"names":[],"mappings":"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"}
@@ -14,28 +14,12 @@ import { createSubscribedQueries, emptyRows, serializeQuery, } from "./Query.js"
14
14
  import { evoluSchemaToDbSchema, insertable, kysely, updateable, upsertable, } from "./Schema.js";
15
15
  import { DbChange } from "./Storage.js";
16
16
  import { initialSyncState } from "./Sync.js";
17
- // For hot reloading and Evolu multitenancy.
18
17
  const evoluInstances = new Map();
19
18
  let tabId = null;
20
19
  /**
21
- * Creates an {@link Evolu} instance configured with the specified
22
- * {@link EvoluSchema} and optional configuration.
23
- *
24
- * This function returns a configured Evolu instance, providing a typed
25
- * interface for querying, mutating, and syncing your application's data. The
26
- * returned instance includes:
27
- *
28
- * - Subscription methods for receiving updates on queries, the owner, errors, and
29
- * sync state.
30
- * - Methods for creating, updating, or deleting rows in a type-safe manner.
31
- * - Methods for querying data using Evolu's typed SQL queries, leveraging Kysely
32
- * under the hood.
33
- * - Built-in support for local-first and offline-first data with automatic sync
34
- * and merging.
35
- * - Automatic schema evolution that updates the underlying database with new
36
- * columns or tables.
37
- * - Managing owner data with {@link Evolu#resetAppOwner} and
38
- * {@link Evolu#restoreAppOwner}.
20
+ * Creates an {@link Evolu} instance for a platform configured with the specified
21
+ * {@link EvoluSchema} and optional {@link EvoluConfig} providing a typed
22
+ * interface for querying, mutating, and syncing your application's data.
39
23
  *
40
24
  * ### Example
41
25
  *
@@ -64,6 +48,18 @@ let tabId = null;
64
48
  *
65
49
  * const evolu = createEvolu(evoluReactDeps)(Schema);
66
50
  * ```
51
+ *
52
+ * ### Instance Caching
53
+ *
54
+ * Evolu caches instances by {@link EvoluConfig} name to enable hot reloading and
55
+ * multitenancy. Multiple calls to `createEvolu` with the same name return the
56
+ * same instance, preserving database connections and state across module
57
+ * reloads during development. This ensures a seamless developer experience
58
+ * where edits don't interrupt ongoing sync or lose in-memory state.
59
+ *
60
+ * For testing, either dispose of instances after each test (TODO: implement
61
+ * dispose method) or use unique instance names to ensure proper isolation
62
+ * between test cases.
67
63
  */
68
64
  export const createEvolu = (deps) => (schema, config) => {
69
65
  const name = config?.name ?? defaultDbConfig.name;
@@ -102,39 +98,6 @@ const createEvoluInstance = (deps) => (schema, config) => {
102
98
  tabId ??= createId(deps);
103
99
  return tabId;
104
100
  };
105
- // const createLocalOnly = (
106
- // localMutations: Array<MutationChange>,
107
- // defaultOwnerId: OwnerId | undefined,
108
- // ): LocalOnly<EvoluSchema> => ({
109
- // insert: (table, values) => {
110
- // const id = createId(deps);
111
- // localMutations.push({
112
- // table,
113
- // id,
114
- // values,
115
- // ownerId: defaultOwnerId,
116
- // });
117
- // return id;
118
- // },
119
- // update: (table, values) => {
120
- // const { id, ...rest } = values;
121
- // localMutations.push({
122
- // table,
123
- // id: id as Id,
124
- // values: rest,
125
- // ownerId: defaultOwnerId,
126
- // });
127
- // },
128
- // upsert: (table, values) => {
129
- // const { id, ...rest } = values as Record<string, unknown> & { id: Id };
130
- // localMutations.push({
131
- // table,
132
- // id: id,
133
- // values: rest as MutationChange["values"],
134
- // ownerId: defaultOwnerId,
135
- // });
136
- // },
137
- // });
138
101
  // Worker responses are delivered to all tabs. Each case must handle this
139
102
  // properly (e.g., AppOwner promise resolves only once, tabId filtering).
140
103
  dbWorker.onMessage((message) => {
@@ -197,66 +160,6 @@ const createEvoluInstance = (deps) => (schema, config) => {
197
160
  }
198
161
  break;
199
162
  }
200
- // case "processNewMessages": {
201
- // void requestIdleTask(
202
- // toTask(async () => {
203
- // const approved: Array<Timestamp> = [];
204
- // const invalidChanges: Array<DbChange> = [];
205
- // const rejectedChanges: Array<DbChange> = [];
206
- // const localMutations: Array<MutationChange> = [];
207
- // for (const crdtMessage of message.messages) {
208
- // let isApproved = true;
209
- // let isValid = true;
210
- // const table = crdtMessage.change.table;
211
- // if (table in schema) {
212
- // const { createdAt, ...values } = crdtMessage.change.values;
213
- // isValid =
214
- // (createdAt ? DateIso.is(createdAt) : true) &&
215
- // getMutationType(table, "update").is({
216
- // id: crdtMessage.change.id,
217
- // ...values,
218
- // });
219
- // } else {
220
- // isValid = false;
221
- // }
222
- // if (!isValid) {
223
- // isApproved = false;
224
- // invalidChanges.push(crdtMessage.change);
225
- // } else if (onMessage) {
226
- // // At this point, we've validated that the message conforms to the
227
- // // schema, so the typed callback can safely process it.
228
- // isApproved = await onMessage(crdtMessage.change, {
229
- // ownerId: message.ownerId,
230
- // localOnly: createLocalOnly(localMutations, message.ownerId),
231
- // });
232
- // if (!isApproved) {
233
- // rejectedChanges.push(crdtMessage.change);
234
- // }
235
- // }
236
- // if (isApproved) {
237
- // approved.push(crdtMessage.timestamp);
238
- // }
239
- // }
240
- // // Report OnMessageError if there were any invalid or rejected changes
241
- // if (invalidChanges.length > 0 || rejectedChanges.length > 0) {
242
- // const onMessageError: OnMessageError = {
243
- // type: "OnMessageError",
244
- // invalidChanges,
245
- // rejectedChanges,
246
- // };
247
- // errorStore.set(onMessageError);
248
- // }
249
- // dbWorker.postMessage({
250
- // type: "onProcessNewMessages",
251
- // onCompleteId: message.onCompleteId,
252
- // approved,
253
- // localMutations,
254
- // });
255
- // return ok();
256
- // }),
257
- // )();
258
- // break;
259
- // }
260
163
  case "onExport": {
261
164
  exportRegistry.execute(message.onCompleteId, message.file);
262
165
  break;
@@ -339,29 +242,6 @@ const createEvoluInstance = (deps) => (schema, config) => {
339
242
  loadingPromises.releaseUnsubscribedOnMutation();
340
243
  if (!isNonEmptyArray(changes))
341
244
  return;
342
- // if (onMessage) {
343
- // const rejectedChanges: Array<DbChange> = [];
344
- // const localMutations: Array<MutationChange> = [];
345
- // for (const change of changes) {
346
- // const localOnly = createLocalOnly(localMutations, change.ownerId);
347
- // const isApproved = await onMessage(change, {
348
- // ownerId: change.ownerId,
349
- // localOnly,
350
- // });
351
- // if (!isApproved) {
352
- // rejectedChanges.push(change);
353
- // }
354
- // }
355
- // if (rejectedChanges.length > 0) {
356
- // errorStore.set({
357
- // type: "OnMessageError",
358
- // invalidChanges: [],
359
- // rejectedChanges,
360
- // });
361
- // return;
362
- // }
363
- // changes.push(...localMutations);
364
- // }
365
245
  dbWorker.postMessage({
366
246
  type: "mutate",
367
247
  tabId: getTabId(),
@@ -1,23 +1,25 @@
1
1
  import { RandomBytesDep } from "../Crypto.js";
2
2
  import { Mnemonic } from "../Type.js";
3
3
  import { AppOwner, OwnerId } from "./Owner.js";
4
- /** @experimental */
4
+ /**
5
+ * Local authentication and authorization system for Evolu.
6
+ * This is API is subject to change and not recommended for production use.
7
+ *
8
+ * @experimental
9
+ */
5
10
  export interface LocalAuth {
6
11
  /** Logs in with the given owner ID, or loads the target owner if not provided. */
7
- login: (ownerId?: OwnerId, options?: LocalAuthOptions & {
8
- reloadNeeded?: boolean;
9
- }) => Promise<AuthResult | null>;
12
+ login: (ownerId: OwnerId, options?: LocalAuthOptions) => Promise<AuthResult | null>;
10
13
  /** Registers a new owner with the given username. */
11
14
  register: (username: string, options?: LocalAuthOptions & {
12
15
  mnemonic?: Mnemonic | null;
13
16
  }) => Promise<AuthResult | null>;
14
17
  /** Unregisters an owner with the given owner ID. */
15
18
  unregister: (ownerId: OwnerId, options?: LocalAuthOptions) => Promise<void>;
19
+ /** Gets the current owner (last logged in or last registered owner) */
20
+ getOwner: (options?: LocalAuthOptions) => Promise<AuthResult | null>;
16
21
  /** Lists all registered owner ids with associated usernames. */
17
- getProfiles: (options?: LocalAuthOptionsValues) => Promise<Array<{
18
- ownerId: OwnerId;
19
- username: string;
20
- }>>;
22
+ getProfiles: (options?: LocalAuthOptionsValues) => Promise<Array<AuthList>>;
21
23
  /** Clears all owners and metadata from the local auth. */
22
24
  clearAll: (options?: LocalAuthOptions) => Promise<void>;
23
25
  }
@@ -50,11 +52,17 @@ export declare const AUTH_METAKEY_LAST_OWNER = "_last_owner";
50
52
  export declare const AUTH_METAKEY_OWNER_NAMES = "_owner_names";
51
53
  export declare const AUTH_DEFAULT_OPTIONS: LocalAuthOptions;
52
54
  export interface AuthResult {
53
- /** The owner created during registration. */
55
+ /** The app owner created during registration. */
54
56
  readonly owner: AppOwner | undefined;
55
57
  /** The name provided by the user during registration. */
56
58
  readonly username: string;
57
59
  }
60
+ export interface AuthList {
61
+ /** The app owner ID. */
62
+ readonly ownerId: OwnerId;
63
+ /** The name provided by the user during registration. */
64
+ readonly username: string;
65
+ }
58
66
  export interface LocalAuthOptions {
59
67
  /**
60
68
  * Native: Namespaces the stored entry. Defaults to the bundle identifier
@@ -1 +1 @@
1
- {"version":3,"file":"LocalAuth.d.ts","sourceRoot":"","sources":["../../../src/Evolu/LocalAuth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LocalAuth.d.ts","sourceRoot":"","sources":["../../../src/Evolu/LocalAuth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EACL,QAAQ,EAKR,OAAO,EAER,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,KAAK,EAAE,CACL,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,gBAAgB,KACvB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAEhC,qDAAqD;IACrD,QAAQ,EAAE,CACR,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,gBAAgB,GAAG;QAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;KAAE,KACxD,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAEhC,oDAAoD;IACpD,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5E,uEAAuE;IACvE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAErE,gEAAgE;IAChE,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,sBAAsB,KAAK,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE5E,0DAA0D;IAC1D,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CACP,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,gBAAgB,KACvB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,OAAO,EAAE,CACP,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,sBAAsB,KAC7B,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1E,WAAW,EAAE,CACX,OAAO,CAAC,EAAE,sBAAsB,KAC7B,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACvC,YAAY,EAAE,CAAC,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;CACvC;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAC1B,MAAM,gBAAgB,GAAG,cAAc,KACtC,SA0NF,CAAC;AAGF,eAAO,MAAM,cAAc,UAAU,CAAC;AACtC,eAAO,MAAM,uBAAuB,gBAAgB,CAAC;AACrD,eAAO,MAAM,wBAAwB,iBAAiB,CAAC;AACvD,eAAO,MAAM,oBAAoB,EAAE,gBASlC,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IACrC,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,wBAAwB;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAID,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B,4DAA4D;IAC5D,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAErC,yCAAyC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAEhC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IAEvC;;;OAGG;IACH,QAAQ,CAAC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IAE/C;;;OAGG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAErD,gFAAgF;IAChF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAEjC,qEAAqE;IACrE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAEnC,gEAAgE;IAChE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAEnC;;;OAGG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,2BAA2B,CAAC;IAEhE,8EAA8E;IAC9E,QAAQ,CAAC,+BAA+B,CAAC,EAAE,uBAAuB,CAAC;CACpE;AAED,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,gFAAgF;IAChF,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB,eAAe,GACf,WAAW,GACX,UAAU,GACV,kBAAkB,GAClB,UAAU,CAAC;AAEf,4EAA4E;AAC5E,MAAM,MAAM,cAAc,GACtB,UAAU,GACV,iBAAiB,GACjB,4BAA4B,CAAC;AAEjC;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB,uBAAuB,GACvB,oBAAoB,GACpB,aAAa,GACb,gBAAgB,GAChB,MAAM,CAAC"}
@@ -7,23 +7,20 @@ import { createAppOwner, createOwnerSecret, mnemonicToOwnerSecret, OwnerEncrypti
7
7
  export const createLocalAuth = (deps) => {
8
8
  const setLastOwnerId = async (id, options) => {
9
9
  await deps.secureStorage.setItem(AUTH_METAKEY_LAST_OWNER, id, {
10
- ...AUTH_DEFAULT_OPTIONS,
11
- ...options,
10
+ ...buildAuthOptions(options),
12
11
  accessControl: "none",
13
12
  });
14
13
  };
15
14
  const getLastOwnerId = async (options) => {
16
15
  const item = await deps.secureStorage.getItem(AUTH_METAKEY_LAST_OWNER, {
17
- ...AUTH_DEFAULT_OPTIONS,
18
- ...options,
16
+ ...buildAuthOptions(options),
19
17
  accessControl: "none",
20
18
  });
21
19
  return item?.value;
22
20
  };
23
21
  const getOwnerNames = async (options) => {
24
22
  const item = await deps.secureStorage.getItem(AUTH_METAKEY_OWNER_NAMES, {
25
- ...AUTH_DEFAULT_OPTIONS,
26
- ...options,
23
+ ...buildAuthOptions(options),
27
24
  accessControl: "none",
28
25
  });
29
26
  let names = {};
@@ -36,23 +33,20 @@ export const createLocalAuth = (deps) => {
36
33
  const names = await getOwnerNames(options);
37
34
  names[id] = username;
38
35
  await deps.secureStorage.setItem(AUTH_METAKEY_OWNER_NAMES, JSON.stringify(names), {
39
- ...AUTH_DEFAULT_OPTIONS,
40
- ...options,
36
+ ...buildAuthOptions(options),
41
37
  accessControl: "none",
42
38
  });
43
39
  };
44
40
  const deleteOwnerName = async (id, options) => {
45
41
  const { [id]: _, ...names } = await getOwnerNames(options);
46
42
  await deps.secureStorage.setItem(AUTH_METAKEY_OWNER_NAMES, JSON.stringify(names), {
47
- ...AUTH_DEFAULT_OPTIONS,
48
- ...options,
43
+ ...buildAuthOptions(options),
49
44
  accessControl: "none",
50
45
  });
51
46
  };
52
47
  const getOwnerIds = async (options) => {
53
48
  const items = await deps.secureStorage.getAllItems({
54
- ...AUTH_DEFAULT_OPTIONS,
55
- ...options,
49
+ ...buildAuthOptions(options),
56
50
  includeValues: false,
57
51
  });
58
52
  return items
@@ -61,42 +55,39 @@ export const createLocalAuth = (deps) => {
61
55
  i.key !== AUTH_METAKEY_OWNER_NAMES)
62
56
  .map((i) => i.key);
63
57
  };
64
- const clearAuthStore = (options) => deps.secureStorage.clearService({
65
- ...AUTH_DEFAULT_OPTIONS,
66
- ...options,
67
- });
58
+ const clearAuthStore = (options) => deps.secureStorage.clearService(buildAuthOptions(options));
59
+ const buildAuthOptions = (options, username) => {
60
+ const newOptions = {
61
+ ...AUTH_DEFAULT_OPTIONS,
62
+ ...(username && { webAuthnUsername: username }),
63
+ ...options,
64
+ };
65
+ return {
66
+ ...newOptions,
67
+ authenticationPrompt: {
68
+ title: replaceMessageTokens(newOptions.authenticationPrompt?.title ?? "", username),
69
+ cancel: replaceMessageTokens(newOptions.authenticationPrompt?.cancel ?? "", username),
70
+ subtitle: replaceMessageTokens(newOptions.authenticationPrompt?.subtitle ?? "", username),
71
+ description: replaceMessageTokens(newOptions.authenticationPrompt?.description ?? "", username),
72
+ },
73
+ };
74
+ };
75
+ const replaceMessageTokens = (text, username) => {
76
+ if (!username)
77
+ return text;
78
+ return text.replace("|USERNAME|", username);
79
+ };
68
80
  return {
69
81
  login: async (ownerId, options) => {
70
- // Use either specified owner or the last owner used during registration/login.
71
- const targetOwnerId = ownerId ?? (await getLastOwnerId(options));
72
- if (!targetOwnerId)
73
- return null;
74
82
  // Lookup the associated username
75
83
  const names = await getOwnerNames(options);
76
- const username = names[targetOwnerId] ?? "";
77
- // If a reload is needed, avoid authentication, it needs to be handled on next page load.
84
+ const username = names[ownerId] ?? "";
85
+ // Currently a reload is needed. This avoids authentication
86
+ // it needs to be handled on next page load.
78
87
  // We set the last owner so we know what the target is.
79
88
  // It is the applications's responsibility to reload and trigger login.
80
- if (options?.reloadNeeded) {
81
- await setLastOwnerId(targetOwnerId, options);
82
- return { owner: undefined, username };
83
- }
84
- // Retrieve and decrypt the owner (this will trigger device authentication)
85
- const account = await deps.secureStorage.getItem(targetOwnerId, {
86
- ...AUTH_DEFAULT_OPTIONS,
87
- ...options,
88
- });
89
- if (!account?.value)
90
- return null;
91
- // Unserialize the values (TODO: save these as base64 instead of json serializing)
92
- const result = JSON.parse(account.value);
93
- const writeKey = OwnerWriteKey.orThrow(new Uint8Array(Object.values(result.owner.writeKey)));
94
- const encryptionKey = OwnerEncryptionKey.orThrow(new Uint8Array(Object.values(result.owner.encryptionKey)));
95
- const owner = { ...result.owner, writeKey, encryptionKey };
96
- // Update the last owner for future login attempts
97
- await setLastOwnerId(targetOwnerId, options);
98
- // Return the owner and associated username
99
- return { owner, username };
89
+ await setLastOwnerId(ownerId, options);
90
+ return { owner: undefined, username };
100
91
  },
101
92
  register: async (username, options) => {
102
93
  // Create an owner with a new secret or use specified mnemonic
@@ -106,11 +97,7 @@ export const createLocalAuth = (deps) => {
106
97
  // Store owner, associated username, and update last owner
107
98
  await Promise.all([
108
99
  // setOwnerItem
109
- deps.secureStorage.setItem(owner.id, JSON.stringify({ owner }), {
110
- ...AUTH_DEFAULT_OPTIONS,
111
- webAuthnUsername: username,
112
- ...options,
113
- }),
100
+ deps.secureStorage.setItem(owner.id, JSON.stringify({ owner }), buildAuthOptions(options, username)),
114
101
  setOwnerName(owner.id, username, options),
115
102
  setLastOwnerId(owner.id, options),
116
103
  ]);
@@ -121,10 +108,7 @@ export const createLocalAuth = (deps) => {
121
108
  // Delete the owner and associated username
122
109
  await Promise.all([
123
110
  // deleteOwnerItem
124
- deps.secureStorage.deleteItem(ownerId, {
125
- ...AUTH_DEFAULT_OPTIONS,
126
- ...options,
127
- }),
111
+ deps.secureStorage.deleteItem(ownerId, buildAuthOptions(options)),
128
112
  deleteOwnerName(ownerId, options),
129
113
  ]);
130
114
  // If the owner was the last owner then set to
@@ -137,6 +121,26 @@ export const createLocalAuth = (deps) => {
137
121
  }
138
122
  }
139
123
  },
124
+ getOwner: async (options) => {
125
+ const ownerId = await getLastOwnerId(options);
126
+ if (!ownerId)
127
+ return null;
128
+ const names = await getOwnerNames(options);
129
+ const username = names[ownerId] ?? "";
130
+ // Retrieve and decrypt the owner (this will trigger device authentication)
131
+ const account = await deps.secureStorage.getItem(ownerId, buildAuthOptions(options, username));
132
+ if (!account?.value)
133
+ return null;
134
+ // Unserialize the values (TODO: save these as base64 instead of json serializing)
135
+ const result = JSON.parse(account.value);
136
+ const writeKey = OwnerWriteKey.orThrow(new Uint8Array(Object.values(result.owner.writeKey)));
137
+ const encryptionKey = OwnerEncryptionKey.orThrow(new Uint8Array(Object.values(result.owner.encryptionKey)));
138
+ const owner = { ...result.owner, writeKey, encryptionKey };
139
+ // Update the last owner for future login attempts
140
+ await setLastOwnerId(ownerId, options);
141
+ // Return the owner and associated username
142
+ return { owner, username };
143
+ },
140
144
  getProfiles: async (options) => {
141
145
  // Get all owner ids and associated usernames
142
146
  const [ids, names] = await Promise.all([
@@ -166,6 +170,6 @@ export const AUTH_DEFAULT_OPTIONS = {
166
170
  iosSynchronizable: true,
167
171
  webAuthnUsername: "Evolu User",
168
172
  authenticationPrompt: {
169
- title: "Authenticate to unlock your session",
173
+ title: "Authenticate as |USERNAME|",
170
174
  },
171
175
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "6.0.1-preview.21",
3
+ "version": "6.0.1-preview.23",
4
4
  "description": "TypeScript library and local-first framework",
5
5
  "keywords": [
6
6
  "evolu",
@@ -25,7 +25,6 @@ import {
25
25
  import { IntentionalNever } from "../Types.js";
26
26
  import { CreateDbWorkerDep, DbConfig, defaultDbConfig } from "./Db.js";
27
27
  import { applyPatches } from "./Diff.js";
28
- import { LocalAuthDep } from "./LocalAuth.js";
29
28
  import { AppOwner } from "./Owner.js";
30
29
  import { FlushSyncDep, ReloadAppDep } from "./Platform.js";
31
30
  import { ProtocolError, ProtocolUnsupportedVersionError } from "./Protocol.js";
@@ -93,48 +92,6 @@ export interface EvoluConfig extends Partial<DbConfig> {
93
92
  readonly reloadUrl?: string;
94
93
  }
95
94
 
96
- // /**
97
- // * Validated database change with schema-typed values.
98
- // *
99
- // * This is a tagged union where the tag is the table name and the values are
100
- // * updateable (validated against the schema). This represents the content of a
101
- // * {@link CrdtMessage} without the timestamp, which is sufficient for business
102
- // * logic validation in {@link EvoluConfig.onMessage}.
103
- // */
104
- // export type ValidatedDbChange<S extends EvoluSchema> = {
105
- // [Table in keyof S]: {
106
- // readonly table: Table;
107
- // readonly id: Id;
108
- // readonly values: Updateable<S[Table]> & { readonly createdAt?: DateIso };
109
- // };
110
- // }[keyof S];
111
-
112
- // /**
113
- // * Local-only mutation interface for use within {@link EvoluConfig.onMessage}
114
- // * callback.
115
- // *
116
- // * Provides type-safe mutation methods that only accept tables with names
117
- // * starting with underscore (local-only tables). All methods require fully
118
- // * validated branded values. No validation is performed as TypeScript ensures
119
- // * type correctness.
120
- // */
121
- // export interface LocalOnly<S extends EvoluSchema> {
122
- // readonly insert: <T extends keyof S & `_${string}`>(
123
- // table: T,
124
- // values: InferType<ObjectType<InsertableProps<S[T]>>>,
125
- // ) => InferType<S[T]["id"]>;
126
-
127
- // readonly update: <T extends keyof S & `_${string}`>(
128
- // table: T,
129
- // values: InferType<ObjectType<UpdateableProps<S[T]>>>,
130
- // ) => void;
131
-
132
- // readonly upsert: <T extends keyof S & `_${string}`>(
133
- // table: T,
134
- // values: InferType<ObjectType<UpsertableProps<S[T]>>>,
135
- // ) => void;
136
- // }
137
-
138
95
  export interface Evolu<S extends EvoluSchema = EvoluSchema> {
139
96
  /**
140
97
  * Subscribe to {@link EvoluError} changes.
@@ -187,13 +144,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
187
144
  * reason why loading should fail. All data are local, and the query is typed.
188
145
  * Unexpected errors are handled with {@link Evolu#subscribeError}.
189
146
  *
190
- * Loading is batched, and returned promises are cached, so there is no need
191
- * for an additional cache. Evolu's internal cache is invalidated on mutation.
192
- * Unsubscribed queries are removed from the cache, so loading them again will
193
- * return a new pending promise. Subscribed queries remain in the cache to
194
- * prevent unnecessary Suspense boundaries from activating. Their promises are
195
- * replaced with `Promise.resolve(rows)`, allowing React to synchronously
196
- * unwrap the updated data without suspending.
147
+ * Loading is batched, and returned promises are cached until resolved to
148
+ * prevent redundant database queries and to support React Suspense (which
149
+ * requires stable promise references while pending).
197
150
  *
198
151
  * To subscribe a query for automatic updates, use
199
152
  * {@link Evolu#subscribeQuery}.
@@ -255,6 +208,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
255
208
  /**
256
209
  * Promise that resolves to {@link AppOwner} when available.
257
210
  *
211
+ * Note: With web-only deps, this promise will not resolve during SSR because
212
+ * there is no AppOwner on the server.
213
+ *
258
214
  * ### Example
259
215
  *
260
216
  * ```ts
@@ -263,33 +219,6 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
263
219
  */
264
220
  readonly appOwner: Promise<AppOwner>;
265
221
 
266
- // TODO: Update it for the owners
267
- // /**
268
- // * Subscribe to {@link SyncState} changes.
269
- // *
270
- // * ### Example
271
- // *
272
- // * ```ts
273
- // * const unsubscribe = evolu.subscribeSyncState(() => {
274
- // * const syncState = evolu.getSyncState();
275
- // * });
276
- // * ```
277
- // */
278
- // readonly subscribeSyncState: StoreSubscribe;
279
-
280
- // /**
281
- // * Get {@link SyncState}.
282
- // *
283
- // * ### Example
284
- // *
285
- // * ```ts
286
- // * const unsubscribe = evolu.subscribeSyncState(() => {
287
- // * const syncState = evolu.getSyncState();
288
- // * });
289
- // * ```
290
- // */
291
- // readonly getSyncState: () => SyncState;
292
-
293
222
  /**
294
223
  * Inserts a row into the database and returns a {@link Result} with the new
295
224
  * {@link Id}.
@@ -512,23 +441,6 @@ export type EvoluError =
512
441
  | TimestampError
513
442
  | TransferableError;
514
443
 
515
- // /**
516
- // * Error reported when a message is invalid or rejected during processing.
517
- // *
518
- // * This error should never happen because a properly written app should ensure
519
- // * data correctness, but it can occur for two reasons:
520
- // *
521
- // * 1. An attack from someone who modified app code
522
- // * 2. A bug by the developer
523
- // *
524
- // * Both cases are useful to report for debugging and security monitoring.
525
- // */
526
- // export interface OnMessageError {
527
- // readonly type: "OnMessageError";
528
- // readonly invalidChanges: ReadonlyArray<DbChange>;
529
- // readonly rejectedChanges: ReadonlyArray<DbChange>;
530
- // }
531
-
532
444
  interface InternalEvoluInstance<S extends EvoluSchema = EvoluSchema>
533
445
  extends Evolu<S> {
534
446
  /**
@@ -540,36 +452,19 @@ interface InternalEvoluInstance<S extends EvoluSchema = EvoluSchema>
540
452
 
541
453
  export type EvoluDeps = ConsoleDep &
542
454
  CreateDbWorkerDep &
543
- LocalAuthDep &
544
455
  Partial<FlushSyncDep> &
545
456
  RandomBytesDep &
546
457
  ReloadAppDep &
547
458
  TimeDep;
548
459
 
549
- // For hot reloading and Evolu multitenancy.
550
460
  const evoluInstances = new Map<string, InternalEvoluInstance>();
551
461
 
552
462
  let tabId: Id | null = null;
553
463
 
554
464
  /**
555
- * Creates an {@link Evolu} instance configured with the specified
556
- * {@link EvoluSchema} and optional configuration.
557
- *
558
- * This function returns a configured Evolu instance, providing a typed
559
- * interface for querying, mutating, and syncing your application's data. The
560
- * returned instance includes:
561
- *
562
- * - Subscription methods for receiving updates on queries, the owner, errors, and
563
- * sync state.
564
- * - Methods for creating, updating, or deleting rows in a type-safe manner.
565
- * - Methods for querying data using Evolu's typed SQL queries, leveraging Kysely
566
- * under the hood.
567
- * - Built-in support for local-first and offline-first data with automatic sync
568
- * and merging.
569
- * - Automatic schema evolution that updates the underlying database with new
570
- * columns or tables.
571
- * - Managing owner data with {@link Evolu#resetAppOwner} and
572
- * {@link Evolu#restoreAppOwner}.
465
+ * Creates an {@link Evolu} instance for a platform configured with the specified
466
+ * {@link EvoluSchema} and optional {@link EvoluConfig} providing a typed
467
+ * interface for querying, mutating, and syncing your application's data.
573
468
  *
574
469
  * ### Example
575
470
  *
@@ -598,6 +493,18 @@ let tabId: Id | null = null;
598
493
  *
599
494
  * const evolu = createEvolu(evoluReactDeps)(Schema);
600
495
  * ```
496
+ *
497
+ * ### Instance Caching
498
+ *
499
+ * Evolu caches instances by {@link EvoluConfig} name to enable hot reloading and
500
+ * multitenancy. Multiple calls to `createEvolu` with the same name return the
501
+ * same instance, preserving database connections and state across module
502
+ * reloads during development. This ensures a seamless developer experience
503
+ * where edits don't interrupt ongoing sync or lose in-memory state.
504
+ *
505
+ * For testing, either dispose of instances after each test (TODO: implement
506
+ * dispose method) or use unique instance names to ensure proper isolation
507
+ * between test cases.
601
508
  */
602
509
  export const createEvolu =
603
510
  (deps: EvoluDeps) =>
@@ -658,40 +565,6 @@ const createEvoluInstance =
658
565
  return tabId;
659
566
  };
660
567
 
661
- // const createLocalOnly = (
662
- // localMutations: Array<MutationChange>,
663
- // defaultOwnerId: OwnerId | undefined,
664
- // ): LocalOnly<EvoluSchema> => ({
665
- // insert: (table, values) => {
666
- // const id = createId(deps);
667
- // localMutations.push({
668
- // table,
669
- // id,
670
- // values,
671
- // ownerId: defaultOwnerId,
672
- // });
673
- // return id;
674
- // },
675
- // update: (table, values) => {
676
- // const { id, ...rest } = values;
677
- // localMutations.push({
678
- // table,
679
- // id: id as Id,
680
- // values: rest,
681
- // ownerId: defaultOwnerId,
682
- // });
683
- // },
684
- // upsert: (table, values) => {
685
- // const { id, ...rest } = values as Record<string, unknown> & { id: Id };
686
- // localMutations.push({
687
- // table,
688
- // id: id,
689
- // values: rest as MutationChange["values"],
690
- // ownerId: defaultOwnerId,
691
- // });
692
- // },
693
- // });
694
-
695
568
  // Worker responses are delivered to all tabs. Each case must handle this
696
569
  // properly (e.g., AppOwner promise resolves only once, tabId filtering).
697
570
  dbWorker.onMessage((message) => {
@@ -765,74 +638,6 @@ const createEvoluInstance =
765
638
  break;
766
639
  }
767
640
 
768
- // case "processNewMessages": {
769
- // void requestIdleTask(
770
- // toTask(async () => {
771
- // const approved: Array<Timestamp> = [];
772
- // const invalidChanges: Array<DbChange> = [];
773
- // const rejectedChanges: Array<DbChange> = [];
774
- // const localMutations: Array<MutationChange> = [];
775
-
776
- // for (const crdtMessage of message.messages) {
777
- // let isApproved = true;
778
- // let isValid = true;
779
-
780
- // const table = crdtMessage.change.table;
781
- // if (table in schema) {
782
- // const { createdAt, ...values } = crdtMessage.change.values;
783
- // isValid =
784
- // (createdAt ? DateIso.is(createdAt) : true) &&
785
- // getMutationType(table, "update").is({
786
- // id: crdtMessage.change.id,
787
- // ...values,
788
- // });
789
- // } else {
790
- // isValid = false;
791
- // }
792
-
793
- // if (!isValid) {
794
- // isApproved = false;
795
- // invalidChanges.push(crdtMessage.change);
796
- // } else if (onMessage) {
797
- // // At this point, we've validated that the message conforms to the
798
- // // schema, so the typed callback can safely process it.
799
- // isApproved = await onMessage(crdtMessage.change, {
800
- // ownerId: message.ownerId,
801
- // localOnly: createLocalOnly(localMutations, message.ownerId),
802
- // });
803
- // if (!isApproved) {
804
- // rejectedChanges.push(crdtMessage.change);
805
- // }
806
- // }
807
-
808
- // if (isApproved) {
809
- // approved.push(crdtMessage.timestamp);
810
- // }
811
- // }
812
-
813
- // // Report OnMessageError if there were any invalid or rejected changes
814
- // if (invalidChanges.length > 0 || rejectedChanges.length > 0) {
815
- // const onMessageError: OnMessageError = {
816
- // type: "OnMessageError",
817
- // invalidChanges,
818
- // rejectedChanges,
819
- // };
820
- // errorStore.set(onMessageError);
821
- // }
822
-
823
- // dbWorker.postMessage({
824
- // type: "onProcessNewMessages",
825
- // onCompleteId: message.onCompleteId,
826
- // approved,
827
- // localMutations,
828
- // });
829
-
830
- // return ok();
831
- // }),
832
- // )();
833
- // break;
834
- // }
835
-
836
641
  case "onExport": {
837
642
  exportRegistry.execute(
838
643
  message.onCompleteId,
@@ -972,34 +777,6 @@ const createEvoluInstance =
972
777
 
973
778
  if (!isNonEmptyArray(changes)) return;
974
779
 
975
- // if (onMessage) {
976
- // const rejectedChanges: Array<DbChange> = [];
977
- // const localMutations: Array<MutationChange> = [];
978
-
979
- // for (const change of changes) {
980
- // const localOnly = createLocalOnly(localMutations, change.ownerId);
981
-
982
- // const isApproved = await onMessage(change, {
983
- // ownerId: change.ownerId,
984
- // localOnly,
985
- // });
986
- // if (!isApproved) {
987
- // rejectedChanges.push(change);
988
- // }
989
- // }
990
-
991
- // if (rejectedChanges.length > 0) {
992
- // errorStore.set({
993
- // type: "OnMessageError",
994
- // invalidChanges: [],
995
- // rejectedChanges,
996
- // });
997
- // return;
998
- // }
999
-
1000
- // changes.push(...localMutations);
1001
- // }
1002
-
1003
780
  dbWorker.postMessage({
1004
781
  type: "mutate",
1005
782
  tabId: getTabId(),
@@ -1199,25 +976,8 @@ interface LoadingPromises {
1199
976
  readonly isNew: boolean;
1200
977
  };
1201
978
 
1202
- /**
1203
- * Resolve a cached promise with updated rows.
1204
- *
1205
- * If the promise is not yet fulfilled, it will be resolved normally. If
1206
- * already fulfilled (subscribed query updated after mutation), the promise
1207
- * property is replaced with a new `Promise.resolve(rows)` while keeping the
1208
- * same cached object reference. The promise is not removed from the cache
1209
- * because React Suspense requires repeated calls to return the same promise.
1210
- */
1211
979
  resolve: (query: Query, rows: ReadonlyArray<Row>) => void;
1212
980
 
1213
- /**
1214
- * Release unsubscribed queries from the cache.
1215
- *
1216
- * Loading promises can't be released in `resolve` because they must be cached
1217
- * for React Suspense, but they also can't be cached forever because only
1218
- * subscribed queries are automatically updated (reactivity is expensive
1219
- * because it's implemented via refetching subscribed queries).
1220
- */
1221
981
  releaseUnsubscribedOnMutation: () => void;
1222
982
 
1223
983
  getQueries: () => ReadonlyArray<Query>;
@@ -10,12 +10,17 @@ import {
10
10
  OwnerWriteKey,
11
11
  } from "./Owner.js";
12
12
 
13
- /** @experimental */
13
+ /**
14
+ * Local authentication and authorization system for Evolu.
15
+ * This is API is subject to change and not recommended for production use.
16
+ *
17
+ * @experimental
18
+ */
14
19
  export interface LocalAuth {
15
20
  /** Logs in with the given owner ID, or loads the target owner if not provided. */
16
21
  login: (
17
- ownerId?: OwnerId,
18
- options?: LocalAuthOptions & { reloadNeeded?: boolean },
22
+ ownerId: OwnerId,
23
+ options?: LocalAuthOptions,
19
24
  ) => Promise<AuthResult | null>;
20
25
 
21
26
  /** Registers a new owner with the given username. */
@@ -27,10 +32,11 @@ export interface LocalAuth {
27
32
  /** Unregisters an owner with the given owner ID. */
28
33
  unregister: (ownerId: OwnerId, options?: LocalAuthOptions) => Promise<void>;
29
34
 
35
+ /** Gets the current owner (last logged in or last registered owner) */
36
+ getOwner: (options?: LocalAuthOptions) => Promise<AuthResult | null>;
37
+
30
38
  /** Lists all registered owner ids with associated usernames. */
31
- getProfiles: (
32
- options?: LocalAuthOptionsValues,
33
- ) => Promise<Array<{ ownerId: OwnerId; username: string }>>;
39
+ getProfiles: (options?: LocalAuthOptionsValues) => Promise<Array<AuthList>>;
34
40
 
35
41
  /** Clears all owners and metadata from the local auth. */
36
42
  clearAll: (options?: LocalAuthOptions) => Promise<void>;
@@ -79,8 +85,7 @@ export const createLocalAuth = (
79
85
  options?: LocalAuthOptions,
80
86
  ): Promise<void> => {
81
87
  await deps.secureStorage.setItem(AUTH_METAKEY_LAST_OWNER, id, {
82
- ...AUTH_DEFAULT_OPTIONS,
83
- ...options,
88
+ ...buildAuthOptions(options),
84
89
  accessControl: "none",
85
90
  });
86
91
  };
@@ -89,8 +94,7 @@ export const createLocalAuth = (
89
94
  options?: LocalAuthOptions,
90
95
  ): Promise<OwnerId | undefined> => {
91
96
  const item = await deps.secureStorage.getItem(AUTH_METAKEY_LAST_OWNER, {
92
- ...AUTH_DEFAULT_OPTIONS,
93
- ...options,
97
+ ...buildAuthOptions(options),
94
98
  accessControl: "none",
95
99
  });
96
100
  return item?.value as OwnerId;
@@ -100,8 +104,7 @@ export const createLocalAuth = (
100
104
  options?: LocalAuthOptions,
101
105
  ): Promise<Record<OwnerId, string>> => {
102
106
  const item = await deps.secureStorage.getItem(AUTH_METAKEY_OWNER_NAMES, {
103
- ...AUTH_DEFAULT_OPTIONS,
104
- ...options,
107
+ ...buildAuthOptions(options),
105
108
  accessControl: "none",
106
109
  });
107
110
  let names: Record<OwnerId, string> = {};
@@ -122,8 +125,7 @@ export const createLocalAuth = (
122
125
  AUTH_METAKEY_OWNER_NAMES,
123
126
  JSON.stringify(names),
124
127
  {
125
- ...AUTH_DEFAULT_OPTIONS,
126
- ...options,
128
+ ...buildAuthOptions(options),
127
129
  accessControl: "none",
128
130
  },
129
131
  );
@@ -138,8 +140,7 @@ export const createLocalAuth = (
138
140
  AUTH_METAKEY_OWNER_NAMES,
139
141
  JSON.stringify(names),
140
142
  {
141
- ...AUTH_DEFAULT_OPTIONS,
142
- ...options,
143
+ ...buildAuthOptions(options),
143
144
  accessControl: "none",
144
145
  },
145
146
  );
@@ -149,8 +150,7 @@ export const createLocalAuth = (
149
150
  options?: LocalAuthOptions,
150
151
  ): Promise<Array<OwnerId>> => {
151
152
  const items = await deps.secureStorage.getAllItems({
152
- ...AUTH_DEFAULT_OPTIONS,
153
- ...options,
153
+ ...buildAuthOptions(options),
154
154
  includeValues: false,
155
155
  });
156
156
  return items
@@ -164,51 +164,44 @@ export const createLocalAuth = (
164
164
  };
165
165
 
166
166
  const clearAuthStore = (options?: LocalAuthOptions): Promise<void> =>
167
- deps.secureStorage.clearService({
167
+ deps.secureStorage.clearService(buildAuthOptions(options));
168
+
169
+ const buildAuthOptions = (
170
+ options?: LocalAuthOptions,
171
+ username?: string,
172
+ ): LocalAuthOptions => {
173
+ const newOptions: LocalAuthOptions = {
168
174
  ...AUTH_DEFAULT_OPTIONS,
175
+ ...(username && { webAuthnUsername: username }),
169
176
  ...options,
170
- });
177
+ };
178
+ return {
179
+ ...newOptions,
180
+ authenticationPrompt: {
181
+ title: replaceMessageTokens(newOptions.authenticationPrompt?.title ?? "", username),
182
+ cancel: replaceMessageTokens(newOptions.authenticationPrompt?.cancel ?? "", username),
183
+ subtitle: replaceMessageTokens(newOptions.authenticationPrompt?.subtitle ?? "", username),
184
+ description: replaceMessageTokens(newOptions.authenticationPrompt?.description ?? "", username),
185
+ },
186
+ };
187
+ };
188
+
189
+ const replaceMessageTokens = (text: string, username?: string): string => {
190
+ if (!username) return text;
191
+ return text.replace("|USERNAME|", username);
192
+ };
171
193
 
172
194
  return {
173
195
  login: async (ownerId, options) => {
174
- // Use either specified owner or the last owner used during registration/login.
175
- const targetOwnerId = ownerId ?? (await getLastOwnerId(options));
176
- if (!targetOwnerId) return null;
177
-
178
196
  // Lookup the associated username
179
197
  const names = await getOwnerNames(options);
180
- const username = names[targetOwnerId] ?? "";
181
-
182
- // If a reload is needed, avoid authentication, it needs to be handled on next page load.
198
+ const username = names[ownerId] ?? "";
199
+ // Currently a reload is needed. This avoids authentication
200
+ // it needs to be handled on next page load.
183
201
  // We set the last owner so we know what the target is.
184
202
  // It is the applications's responsibility to reload and trigger login.
185
- if (options?.reloadNeeded) {
186
- await setLastOwnerId(targetOwnerId, options);
187
- return { owner: undefined, username };
188
- }
189
-
190
- // Retrieve and decrypt the owner (this will trigger device authentication)
191
- const account = await deps.secureStorage.getItem(targetOwnerId, {
192
- ...AUTH_DEFAULT_OPTIONS,
193
- ...options,
194
- });
195
- if (!account?.value) return null;
196
-
197
- // Unserialize the values (TODO: save these as base64 instead of json serializing)
198
- const result = JSON.parse(account.value) as { owner: AppOwner };
199
- const writeKey = OwnerWriteKey.orThrow(
200
- new Uint8Array(Object.values(result.owner.writeKey)),
201
- );
202
- const encryptionKey = OwnerEncryptionKey.orThrow(
203
- new Uint8Array(Object.values(result.owner.encryptionKey)),
204
- );
205
- const owner: AppOwner = { ...result.owner, writeKey, encryptionKey };
206
-
207
- // Update the last owner for future login attempts
208
- await setLastOwnerId(targetOwnerId, options);
209
-
210
- // Return the owner and associated username
211
- return { owner, username };
203
+ await setLastOwnerId(ownerId, options);
204
+ return { owner: undefined, username };
212
205
  },
213
206
 
214
207
  register: async (username, options) => {
@@ -222,11 +215,11 @@ export const createLocalAuth = (
222
215
  // Store owner, associated username, and update last owner
223
216
  await Promise.all([
224
217
  // setOwnerItem
225
- deps.secureStorage.setItem(owner.id, JSON.stringify({ owner }), {
226
- ...AUTH_DEFAULT_OPTIONS,
227
- webAuthnUsername: username,
228
- ...options,
229
- }),
218
+ deps.secureStorage.setItem(
219
+ owner.id,
220
+ JSON.stringify({ owner }),
221
+ buildAuthOptions(options, username),
222
+ ),
230
223
  setOwnerName(owner.id, username, options),
231
224
  setLastOwnerId(owner.id, options),
232
225
  ]);
@@ -239,10 +232,7 @@ export const createLocalAuth = (
239
232
  // Delete the owner and associated username
240
233
  await Promise.all([
241
234
  // deleteOwnerItem
242
- deps.secureStorage.deleteItem(ownerId, {
243
- ...AUTH_DEFAULT_OPTIONS,
244
- ...options,
245
- }),
235
+ deps.secureStorage.deleteItem(ownerId, buildAuthOptions(options)),
246
236
  deleteOwnerName(ownerId, options),
247
237
  ]);
248
238
 
@@ -257,6 +247,37 @@ export const createLocalAuth = (
257
247
  }
258
248
  },
259
249
 
250
+ getOwner: async (options) => {
251
+ const ownerId = await getLastOwnerId(options);
252
+ if (!ownerId) return null;
253
+
254
+ const names = await getOwnerNames(options);
255
+ const username = names[ownerId] ?? "";
256
+
257
+ // Retrieve and decrypt the owner (this will trigger device authentication)
258
+ const account = await deps.secureStorage.getItem(
259
+ ownerId,
260
+ buildAuthOptions(options, username),
261
+ );
262
+ if (!account?.value) return null;
263
+
264
+ // Unserialize the values (TODO: save these as base64 instead of json serializing)
265
+ const result = JSON.parse(account.value) as { owner: AppOwner };
266
+ const writeKey = OwnerWriteKey.orThrow(
267
+ new Uint8Array(Object.values(result.owner.writeKey)),
268
+ );
269
+ const encryptionKey = OwnerEncryptionKey.orThrow(
270
+ new Uint8Array(Object.values(result.owner.encryptionKey)),
271
+ );
272
+ const owner: AppOwner = { ...result.owner, writeKey, encryptionKey };
273
+
274
+ // Update the last owner for future login attempts
275
+ await setLastOwnerId(ownerId, options);
276
+
277
+ // Return the owner and associated username
278
+ return { owner, username };
279
+ },
280
+
260
281
  getProfiles: async (options) => {
261
282
  // Get all owner ids and associated usernames
262
283
  const [ids, names] = await Promise.all([
@@ -289,17 +310,24 @@ export const AUTH_DEFAULT_OPTIONS: LocalAuthOptions = {
289
310
  iosSynchronizable: true,
290
311
  webAuthnUsername: "Evolu User",
291
312
  authenticationPrompt: {
292
- title: "Authenticate to unlock your session",
313
+ title: "Authenticate as |USERNAME|",
293
314
  },
294
315
  };
295
316
 
296
317
  export interface AuthResult {
297
- /** The owner created during registration. */
318
+ /** The app owner created during registration. */
298
319
  readonly owner: AppOwner | undefined;
299
320
  /** The name provided by the user during registration. */
300
321
  readonly username: string;
301
322
  }
302
323
 
324
+ export interface AuthList {
325
+ /** The app owner ID. */
326
+ readonly ownerId: OwnerId;
327
+ /** The name provided by the user during registration. */
328
+ readonly username: string;
329
+ }
330
+
303
331
  /* Types below based off of react-native-sensitive-info */
304
332
 
305
333
  export interface LocalAuthOptions {