@evolu/common 6.0.1-preview.21 → 6.0.1-preview.22
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.
- package/dist/src/Evolu/Evolu.d.ts +21 -25
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +15 -135
- package/package.json +1 -1
- package/src/Evolu/Evolu.ts +21 -259
|
@@ -92,13 +92,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
92
92
|
* reason why loading should fail. All data are local, and the query is typed.
|
|
93
93
|
* Unexpected errors are handled with {@link Evolu#subscribeError}.
|
|
94
94
|
*
|
|
95
|
-
* Loading is batched, and returned promises are cached
|
|
96
|
-
*
|
|
97
|
-
*
|
|
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.
|
|
95
|
+
* Loading is batched, and returned promises are cached until resolved to
|
|
96
|
+
* prevent redundant database queries and to support React Suspense (which
|
|
97
|
+
* requires stable promise references while pending).
|
|
102
98
|
*
|
|
103
99
|
* To subscribe a query for automatic updates, use
|
|
104
100
|
* {@link Evolu#subscribeQuery}.
|
|
@@ -154,6 +150,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
154
150
|
/**
|
|
155
151
|
* Promise that resolves to {@link AppOwner} when available.
|
|
156
152
|
*
|
|
153
|
+
* Note: With web-only deps, this promise will not resolve during SSR because
|
|
154
|
+
* there is no AppOwner on the server.
|
|
155
|
+
*
|
|
157
156
|
* ### Example
|
|
158
157
|
*
|
|
159
158
|
* ```ts
|
|
@@ -367,24 +366,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
367
366
|
export type EvoluError = ProtocolError | ProtocolUnsupportedVersionError | SqliteError | SymmetricCryptoDecryptError | TimestampError | TransferableError;
|
|
368
367
|
export type EvoluDeps = ConsoleDep & CreateDbWorkerDep & LocalAuthDep & Partial<FlushSyncDep> & RandomBytesDep & ReloadAppDep & TimeDep;
|
|
369
368
|
/**
|
|
370
|
-
* Creates an {@link Evolu} instance configured with the specified
|
|
371
|
-
* {@link EvoluSchema} and optional
|
|
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}.
|
|
369
|
+
* Creates an {@link Evolu} instance for a platform configured with the specified
|
|
370
|
+
* {@link EvoluSchema} and optional {@link EvoluConfig} providing a typed
|
|
371
|
+
* interface for querying, mutating, and syncing your application's data.
|
|
388
372
|
*
|
|
389
373
|
* ### Example
|
|
390
374
|
*
|
|
@@ -413,6 +397,18 @@ export type EvoluDeps = ConsoleDep & CreateDbWorkerDep & LocalAuthDep & Partial<
|
|
|
413
397
|
*
|
|
414
398
|
* const evolu = createEvolu(evoluReactDeps)(Schema);
|
|
415
399
|
* ```
|
|
400
|
+
*
|
|
401
|
+
* ### Instance Caching
|
|
402
|
+
*
|
|
403
|
+
* Evolu caches instances by {@link EvoluConfig} name to enable hot reloading and
|
|
404
|
+
* multitenancy. Multiple calls to `createEvolu` with the same name return the
|
|
405
|
+
* same instance, preserving database connections and state across module
|
|
406
|
+
* reloads during development. This ensures a seamless developer experience
|
|
407
|
+
* where edits don't interrupt ongoing sync or lose in-memory state.
|
|
408
|
+
*
|
|
409
|
+
* For testing, either dispose of instances after each test (TODO: implement
|
|
410
|
+
* dispose method) or use unique instance names to ensure proper isolation
|
|
411
|
+
* between test cases.
|
|
416
412
|
*/
|
|
417
413
|
export declare const createEvolu: (deps: EvoluDeps) => <S extends EvoluSchema>(schema: ValidateSchema<S> extends never ? S : ValidateSchema<S>, config?: EvoluConfig) => Evolu<S>;
|
|
418
414
|
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":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAE3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,EAA0B,WAAW,EAAe,MAAM,cAAc,CAAC;AAChF,OAAO,EAAe,cAAc,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAML,QAAQ,EAIT,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAmB,MAAM,SAAS,CAAC;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,+BAA+B,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAGL,OAAO,EACP,0BAA0B,EAC1B,KAAK,EACL,SAAS,EAET,GAAG,EAGJ,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,WAAW,EAEX,WAAW,EAEX,aAAa,EAGb,QAAQ,EAOR,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAoB,SAAS,EAAa,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,WAAY,SAAQ,OAAO,CAAC,QAAQ,CAAC;IACpD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IAEjC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;
|
|
1
|
+
{"version":3,"file":"Evolu.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Evolu.ts"],"names":[],"mappings":"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"}
|
package/dist/src/Evolu/Evolu.js
CHANGED
|
@@ -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
|
|
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(),
|
package/package.json
CHANGED
package/src/Evolu/Evolu.ts
CHANGED
|
@@ -93,48 +93,6 @@ export interface EvoluConfig extends Partial<DbConfig> {
|
|
|
93
93
|
readonly reloadUrl?: string;
|
|
94
94
|
}
|
|
95
95
|
|
|
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
96
|
export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
139
97
|
/**
|
|
140
98
|
* Subscribe to {@link EvoluError} changes.
|
|
@@ -187,13 +145,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
187
145
|
* reason why loading should fail. All data are local, and the query is typed.
|
|
188
146
|
* Unexpected errors are handled with {@link Evolu#subscribeError}.
|
|
189
147
|
*
|
|
190
|
-
* Loading is batched, and returned promises are cached
|
|
191
|
-
*
|
|
192
|
-
*
|
|
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.
|
|
148
|
+
* Loading is batched, and returned promises are cached until resolved to
|
|
149
|
+
* prevent redundant database queries and to support React Suspense (which
|
|
150
|
+
* requires stable promise references while pending).
|
|
197
151
|
*
|
|
198
152
|
* To subscribe a query for automatic updates, use
|
|
199
153
|
* {@link Evolu#subscribeQuery}.
|
|
@@ -255,6 +209,9 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
255
209
|
/**
|
|
256
210
|
* Promise that resolves to {@link AppOwner} when available.
|
|
257
211
|
*
|
|
212
|
+
* Note: With web-only deps, this promise will not resolve during SSR because
|
|
213
|
+
* there is no AppOwner on the server.
|
|
214
|
+
*
|
|
258
215
|
* ### Example
|
|
259
216
|
*
|
|
260
217
|
* ```ts
|
|
@@ -263,33 +220,6 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
263
220
|
*/
|
|
264
221
|
readonly appOwner: Promise<AppOwner>;
|
|
265
222
|
|
|
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
223
|
/**
|
|
294
224
|
* Inserts a row into the database and returns a {@link Result} with the new
|
|
295
225
|
* {@link Id}.
|
|
@@ -512,23 +442,6 @@ export type EvoluError =
|
|
|
512
442
|
| TimestampError
|
|
513
443
|
| TransferableError;
|
|
514
444
|
|
|
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
445
|
interface InternalEvoluInstance<S extends EvoluSchema = EvoluSchema>
|
|
533
446
|
extends Evolu<S> {
|
|
534
447
|
/**
|
|
@@ -546,30 +459,14 @@ export type EvoluDeps = ConsoleDep &
|
|
|
546
459
|
ReloadAppDep &
|
|
547
460
|
TimeDep;
|
|
548
461
|
|
|
549
|
-
// For hot reloading and Evolu multitenancy.
|
|
550
462
|
const evoluInstances = new Map<string, InternalEvoluInstance>();
|
|
551
463
|
|
|
552
464
|
let tabId: Id | null = null;
|
|
553
465
|
|
|
554
466
|
/**
|
|
555
|
-
* Creates an {@link Evolu} instance configured with the specified
|
|
556
|
-
* {@link EvoluSchema} and optional
|
|
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}.
|
|
467
|
+
* Creates an {@link Evolu} instance for a platform configured with the specified
|
|
468
|
+
* {@link EvoluSchema} and optional {@link EvoluConfig} providing a typed
|
|
469
|
+
* interface for querying, mutating, and syncing your application's data.
|
|
573
470
|
*
|
|
574
471
|
* ### Example
|
|
575
472
|
*
|
|
@@ -598,6 +495,18 @@ let tabId: Id | null = null;
|
|
|
598
495
|
*
|
|
599
496
|
* const evolu = createEvolu(evoluReactDeps)(Schema);
|
|
600
497
|
* ```
|
|
498
|
+
*
|
|
499
|
+
* ### Instance Caching
|
|
500
|
+
*
|
|
501
|
+
* Evolu caches instances by {@link EvoluConfig} name to enable hot reloading and
|
|
502
|
+
* multitenancy. Multiple calls to `createEvolu` with the same name return the
|
|
503
|
+
* same instance, preserving database connections and state across module
|
|
504
|
+
* reloads during development. This ensures a seamless developer experience
|
|
505
|
+
* where edits don't interrupt ongoing sync or lose in-memory state.
|
|
506
|
+
*
|
|
507
|
+
* For testing, either dispose of instances after each test (TODO: implement
|
|
508
|
+
* dispose method) or use unique instance names to ensure proper isolation
|
|
509
|
+
* between test cases.
|
|
601
510
|
*/
|
|
602
511
|
export const createEvolu =
|
|
603
512
|
(deps: EvoluDeps) =>
|
|
@@ -658,40 +567,6 @@ const createEvoluInstance =
|
|
|
658
567
|
return tabId;
|
|
659
568
|
};
|
|
660
569
|
|
|
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
570
|
// Worker responses are delivered to all tabs. Each case must handle this
|
|
696
571
|
// properly (e.g., AppOwner promise resolves only once, tabId filtering).
|
|
697
572
|
dbWorker.onMessage((message) => {
|
|
@@ -765,74 +640,6 @@ const createEvoluInstance =
|
|
|
765
640
|
break;
|
|
766
641
|
}
|
|
767
642
|
|
|
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
643
|
case "onExport": {
|
|
837
644
|
exportRegistry.execute(
|
|
838
645
|
message.onCompleteId,
|
|
@@ -972,34 +779,6 @@ const createEvoluInstance =
|
|
|
972
779
|
|
|
973
780
|
if (!isNonEmptyArray(changes)) return;
|
|
974
781
|
|
|
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
782
|
dbWorker.postMessage({
|
|
1004
783
|
type: "mutate",
|
|
1005
784
|
tabId: getTabId(),
|
|
@@ -1199,25 +978,8 @@ interface LoadingPromises {
|
|
|
1199
978
|
readonly isNew: boolean;
|
|
1200
979
|
};
|
|
1201
980
|
|
|
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
981
|
resolve: (query: Query, rows: ReadonlyArray<Row>) => void;
|
|
1212
982
|
|
|
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
983
|
releaseUnsubscribedOnMutation: () => void;
|
|
1222
984
|
|
|
1223
985
|
getQueries: () => ReadonlyArray<Query>;
|