@evolu/common 4.1.1 → 5.0.1
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/README.md +1 -1
- package/dist/src/Config.d.ts +27 -30
- package/dist/src/Config.d.ts.map +1 -1
- package/dist/src/Config.js +32 -5
- package/dist/src/Crdt.d.ts +5 -4
- package/dist/src/Crdt.d.ts.map +1 -1
- package/dist/src/Crdt.js +27 -24
- package/dist/src/Crypto.d.ts +16 -10
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +24 -11
- package/dist/src/Db.d.ts +58 -78
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +368 -170
- package/dist/src/Diff.d.ts +9 -3
- package/dist/src/Diff.d.ts.map +1 -1
- package/dist/src/Diff.js +12 -11
- package/dist/src/Error.d.ts +15 -0
- package/dist/src/Error.d.ts.map +1 -0
- package/dist/src/Error.js +14 -0
- package/dist/src/Evolu.d.ts +139 -91
- package/dist/src/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu.js +267 -239
- package/dist/src/Model.d.ts +52 -0
- package/dist/src/Model.d.ts.map +1 -1
- package/dist/src/Model.js +47 -5
- package/dist/src/Owner.d.ts.map +1 -1
- package/dist/src/Owner.js +14 -8
- package/dist/src/Platform.d.ts +24 -34
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +9 -31
- package/dist/src/Public.d.ts +3 -5
- package/dist/src/Public.d.ts.map +1 -1
- package/dist/src/Public.js +2 -3
- package/dist/src/Sqlite.d.ts +25 -16
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +28 -20
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Sync.d.ts +70 -0
- package/dist/src/Sync.d.ts.map +1 -0
- package/dist/src/Sync.js +126 -0
- package/dist/src/index.d.ts +2 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -5
- package/package.json +12 -10
- package/src/Config.ts +77 -36
- package/src/Crdt.ts +61 -76
- package/src/Crypto.ts +83 -60
- package/src/Db.ts +782 -352
- package/src/Diff.ts +23 -21
- package/src/Error.ts +29 -0
- package/src/Evolu.ts +599 -499
- package/src/Model.ts +102 -6
- package/src/Owner.ts +24 -24
- package/src/Platform.ts +33 -81
- package/src/Public.ts +3 -5
- package/src/Sqlite.ts +81 -44
- package/src/Sync.ts +306 -0
- package/src/index.ts +2 -5
- package/dist/src/DbWorker.d.ts +0 -82
- package/dist/src/DbWorker.d.ts.map +0 -1
- package/dist/src/DbWorker.js +0 -335
- package/dist/src/ErrorStore.d.ts +0 -29
- package/dist/src/ErrorStore.d.ts.map +0 -1
- package/dist/src/ErrorStore.js +0 -13
- package/dist/src/OnCompletes.d.ts +0 -14
- package/dist/src/OnCompletes.d.ts.map +0 -1
- package/dist/src/OnCompletes.js +0 -25
- package/dist/src/SyncWorker.d.ts +0 -80
- package/dist/src/SyncWorker.d.ts.map +0 -1
- package/dist/src/SyncWorker.js +0 -150
- package/dist/src/Types.d.ts +0 -13
- package/dist/src/Types.d.ts.map +0 -1
- package/dist/src/Types.js +0 -1
- package/src/DbWorker.ts +0 -721
- package/src/ErrorStore.ts +0 -46
- package/src/OnCompletes.ts +0 -51
- package/src/SyncWorker.ts +0 -361
- 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
|
|
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
|
|
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,
|
|
12
|
-
import { NanoIdGenerator
|
|
13
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
129
|
-
])
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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: () =>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
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
|
-
*
|
|
292
|
+
* Create SQLite indexes.
|
|
264
293
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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}`);
|
package/dist/src/Model.d.ts
CHANGED
|
@@ -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
|
package/dist/src/Model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../src/Model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../src/Model.ts"],"names":[],"mappings":"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"}
|