@evolu/common 4.1.0 → 5.0.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 (78) hide show
  1. package/README.md +1 -1
  2. package/dist/src/Config.d.ts +26 -27
  3. package/dist/src/Config.d.ts.map +1 -1
  4. package/dist/src/Config.js +35 -7
  5. package/dist/src/Crdt.d.ts +5 -4
  6. package/dist/src/Crdt.d.ts.map +1 -1
  7. package/dist/src/Crdt.js +27 -24
  8. package/dist/src/Crypto.d.ts +16 -10
  9. package/dist/src/Crypto.d.ts.map +1 -1
  10. package/dist/src/Crypto.js +24 -11
  11. package/dist/src/Db.d.ts +58 -78
  12. package/dist/src/Db.d.ts.map +1 -1
  13. package/dist/src/Db.js +383 -185
  14. package/dist/src/Diff.d.ts +9 -3
  15. package/dist/src/Diff.d.ts.map +1 -1
  16. package/dist/src/Diff.js +12 -11
  17. package/dist/src/Error.d.ts +15 -0
  18. package/dist/src/Error.d.ts.map +1 -0
  19. package/dist/src/Error.js +14 -0
  20. package/dist/src/Evolu.d.ts +139 -91
  21. package/dist/src/Evolu.d.ts.map +1 -1
  22. package/dist/src/Evolu.js +267 -239
  23. package/dist/src/Model.d.ts +52 -0
  24. package/dist/src/Model.d.ts.map +1 -1
  25. package/dist/src/Model.js +47 -5
  26. package/dist/src/Owner.d.ts.map +1 -1
  27. package/dist/src/Owner.js +14 -8
  28. package/dist/src/Platform.d.ts +24 -34
  29. package/dist/src/Platform.d.ts.map +1 -1
  30. package/dist/src/Platform.js +9 -31
  31. package/dist/src/Public.d.ts +3 -5
  32. package/dist/src/Public.d.ts.map +1 -1
  33. package/dist/src/Public.js +2 -3
  34. package/dist/src/Sqlite.d.ts +25 -15
  35. package/dist/src/Sqlite.d.ts.map +1 -1
  36. package/dist/src/Sqlite.js +28 -7
  37. package/dist/src/Store.d.ts.map +1 -1
  38. package/dist/src/Sync.d.ts +70 -0
  39. package/dist/src/Sync.d.ts.map +1 -0
  40. package/dist/src/Sync.js +126 -0
  41. package/dist/src/index.d.ts +2 -5
  42. package/dist/src/index.d.ts.map +1 -1
  43. package/dist/src/index.js +2 -5
  44. package/package.json +12 -10
  45. package/src/Config.ts +82 -39
  46. package/src/Crdt.ts +61 -76
  47. package/src/Crypto.ts +83 -60
  48. package/src/Db.ts +802 -370
  49. package/src/Diff.ts +23 -21
  50. package/src/Error.ts +29 -0
  51. package/src/Evolu.ts +599 -499
  52. package/src/Model.ts +102 -6
  53. package/src/Owner.ts +24 -24
  54. package/src/Platform.ts +33 -81
  55. package/src/Public.ts +3 -5
  56. package/src/Sqlite.ts +81 -27
  57. package/src/Sync.ts +306 -0
  58. package/src/index.ts +2 -5
  59. package/dist/src/DbWorker.d.ts +0 -82
  60. package/dist/src/DbWorker.d.ts.map +0 -1
  61. package/dist/src/DbWorker.js +0 -335
  62. package/dist/src/ErrorStore.d.ts +0 -29
  63. package/dist/src/ErrorStore.d.ts.map +0 -1
  64. package/dist/src/ErrorStore.js +0 -13
  65. package/dist/src/OnCompletes.d.ts +0 -14
  66. package/dist/src/OnCompletes.d.ts.map +0 -1
  67. package/dist/src/OnCompletes.js +0 -25
  68. package/dist/src/SyncWorker.d.ts +0 -80
  69. package/dist/src/SyncWorker.d.ts.map +0 -1
  70. package/dist/src/SyncWorker.js +0 -150
  71. package/dist/src/Types.d.ts +0 -13
  72. package/dist/src/Types.d.ts.map +0 -1
  73. package/dist/src/Types.js +0 -1
  74. package/src/DbWorker.ts +0 -721
  75. package/src/ErrorStore.ts +0 -46
  76. package/src/OnCompletes.ts +0 -51
  77. package/src/SyncWorker.ts +0 -361
  78. package/src/Types.ts +0 -11
package/dist/src/Evolu.js CHANGED
@@ -1,149 +1,234 @@
1
+ import * as AST from "@effect/schema/AST";
2
+ import { make } from "@effect/schema/Schema";
3
+ import * as Arr from "effect/Array";
1
4
  import * as Context from "effect/Context";
2
5
  import * as Effect from "effect/Effect";
3
- import * as Function from "effect/Function";
4
- import { pipe } from "effect/Function";
5
- import * as GlobalValue from "effect/GlobalValue";
6
+ import { constVoid, flow, pipe } from "effect/Function";
6
7
  import * as Layer from "effect/Layer";
7
- import * as Match from "effect/Match";
8
8
  import * as Number from "effect/Number";
9
- import * as ReadonlyArray from "effect/ReadonlyArray";
9
+ import * as Option from "effect/Option";
10
+ import * as Predicate from "effect/Predicate";
11
+ import * as Record from "effect/Record";
10
12
  import * as Kysely from "kysely";
11
- import { Config, ConfigLive } from "./Config.js";
12
- import { NanoIdGenerator, NanoIdGeneratorLive } from "./Crypto.js";
13
- import { RowsStore, RowsStoreLive, emptyRows, queryResultFromRows, schemaToTables, serializeQuery, } from "./Db.js";
14
- import { DbWorker } from "./DbWorker.js";
13
+ import { Config, createRuntime, defaultConfig } from "./Config.js";
14
+ import { NanoIdGenerator } from "./Crypto.js";
15
+ import { DbFactory, deserializeQuery, emptyRows, queryResultFromRows, serializeQuery, } from "./Db.js";
15
16
  import { applyPatches } from "./Diff.js";
16
- import { makeErrorStore } from "./ErrorStore.js";
17
- import { OnCompletes, OnCompletesLive } from "./OnCompletes.js";
17
+ import { makeUnexpectedError } from "./Error.js";
18
18
  import { AppState, FlushSync } from "./Platform.js";
19
19
  import { isSqlMutation, } from "./Sqlite.js";
20
20
  import { makeStore } from "./Store.js";
21
- export const Evolu = Context.GenericTag("@services/Evolu");
22
- // https://kysely.dev/docs/recipes/splitting-query-building-and-execution
23
- const kysely = new Kysely.Kysely({
24
- dialect: {
25
- createAdapter: () => new Kysely.SqliteAdapter(),
26
- createDriver: () => new Kysely.DummyDriver(),
27
- createIntrospector() {
28
- throw "Not implemeneted";
29
- },
30
- createQueryCompiler: () => new Kysely.SqliteQueryCompiler(),
31
- },
32
- });
33
- export const createIndex = kysely.schema.createIndex.bind(kysely.schema);
34
- export const makeCreateQuery = () => (queryCallback, options) => pipe(queryCallback(kysely).compile(), (compiledQuery) => {
35
- if (isSqlMutation(compiledQuery.sql))
36
- throw new Error("SQL mutation (INSERT, UPDATE, DELETE, etc.) isn't allowed in the Evolu `createQuery` function. Kysely suggests it because there is no read-only Kysely yet, and removing such an API is not possible. For mutations, use Evolu mutation API.");
37
- const parameters = compiledQuery.parameters;
38
- return {
39
- sql: compiledQuery.sql,
40
- parameters,
41
- ...(options && { options }),
42
- };
43
- }, (query) => serializeQuery(query));
44
- export const LoadingPromises = Context.GenericTag("@services/LoadingPromises");
45
- export const LoadingPromiseLive = Layer.effect(LoadingPromises, Effect.gen(function* (_) {
46
- const subscribedQueries = yield* _(SubscribedQueries);
47
- const promises = new Map();
48
- return LoadingPromises.of({
49
- get(query) {
50
- let isNew = false;
51
- let promiseWithResolve = promises.get(query);
52
- if (!promiseWithResolve) {
53
- isNew = true;
54
- let resolve = Function.constVoid;
55
- const promise = new Promise((_resolve) => {
56
- resolve = _resolve;
57
- });
58
- promiseWithResolve = { promise, resolve, releaseOnResolve: false };
59
- promises.set(query, promiseWithResolve);
60
- }
61
- return {
62
- promise: promiseWithResolve.promise,
63
- isNew,
21
+ import { initialSyncState } from "./Sync.js";
22
+ export class EvoluFactory extends Context.Tag("EvoluFactory")() {
23
+ }
24
+ EvoluFactory.Common = Layer.effect(EvoluFactory, Effect.gen(function* () {
25
+ const flushSync = yield* Effect.map(Effect.serviceOption(FlushSync), Option.getOrElse(() => (callback) => callback()));
26
+ const context = Context.empty().pipe(Context.add(DbFactory, yield* DbFactory), Context.add(NanoIdGenerator, yield* NanoIdGenerator), Context.add(FlushSync, flushSync), Context.add(AppState, yield* AppState));
27
+ // For hot/live reloading and future Evolu dynamic import.
28
+ const instances = new Map();
29
+ return EvoluFactory.of({
30
+ createEvolu: (schema, { indexes, initialData, ...config } = {}) => {
31
+ const runtime = createRuntime(config);
32
+ const name = config?.name || defaultConfig.name;
33
+ const dbSchema = {
34
+ tables: schemaToTables(schema),
35
+ indexes: indexes || [],
64
36
  };
65
- },
66
- resolve(query, rows) {
67
- const promiseWithResolve = promises.get(query);
68
- if (!promiseWithResolve)
69
- return;
70
- const result = queryResultFromRows(rows);
71
- if (promiseWithResolve.promise.status !== "fulfilled")
72
- promiseWithResolve.resolve(result);
73
- else
74
- promiseWithResolve.promise = Promise.resolve(result);
75
- setPromiseAsResolved(promiseWithResolve.promise)(result);
76
- if (promiseWithResolve.releaseOnResolve)
77
- promises.delete(query);
78
- },
79
- release() {
80
- const keep = subscribedQueries.getSubscribedQueries();
81
- promises.forEach((promiseWithResolve, query) => {
82
- if (keep.includes(query))
83
- return;
84
- if (promiseWithResolve.promise.status === "fulfilled")
85
- promises.delete(query);
86
- else
87
- promiseWithResolve.releaseOnResolve = true;
88
- });
37
+ let evolu = instances.get(name);
38
+ if (evolu == null) {
39
+ evolu = createEvolu(dbSchema, runtime, initialData).pipe(Effect.provide(context), runtime.runSync);
40
+ instances.set(name, evolu);
41
+ }
42
+ else {
43
+ evolu.ensureSchema(dbSchema);
44
+ }
45
+ return evolu;
89
46
  },
90
47
  });
91
48
  }));
92
- // "For example, a data framework can set the status and value fields on a promise
93
- // preemptively, before passing to React, so that React can unwrap it without waiting
94
- // a microtask."
95
- // https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
96
- const setPromiseAsResolved = (promise) => (value) => {
97
- Object.assign(promise, { status: "fulfilled", value });
49
+ const schemaToTables = (schema) => pipe(getPropertySignatures(schema), Record.toEntries, Arr.map(([name, schema]) => ({
50
+ name,
51
+ columns: Object.keys(getPropertySignatures(schema)),
52
+ })));
53
+ // TODO: Simplify.
54
+ // https://discord.com/channels/795981131316985866/1218626687546294386/1218796529725476935
55
+ const getPropertySignatures = (schema) => {
56
+ const out = {};
57
+ const propertySignatures = AST.getPropertySignatures(schema.ast);
58
+ for (let i = 0; i < propertySignatures.length; i++) {
59
+ const propertySignature = propertySignatures[i];
60
+ out[propertySignature.name] = make(propertySignature.type);
61
+ }
62
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
63
+ return out;
98
64
  };
99
- const LoadQuery = Context.GenericTag("@services/LoadQuery");
100
- const LoadQueryLive = Layer.effect(LoadQuery, Effect.gen(function* (_) {
101
- const loadingPromises = yield* _(LoadingPromises);
102
- const dbWorker = yield* _(DbWorker);
103
- let queries = [];
104
- return LoadQuery.of((query) => {
105
- const { promise, isNew } = loadingPromises.get(query);
106
- if (isNew)
107
- queries = [...queries, query];
108
- if (queries.length === 1) {
109
- queueMicrotask(() => {
110
- if (ReadonlyArray.isNonEmptyReadonlyArray(queries))
111
- dbWorker.postMessage({ _tag: "query", queries });
112
- queries = [];
65
+ const createEvolu = (schema, runtime, initialData) => Effect.gen(function* () {
66
+ yield* Effect.logTrace("EvoluFactory createEvolu");
67
+ const config = yield* Config;
68
+ const dbFactory = yield* DbFactory;
69
+ const appState = yield* AppState;
70
+ const nanoIdGenerator = yield* NanoIdGenerator;
71
+ const flushSync = yield* FlushSync;
72
+ const db = yield* dbFactory.createDb;
73
+ const errorStore = yield* makeStore(null);
74
+ const ownerStore = yield* makeStore(null);
75
+ const rowsStore = yield* makeStore(new Map());
76
+ const syncStateStore = yield* makeStore(initialSyncState);
77
+ const loadingPromises = new Map();
78
+ const subscribedQueries = new Map();
79
+ const handleAllErrors = (effect) => effect.pipe(Effect.catchAllDefect((error) => Effect.fail(makeUnexpectedError(error))), Effect.tapError(Effect.logError), Effect.tapError(errorStore.setState));
80
+ const runFork = flow(handleAllErrors, runtime.runFork);
81
+ const runSync = flow(handleAllErrors, runtime.runSync);
82
+ // const runPromise = flow(handleAllErrors, runtime.runPromise);
83
+ const initialDataAsMutations = yield* Effect.provideService(initialDataToMutations(initialData), NanoIdGenerator, nanoIdGenerator);
84
+ const handleDbError = (error) => {
85
+ Effect.fail(error).pipe(runFork);
86
+ };
87
+ const handleSyncStateChange = (state) => {
88
+ Effect.logDebug(["Evolu handleSyncStateChange", { state }]).pipe(Effect.zipRight(syncStateStore.setState(state)), runFork);
89
+ };
90
+ const handleDbReceive = () => {
91
+ Effect.gen(function* () {
92
+ yield* Effect.logTrace("Evolu handleDbReceive");
93
+ releaseUnsubscribedLoadingPromises();
94
+ const queries = [...subscribedQueries.keys()];
95
+ if (queries.length > 0) {
96
+ yield* Effect.flatMap(db.loadQueries(queries), handlePatches());
97
+ }
98
+ }).pipe(runFork);
99
+ };
100
+ const sync = ({ refreshQueries }) => () => {
101
+ Effect.flatMap(db.sync(refreshQueries ? [...subscribedQueries.keys()] : []), handlePatches()).pipe(runFork);
102
+ };
103
+ db.init(schema, initialDataAsMutations, handleDbError, handleSyncStateChange, handleDbReceive).pipe(Effect.tap(sync({ refreshQueries: false })), Effect.flatMap(ownerStore.setState), Effect.catchTag("NotSupportedPlatformError", () => Effect.void), // no-op
104
+ runFork);
105
+ const appStateReset = yield* appState.init({
106
+ onRequestSync: sync({ refreshQueries: true }),
107
+ reloadUrl: config.reloadUrl,
108
+ });
109
+ const handlePatches = (options) => (patches) => Effect.logDebug(["Evolu handlePatches", { patches }]).pipe(Effect.zipRight(rowsStoreStateFromPatches(patches)), Effect.tap((nextState) => Effect.forEach(patches, ({ query }) => resolveLoadingPromises(query, nextState.get(query) || emptyRows()))), Effect.tap((nextState) => {
110
+ if (options?.flushSync) {
111
+ flushSync(() => {
112
+ rowsStore.setState(nextState).pipe(runSync);
113
113
  });
114
114
  }
115
- return promise;
116
- });
117
- }));
118
- const OnQuery = Context.GenericTag("@services/OnQuery");
119
- const OnQueryLive = Layer.effect(OnQuery, Effect.gen(function* (_) {
120
- const rowsStore = yield* _(RowsStore);
121
- const loadingPromises = yield* _(LoadingPromises);
122
- const flushSync = yield* _(FlushSync);
123
- const onCompletes = yield* _(OnCompletes);
124
- return OnQuery.of(({ queriesPatches, onCompleteIds }) => Effect.gen(function* (_) {
125
- const currentState = rowsStore.getState();
126
- const nextState = pipe(queriesPatches, ReadonlyArray.map(({ query, patches }) => [
115
+ else {
116
+ rowsStore.setState(nextState).pipe(runSync);
117
+ }
118
+ }));
119
+ const rowsStoreStateFromPatches = (patches) => Effect.sync(() => {
120
+ const rowsStoreState = rowsStore.getState();
121
+ if (patches.length === 0)
122
+ return rowsStoreState;
123
+ const queriesRows = Arr.map(patches, ({ query, patches }) => [
127
124
  query,
128
- applyPatches(patches)(currentState.get(query) || emptyRows()),
129
- ]), (map) => new Map([...currentState, ...map]));
130
- queriesPatches.forEach(({ query }) => {
131
- loadingPromises.resolve(query, nextState.get(query) || emptyRows());
132
- });
133
- // No mutation is using onComplete, so we don't need flushSync.
134
- if (onCompleteIds.length === 0) {
135
- yield* _(rowsStore.setState(nextState));
125
+ applyPatches(patches, rowsStoreState.get(query) || emptyRows()),
126
+ ]);
127
+ return new Map([...rowsStoreState, ...queriesRows]);
128
+ });
129
+ const resolveLoadingPromises = (query, rows) => Effect.sync(() => {
130
+ const loadingPromise = loadingPromises.get(query);
131
+ if (!loadingPromise)
136
132
  return;
133
+ const result = queryResultFromRows(rows);
134
+ if (loadingPromise.promise.status !== "fulfilled") {
135
+ loadingPromise.resolve(result);
137
136
  }
138
- flushSync(() => rowsStore.setState(nextState).pipe(Effect.runSync));
139
- yield* _(onCompletes.complete(onCompleteIds));
140
- }));
141
- }));
142
- export const SubscribedQueries = Context.GenericTag("@services/SubscribedQueries");
143
- export const SubscribedQueriesLive = Layer.effect(SubscribedQueries, Effect.gen(function* (_) {
144
- const rowsStore = yield* _(RowsStore);
145
- const subscribedQueries = new Map();
146
- return SubscribedQueries.of({
137
+ else {
138
+ // A promise can't be fulfilled 2x, so we need a new one.
139
+ loadingPromise.promise = Promise.resolve(result);
140
+ }
141
+ /**
142
+ * "For example, a data framework can set the status and value fields on
143
+ * a promise preemptively, before passing to React, so that React can
144
+ * unwrap it without waiting a microtask."
145
+ * https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
146
+ */
147
+ Object.assign(loadingPromise, { status: "fulfilled", value: result });
148
+ if (loadingPromise.releaseOnResolve) {
149
+ loadingPromises.delete(query);
150
+ }
151
+ });
152
+ /**
153
+ * We can't delete loading promises in `resolveLoadingPromises` because they
154
+ * must be cached, so repeated calls to `loadQuery` will always return the
155
+ * same promise until the data changes, and we also can't cache them forever
156
+ * because only subscribed queries are automatically updated (reactivity is
157
+ * expensive) hence this function must be called manually on any mutation.
158
+ */
159
+ const releaseUnsubscribedLoadingPromises = () => {
160
+ [...loadingPromises.entries()]
161
+ .filter(([query]) => !subscribedQueries.has(query))
162
+ .forEach(([query, loadingPromise]) => {
163
+ if (loadingPromise.promise.status === "fulfilled") {
164
+ loadingPromises.delete(query);
165
+ }
166
+ else {
167
+ loadingPromise.releaseOnResolve = true;
168
+ }
169
+ });
170
+ };
171
+ const mutate = (() => {
172
+ let queue = [];
173
+ return (table, { id, ...values }, onComplete) => {
174
+ Effect.logDebug(["Evolu mutate", { table, id, values }]).pipe(runSync);
175
+ const isInsert = id == null;
176
+ if (isInsert)
177
+ id = nanoIdGenerator.rowId.pipe(runSync);
178
+ queue = [...queue, [{ table, id, values, isInsert }, onComplete]];
179
+ if (queue.length === 1)
180
+ queueMicrotask(() => {
181
+ const [mutations, onCompletes] = Arr.unzip(queue);
182
+ queue = [];
183
+ const onCompletesDef = onCompletes.filter(Predicate.isNotUndefined);
184
+ releaseUnsubscribedLoadingPromises();
185
+ db.mutate(mutations, [...subscribedQueries.keys()]).pipe(Effect.flatMap(handlePatches({ flushSync: onCompletesDef.length > 0 })), Effect.tap(() => {
186
+ onCompletesDef.forEach((onComplete) => onComplete());
187
+ }), runFork);
188
+ });
189
+ return { id };
190
+ };
191
+ })();
192
+ const evolu = {
193
+ subscribeError: errorStore.subscribe,
194
+ getError: errorStore.getState,
195
+ createQuery: (queryCallback, options) => pipe(queryCallback(kysely).compile(), (compiledQuery) => {
196
+ if (isSqlMutation(compiledQuery.sql))
197
+ throw new Error("SQL mutation (INSERT, UPDATE, DELETE, etc.) isn't allowed in the Evolu `createQuery` function. Kysely suggests it because there is no read-only Kysely yet, and removing such an API is not possible. For mutations, use Evolu mutation API.");
198
+ const parameters = compiledQuery.parameters;
199
+ return {
200
+ sql: compiledQuery.sql,
201
+ parameters,
202
+ ...(options && { options }),
203
+ };
204
+ }, (query) => serializeQuery(query)),
205
+ loadQuery: (() => {
206
+ let queue = [];
207
+ return (query) => {
208
+ Effect.logDebug([
209
+ "Evolu loadQuery",
210
+ { query: deserializeQuery(query) },
211
+ ]).pipe(runSync);
212
+ let loadingPromise = loadingPromises.get(query);
213
+ if (!loadingPromise) {
214
+ let resolve = constVoid;
215
+ const promise = new Promise((_resolve) => {
216
+ resolve = _resolve;
217
+ });
218
+ loadingPromise = { resolve, promise, releaseOnResolve: false };
219
+ loadingPromises.set(query, loadingPromise);
220
+ queue = [...queue, query];
221
+ if (queue.length === 1) {
222
+ queueMicrotask(() => {
223
+ db.loadQueries(Arr.dedupe(queue)).pipe(Effect.flatMap(handlePatches()), runFork);
224
+ queue = [];
225
+ });
226
+ }
227
+ }
228
+ return loadingPromise.promise;
229
+ };
230
+ })(),
231
+ loadQueries: (queries) => queries.map(evolu.loadQuery),
147
232
  subscribeQuery: (query) => (listener) => {
148
233
  subscribedQueries.set(query, Number.increment(subscribedQueries.get(query) ?? 0));
149
234
  const unsubscribe = rowsStore.subscribe(listener);
@@ -157,90 +242,6 @@ export const SubscribedQueriesLive = Layer.effect(SubscribedQueries, Effect.gen(
157
242
  };
158
243
  },
159
244
  getQuery: (query) => queryResultFromRows(rowsStore.getState().get(query) || emptyRows()),
160
- getSubscribedQueries: () => ReadonlyArray.fromIterable(subscribedQueries.keys()),
161
- });
162
- }));
163
- export const Mutate = Context.GenericTag("@services/Mutate");
164
- const MutateLive = Layer.effect(Mutate, Effect.gen(function* (_) {
165
- const { nanoid } = yield* _(NanoIdGenerator);
166
- const onCompletes = yield* _(OnCompletes);
167
- const subscribedQueries = yield* _(SubscribedQueries);
168
- const loadingPromises = yield* _(LoadingPromises);
169
- const dbWorker = yield* _(DbWorker);
170
- let mutations = [];
171
- return Mutate.of((table, { id, ...values }, onComplete) => {
172
- const isInsert = id == null;
173
- if (isInsert)
174
- id = Effect.runSync(nanoid);
175
- const onCompleteId = onComplete
176
- ? onCompletes.add(onComplete).pipe(Effect.runSync)
177
- : null;
178
- mutations = [
179
- ...mutations,
180
- {
181
- table: table.toString(),
182
- id,
183
- values,
184
- isInsert,
185
- onCompleteId,
186
- },
187
- ];
188
- if (mutations.length === 1)
189
- queueMicrotask(() => {
190
- if (ReadonlyArray.isNonEmptyReadonlyArray(mutations))
191
- dbWorker.postMessage({
192
- _tag: "mutate",
193
- mutations,
194
- queries: subscribedQueries.getSubscribedQueries(),
195
- });
196
- loadingPromises.release();
197
- mutations = [];
198
- });
199
- return { id };
200
- });
201
- }));
202
- /** EvoluCommon is without side effects, so it's unit-testable. */
203
- const EvoluCommon = Layer.effect(Evolu, Effect.gen(function* (_) {
204
- const dbWorker = yield* _(DbWorker);
205
- const errorStore = yield* _(makeErrorStore);
206
- const loadQuery = yield* _(LoadQuery);
207
- const onQuery = yield* _(OnQuery);
208
- const { subscribeQuery, getQuery, getSubscribedQueries } = yield* _(SubscribedQueries);
209
- const syncStateStore = yield* _(makeStore({ _tag: "SyncStateInitial" }));
210
- const mutate = yield* _(Mutate);
211
- const ownerStore = yield* _(makeStore(null));
212
- const loadingPromises = yield* _(LoadingPromises);
213
- const appState = yield* _(AppState);
214
- dbWorker.onMessage = (output) => Match.value(output).pipe(Match.tagsExhaustive({
215
- onError: ({ error }) => errorStore.setState(error),
216
- onQuery,
217
- onOwner: ({ owner }) => ownerStore.setState(owner),
218
- onSyncState: ({ state }) => syncStateStore.setState(state),
219
- onReceive: () => Effect.sync(() => {
220
- loadingPromises.release();
221
- const queries = getSubscribedQueries();
222
- if (ReadonlyArray.isNonEmptyReadonlyArray(queries))
223
- dbWorker.postMessage({ _tag: "query", queries });
224
- }),
225
- onResetOrRestore: () => appState.reset,
226
- }), Effect.runSync);
227
- dbWorker.postMessage({
228
- _tag: "init",
229
- config: yield* _(Config),
230
- });
231
- const sync = () => {
232
- dbWorker.postMessage({ _tag: "sync", queries: getSubscribedQueries() });
233
- };
234
- appState.init({ onRequestSync: sync });
235
- sync();
236
- return Evolu.of({
237
- subscribeError: errorStore.subscribe,
238
- getError: errorStore.getState,
239
- createQuery: makeCreateQuery(),
240
- loadQuery,
241
- loadQueries: (queries) => queries.map(loadQuery),
242
- subscribeQuery,
243
- getQuery,
244
245
  subscribeOwner: ownerStore.subscribe,
245
246
  getOwner: ownerStore.getState,
246
247
  subscribeSyncState: syncStateStore.subscribe,
@@ -248,34 +249,61 @@ const EvoluCommon = Layer.effect(Evolu, Effect.gen(function* (_) {
248
249
  create: mutate,
249
250
  update: mutate,
250
251
  createOrUpdate: mutate,
251
- resetOwner: () => dbWorker.postMessage({ _tag: "reset" }),
252
- restoreOwner: (mnemonic) => dbWorker.postMessage({ _tag: "reset", mnemonic }),
253
- ensureSchema: (schema, indexes = []) => dbWorker.postMessage({
254
- _tag: "ensureSchema",
255
- tables: schemaToTables(schema),
256
- indexes,
257
- }),
258
- sync,
259
- });
260
- })).pipe(Layer.provide(Layer.mergeAll(LoadQueryLive, OnQueryLive, MutateLive)), Layer.provide(LoadingPromiseLive), Layer.provide(SubscribedQueriesLive), Layer.provide(Layer.merge(RowsStoreLive, OnCompletesLive)));
261
- export const EvoluCommonLive = EvoluCommon.pipe(Layer.provide(NanoIdGeneratorLive));
252
+ resetOwner: () => {
253
+ db.resetOwner().pipe(Effect.zipRight(appStateReset.reset), runFork);
254
+ },
255
+ restoreOwner: (mnemonic) => {
256
+ db.restoreOwner(schema, mnemonic).pipe(Effect.zipRight(appStateReset.reset), runFork);
257
+ },
258
+ ensureSchema: (schema) => {
259
+ db.ensureSchema(schema).pipe(runFork);
260
+ },
261
+ };
262
+ return evolu;
263
+ });
264
+ const initialDataToMutations = (initialData = constVoid) => Effect.map(NanoIdGenerator, (nanoIdGenerator) => {
265
+ const mutations = [];
266
+ const mutate = (table, { id, ...values }) => {
267
+ if (id == null)
268
+ id = nanoIdGenerator.rowId.pipe(Effect.runSync);
269
+ mutations.push({ isInsert: true, id, table: table, values });
270
+ return { id };
271
+ };
272
+ const evolu = {
273
+ create: mutate,
274
+ createOrUpdate: mutate,
275
+ };
276
+ initialData(evolu);
277
+ return mutations;
278
+ });
279
+ // https://kysely.dev/docs/recipes/splitting-query-building-and-execution
280
+ const kysely = new Kysely.Kysely({
281
+ dialect: {
282
+ createAdapter: () => new Kysely.SqliteAdapter(),
283
+ createDriver: () => new Kysely.DummyDriver(),
284
+ createIntrospector() {
285
+ throw "Not implemeneted";
286
+ },
287
+ createQueryCompiler: () => new Kysely.SqliteQueryCompiler(),
288
+ },
289
+ });
290
+ const createIndex = kysely.schema.createIndex.bind(kysely.schema);
262
291
  /**
263
- * The recipe for creating Evolu for a platform and UI library:
292
+ * Create SQLite indexes.
264
293
  *
265
- * 1. Export everything from "@evolu/common/public"
266
- * 2. Export platform-specific parseMnemonic. If the platform supports lazy import,
267
- * use it because dictionaries have a few hundred KBs.
268
- * 3. Export `createEvolu` for a platform. The TS docs must be copy-pasted, and
269
- * remember to update the import.
270
- * 4. Export UI library API code.
294
+ * See https://www.evolu.dev/docs/indexes
295
+ *
296
+ * @example
297
+ * const indexes = createIndexes((create) => [
298
+ * create("indexTodoCreatedAt").on("todo").column("createdAt"),
299
+ * create("indexTodoCategoryCreatedAt")
300
+ * .on("todoCategory")
301
+ * .column("createdAt"),
302
+ * ]);
271
303
  */
272
- export const makeCreateEvolu = (EvoluLive) => (schema, config) => {
273
- // For https://nextjs.org/docs/architecture/fast-refresh etc.
274
- const evolu = GlobalValue.globalValue("@evolu/common", () => Evolu.pipe(Effect.provide(EvoluLive), Effect.provide(ConfigLive(config)), Effect.runSync));
275
- const indexes = config?.indexes?.map((index) => ({
276
- name: index.toOperationNode().name.name,
277
- sql: index.compile().sql,
278
- }));
279
- evolu.ensureSchema(schema, indexes);
280
- return evolu;
281
- };
304
+ export const createIndexes = (callback) => callback(createIndex).map((index) => ({
305
+ name: index.toOperationNode().name.name,
306
+ sql: index.compile().sql,
307
+ }));
308
+ /** Create a namespaced lock name. */
309
+ export const getLockName = (name) => Effect.map(Config, (config) => `evolu:${config.name}:${name}`);
@@ -1,4 +1,6 @@
1
1
  import * as S from "@effect/schema/Schema";
2
+ import * as Types from "effect/Types";
3
+ import { Value } from "./Sqlite.js";
2
4
  /** Branded Id Schema. To create Id Schema for a specific table, use {@link id}. */
3
5
  export declare const Id: S.brand<S.Schema<string, string, never>, "Id">;
4
6
  export type Id = S.Schema.Type<typeof Id>;
@@ -45,6 +47,55 @@ export declare function cast(value: boolean): SqliteBoolean;
45
47
  export declare function cast(value: SqliteBoolean): boolean;
46
48
  export declare function cast(value: Date): SqliteDate;
47
49
  export declare function cast(value: SqliteDate): Date;
50
+ interface EvoluTypeError<E extends string> {
51
+ readonly __evoluTypeError__: E;
52
+ }
53
+ /**
54
+ * Create table schema.
55
+ *
56
+ * Supported types are null, string, number, Uint8Array, JSON Object, and JSON
57
+ * Array. Use SqliteDate for dates and SqliteBoolean for booleans.
58
+ *
59
+ * Reserved columns are createdAt, updatedAt, isDeleted. Those columns are added
60
+ * by default.
61
+ *
62
+ * @example
63
+ * const TodoId = id("Todo");
64
+ * type TodoId = S.Schema.Type<typeof TodoId>;
65
+ *
66
+ * const TodoTable = table({
67
+ * id: TodoId,
68
+ * title: NonEmptyString1000,
69
+ * isCompleted: S.nullable(SqliteBoolean),
70
+ * });
71
+ * type TodoTable = S.Schema.Type<typeof TodoTable>;
72
+ */
73
+ export declare const table: <Fields extends TableFields>(fields: Fields) => ValidateFieldsTypes<Fields> extends true ? ValidateFieldsNames<Fields> extends true ? ValidateFieldsHasId<Fields> extends true ? S.Schema<Types.Simplify<S.Struct.Type<Fields> & S.Schema.Type<typeof ReservedColumns>>, Types.Simplify<S.Struct.Encoded<Fields> & S.Schema.Encoded<typeof ReservedColumns>>> : EvoluTypeError<"table() called without id column."> : EvoluTypeError<"table() called with a reserved column. Reserved columns are createdAt, updatedAt, isDeleted. Those columns are added by default."> : EvoluTypeError<"table() called with unsupported type. Supported types are null, string, number, Uint8Array, JSON Object, and JSON Array. Use SqliteDate for dates and SqliteBoolean for booleans.">;
74
+ declare const ReservedColumns: S.Struct<{
75
+ createdAt: S.brand<S.Schema<string, string, never>, "SqliteDate">;
76
+ updatedAt: S.brand<S.Schema<string, string, never>, "SqliteDate">;
77
+ isDeleted: S.brand<S.Schema<number, number, never>, "SqliteBoolean">;
78
+ }>;
79
+ type TableFields = Record<string, S.Schema<any>>;
80
+ type ValidateFieldsTypes<Fields extends TableFields> = keyof Fields extends infer K ? K extends keyof Fields ? Fields[K] extends TableFields ? ValidateFieldsTypes<Fields[K]> : Fields[K] extends S.Schema<infer A, infer _I> ? A extends Value ? true : false : never : never : never;
81
+ type ValidateFieldsNames<Fields extends TableFields> = keyof Fields extends infer K ? K extends keyof Fields ? K extends "createdAt" | "updatedAt" | "isDeleted" ? false : true : never : never;
82
+ type ValidateFieldsHasId<Fields extends TableFields> = "id" extends keyof Fields ? true : false;
83
+ /**
84
+ * Create database schema.
85
+ *
86
+ * Tables with a name prefixed with _ are local-only, which means they are not
87
+ * synced. Local-only tables are useful for device-specific or temporal data.
88
+ *
89
+ * @example
90
+ * const Database = database({
91
+ * // A local-only table.
92
+ * _todo: TodoTable,
93
+ * todo: TodoTable,
94
+ * todoCategory: TodoCategoryTable,
95
+ * });
96
+ * type Database = S.Schema.Type<typeof Database>;
97
+ */
98
+ export declare const database: typeof S.Struct;
48
99
  /**
49
100
  * String schema represents a string that is not stringified JSON. Using String
50
101
  * schema for strings stored in SQLite is crucial to ensure a stored string is
@@ -86,4 +137,5 @@ export type NonEmptyString1000 = S.Schema.Type<typeof NonEmptyString1000>;
86
137
  */
87
138
  export declare const PositiveInt: S.brand<S.Schema<number, number, never>, "PositiveInt">;
88
139
  export type PositiveInt = S.Schema.Type<typeof PositiveInt>;
140
+ export {};
89
141
  //# sourceMappingURL=Model.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../src/Model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,uBAAuB,CAAC;AAG3C,mFAAmF;AACnF,eAAO,MAAM,EAAE,gDAAyD,CAAC;AACzE,MAAM,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AAE1C;;;;;;;;;GASG;AACH,eAAO,MAAM,EAAE,4BACN,CAAC,KACP,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CACnC,CAAC;AAE1B;;;;GAIG;AACH,eAAO,MAAM,UAAU,wDAGtB,CAAC;AACF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,UAAU,CAAC,CAAC;AAE1D;;;;GAIG;AACH,eAAO,MAAM,aAAa,2DAIzB,CAAC;AACF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAAC;AACpD,wBAAgB,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC;AACpD,wBAAgB,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,UAAU,CAAC;AAC9C,wBAAgB,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;AAW9C;;;;;GAKG;AACH,eAAO,MAAM,MAAM,oDAclB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,CAAC;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,iGAAwD,CAAC;AAChF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,UAAU,CAAC,CAAC;AAE1D;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,yGAI9B,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE1E;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,yDAIvB,CAAC;AACF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC,CAAC"}
1
+ {"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../src/Model.ts"],"names":[],"mappings":"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"}