@jarenjs/studio 0.83.3 → 0.84.3
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 +21 -6
- package/contracts/data.contract.json +243 -0
- package/dist/types/component/data/actions.d.ts +234 -0
- package/dist/types/component/data/index.d.ts +9 -0
- package/dist/types/component/data/mount.d.ts +87 -0
- package/dist/types/component/data/project-widget.d.ts +27 -0
- package/dist/types/component/data/viewmodel.d.ts +85 -0
- package/dist/types/component/data/views.d.ts +148 -0
- package/dist/types/component/document.d.ts +82 -0
- package/dist/types/component/flow/actions.d.ts +708 -0
- package/dist/types/component/flow/index.d.ts +7 -0
- package/dist/types/component/flow/mount.d.ts +66 -0
- package/dist/types/component/flow/project-widget.d.ts +8 -0
- package/dist/types/component/flow/runtime.d.ts +57 -0
- package/dist/types/component/flow/views.d.ts +242 -0
- package/dist/types/component/host.d.ts +2 -2
- package/dist/types/component/index.d.ts +21 -15
- package/dist/types/component/mount.d.ts +51 -0
- package/dist/types/component/project-actions.d.ts +302 -0
- package/dist/types/component/project-controller.d.ts +81 -0
- package/dist/types/component/project-state.d.ts +1 -0
- package/dist/types/component/project.d.ts +327 -0
- package/dist/types/component/shared/host-widget.d.ts +24 -0
- package/dist/types/component/shared/memo.d.ts +12 -0
- package/dist/types/component/shared/nodes.d.ts +92 -0
- package/dist/types/component/shared/schema-options.d.ts +28 -0
- package/dist/types/component/shared/studio-kit.d.ts +78 -0
- package/dist/types/component/shared/ui.d.ts +136 -0
- package/dist/types/component/view.d.ts +6 -6
- package/dist/types/data/boot-stages.d.ts +97 -0
- package/dist/types/data/browser-worker.d.ts +22 -0
- package/dist/types/data/contract.d.ts +9 -0
- package/dist/types/data/editor.d.ts +81 -0
- package/dist/types/data/handlers.d.ts +130 -0
- package/dist/types/data/host.d.ts +8 -0
- package/dist/types/data/project-worker.d.ts +8 -0
- package/dist/types/data/runtime.d.ts +46 -0
- package/dist/types/data/state.d.ts +33 -0
- package/dist/types/data/storage.d.ts +26 -0
- package/dist/types/data/transport.d.ts +69 -0
- package/dist/types/flow-document.d.ts +19 -0
- package/dist/types/flow-editor.d.ts +88 -0
- package/docs/EDITORS.md +158 -0
- package/docs/PROJECT-FORMAT.md +1 -11
- package/package.json +32 -15
- package/src/component/data/actions.js +138 -0
- package/src/component/data/index.js +10 -0
- package/src/component/data/mount.js +63 -0
- package/src/component/data/project-widget.js +164 -0
- package/src/component/data/viewmodel.js +185 -0
- package/src/component/data/views.js +241 -0
- package/src/component/document.js +344 -0
- package/src/component/flow/actions.js +331 -0
- package/src/component/flow/index.js +8 -0
- package/src/component/flow/mount.js +53 -0
- package/src/component/flow/project-widget.js +44 -0
- package/src/component/flow/runtime.js +481 -0
- package/src/component/flow/views.js +196 -0
- package/src/component/host.js +2 -2
- package/src/component/index.js +19 -9
- package/src/component/mount.js +43 -0
- package/src/component/project-actions.js +189 -0
- package/src/component/project-controller.js +248 -0
- package/src/component/project-state.js +30 -0
- package/src/component/project.js +308 -0
- package/src/component/shared/host-widget.js +35 -0
- package/src/component/shared/memo.js +28 -0
- package/src/component/shared/nodes.js +94 -0
- package/src/component/shared/schema-options.js +30 -0
- package/src/component/shared/studio-kit.js +59 -0
- package/src/component/shared/ui.js +134 -0
- package/src/data/boot-stages.js +202 -0
- package/src/data/browser-worker.js +247 -0
- package/src/data/contract.js +7 -0
- package/src/data/editor.js +95 -0
- package/src/data/handlers.js +349 -0
- package/src/data/host.js +8 -0
- package/src/data/project-worker.js +26 -0
- package/src/data/runtime.js +462 -0
- package/src/data/state.js +46 -0
- package/src/data/storage.js +61 -0
- package/src/data/transport.js +215 -0
- package/src/flow-document.js +24 -0
- package/src/flow-editor.js +98 -0
- package/styles/data.css +53 -0
- package/styles/editor.css +150 -0
- package/styles/flow.css +99 -0
- package/styles/studio.css +1 -0
- package/dist/types/author.d.ts +0 -27
- package/src/author.js +0 -55
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file The data studio's handler table, apart from the browser that
|
|
4
|
+
* hosts it.
|
|
5
|
+
*
|
|
6
|
+
* `db-worker.js` is the OWNER context: it loads the SQLite wasm build,
|
|
7
|
+
* claims the OPFS access-handle pool and serves two transports. None of
|
|
8
|
+
* that is testable outside a browser, and none of it is where the
|
|
9
|
+
* studio's behaviour lives — so the behaviour is here, over an injected
|
|
10
|
+
* host, and the worker supplies the real one. The same table then runs
|
|
11
|
+
* under a Node driver in the test suite, which is how the store rules
|
|
12
|
+
* below are held to anything at all.
|
|
13
|
+
*
|
|
14
|
+
* What the table owns, and the worker deliberately does not:
|
|
15
|
+
*
|
|
16
|
+
* - **The live registry.** Every `data.live` registration is held in a
|
|
17
|
+
* set, and `data.lives` reports its SIZE. A count kept as a number
|
|
18
|
+
* beside the registrations drifts the moment a reopen zeroes it while
|
|
19
|
+
* subscriptions are still held — under-reporting forever, and going
|
|
20
|
+
* negative on the next release, at which point the count fails its own
|
|
21
|
+
* output schema and freezes at its last good value. A frozen count
|
|
22
|
+
* reads exactly like a healthy one, which is why this is a set.
|
|
23
|
+
* - **Reopening releases.** Closing the store releases every registration
|
|
24
|
+
* it fed, so the count comes back to zero because the subscriptions
|
|
25
|
+
* ended, not because someone assigned zero.
|
|
26
|
+
* - **A refused migration does not advance the model.** `state.model`
|
|
27
|
+
* moves only when `migrate()` returned; a store reopened after a
|
|
28
|
+
* refusal is reopened on the model it still has, and the refusal
|
|
29
|
+
* crosses as the declared `db` failure.
|
|
30
|
+
* - **Peers are told.** Any reopen — open, recreate, migrate — drops the
|
|
31
|
+
* live registrations of every tab, so the worker announces it and the
|
|
32
|
+
* boundaries resubscribe. Silence would leave every live pane showing
|
|
33
|
+
* its last rows forever while looking live.
|
|
34
|
+
* - **Recreating is owner-only.** `reset` unlinks the database the owner
|
|
35
|
+
* holds; a client tab asking for it is refused rather than served, so
|
|
36
|
+
* the control that is honest in the owning tab cannot be destructive
|
|
37
|
+
* from another one.
|
|
38
|
+
* - **The oracle's store is throwaway.** `data.oracle` opens a fresh
|
|
39
|
+
* in-memory store on the model it is handed, answers one query over
|
|
40
|
+
* the documents it is handed (with its plan, when asked), and closes
|
|
41
|
+
* it — the store this context holds, its registrations and its peers
|
|
42
|
+
* are untouched. That is what
|
|
43
|
+
* lets the committed spatial corpus hold SQLite-in-wasm to the
|
|
44
|
+
* JavaScript engine from any tab, including one with no OPFS at all.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { openStore, migrate, planModelMigration } from '@jarenjs/db';
|
|
48
|
+
import { ContractFailure } from '@jarenjs/contract';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The host the table runs over: everything that differs between a
|
|
52
|
+
* browser worker holding an OPFS pool and a test holding a file.
|
|
53
|
+
* @typedef {Object} DataHost
|
|
54
|
+
* @property {() => Promise<any>} init - Decide the topology and prepare
|
|
55
|
+
* this context; resolves to the `data.init` answer.
|
|
56
|
+
* @property {() => any} makeDriver - A fresh driver over a fresh handle.
|
|
57
|
+
* @property {() => any} makeScratchDriver - A driver over a throwaway
|
|
58
|
+
* in-memory handle: the oracle's store, opened and closed per call,
|
|
59
|
+
* never the database this context holds.
|
|
60
|
+
* @property {() => string} path - The database path for this context.
|
|
61
|
+
* @property {() => string} vfs - The VFS this context settled on.
|
|
62
|
+
* @property {() => boolean} durable - Whether a migration can be applied
|
|
63
|
+
* here at all; a memory store recreates instead.
|
|
64
|
+
* @property {() => void} unlink - Drop the persistent database.
|
|
65
|
+
* @property {(notice: any) => void} announce - Tell every peer the store
|
|
66
|
+
* was reopened under them.
|
|
67
|
+
* @property {any} operators - The operator registry the store mounts.
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The details of the declared `db` failure: the store's own coded refusal
|
|
72
|
+
* and its message.
|
|
73
|
+
*
|
|
74
|
+
* The contract carries string codes, including numeric DOMException
|
|
75
|
+
* codes converted to strings. The host's message survives unchanged.
|
|
76
|
+
* @param {unknown} error
|
|
77
|
+
* @returns {{ code: string | null, message: string }}
|
|
78
|
+
*/
|
|
79
|
+
export function wireError(error) {
|
|
80
|
+
const raised = /** @type {any} */ (error);
|
|
81
|
+
const code = raised?.code;
|
|
82
|
+
const message = String(raised?.message ?? error);
|
|
83
|
+
return { code: code === undefined || code === null ? null : String(code), message };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A refusal only the owning context can answer.
|
|
88
|
+
* @param {string} message
|
|
89
|
+
* @returns {Error}
|
|
90
|
+
*/
|
|
91
|
+
function ownerOnly(message) {
|
|
92
|
+
const error = new Error(message);
|
|
93
|
+
/** @type {any} */ (error).code = 'JD2061';
|
|
94
|
+
return error;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The studio's handler table over an injected host.
|
|
99
|
+
* @param {DataHost} host
|
|
100
|
+
* @returns {{ handlers: Record<string, any>, clientHandlers: Record<string, any>, state: any, dispose: () => Promise<void> }}
|
|
101
|
+
*/
|
|
102
|
+
export function createDataHandlers(host) {
|
|
103
|
+
let disposed = false;
|
|
104
|
+
function active() {
|
|
105
|
+
if (disposed) throw Object.assign(new Error('the data host is disposed'), { code: 'JD2005' });
|
|
106
|
+
}
|
|
107
|
+
/** An asynchronous open must release its handle if the host left meanwhile. */
|
|
108
|
+
async function acceptStore(next) {
|
|
109
|
+
if (disposed) { await next.close(); active(); }
|
|
110
|
+
state.store = next;
|
|
111
|
+
}
|
|
112
|
+
const state = {
|
|
113
|
+
/** @type {any} */ store: null,
|
|
114
|
+
/** @type {any} */ model: null,
|
|
115
|
+
/** Live registrations, held rather than counted (see the file note). */
|
|
116
|
+
/** @type {Set<any>} */ lives: new Set(),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** Release one registration exactly once, whichever side asks. */
|
|
120
|
+
const releaser = (/** @type {any} */ entry) => () => {
|
|
121
|
+
if (!state.lives.delete(entry)) return;
|
|
122
|
+
entry.live.close();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** The open store, or the coded refusal that says why there is none.
|
|
126
|
+
* An open that failed leaves this context with no store at all, and
|
|
127
|
+
* every later operation reading through a null one reported a host
|
|
128
|
+
* TypeError — a defect message for a state the studio can explain. */
|
|
129
|
+
function store() {
|
|
130
|
+
active();
|
|
131
|
+
if (state.store === null) {
|
|
132
|
+
const error = new Error('the store is not open: the last open was refused,'
|
|
133
|
+
+ ' so recreate it from the model pane');
|
|
134
|
+
/** @type {any} */ (error).code = 'JD2005';
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
return state.store;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Close the store, ending every subscription it was feeding. */
|
|
141
|
+
const closeStore = async () => {
|
|
142
|
+
if (state.store === null) return;
|
|
143
|
+
for (const entry of [...state.lives]) entry.stop();
|
|
144
|
+
const store = state.store;
|
|
145
|
+
state.store = null;
|
|
146
|
+
await store.close();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Open (or recreate) the store on a model. Everything that reopens goes
|
|
151
|
+
* through here, so nothing can reopen without releasing first.
|
|
152
|
+
* @param {any} args
|
|
153
|
+
*/
|
|
154
|
+
async function open(args) {
|
|
155
|
+
await closeStore();
|
|
156
|
+
active();
|
|
157
|
+
if (args.reset === true) await host.unlink();
|
|
158
|
+
active();
|
|
159
|
+
state.model = args.model;
|
|
160
|
+
await acceptStore(await openStore(args.model, {
|
|
161
|
+
driver: host.makeDriver(), path: host.path(), capture: true, operators: host.operators,
|
|
162
|
+
}));
|
|
163
|
+
const opened = storeInfo();
|
|
164
|
+
host.announce({ store: 'opened', reset: args.reset === true, ...opened });
|
|
165
|
+
return opened;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The model and capabilities every attached tab must follow. */
|
|
169
|
+
function storeInfo() {
|
|
170
|
+
const capabilities = store().capabilities;
|
|
171
|
+
const collection = Object.keys(state.model.collections)[0];
|
|
172
|
+
return {
|
|
173
|
+
vfs: host.vfs(),
|
|
174
|
+
model: state.model,
|
|
175
|
+
collection,
|
|
176
|
+
keyPointer: state.model.collections[collection].key ?? '/id',
|
|
177
|
+
capabilities: {
|
|
178
|
+
version: capabilities.version,
|
|
179
|
+
capture: capabilities.capture,
|
|
180
|
+
live: capabilities.live,
|
|
181
|
+
userFunctions: capabilities.userFunctions,
|
|
182
|
+
operators: capabilities.operators,
|
|
183
|
+
pushableOperators: capabilities.pushableOperators,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Plan a migration, apply it, and reopen. The store is reopened either
|
|
190
|
+
* way — on the new model when the migration returned, on the model it
|
|
191
|
+
* still has when it did not — and a refusal is re-thrown so it reaches
|
|
192
|
+
* the caller as the declared failure rather than as a report of work
|
|
193
|
+
* that never happened.
|
|
194
|
+
* @param {any} args
|
|
195
|
+
*/
|
|
196
|
+
async function migrateTo(args) {
|
|
197
|
+
const baseline = state.model;
|
|
198
|
+
const { migration, report } = planModelMigration(baseline, args.to,
|
|
199
|
+
{ dialect: store().dialect, id: args.id ?? 'studio-migration' });
|
|
200
|
+
const rendered = migration.steps.map((/** @type {any} */ step) => step.sql ?? step.kind);
|
|
201
|
+
await closeStore();
|
|
202
|
+
active();
|
|
203
|
+
const driver = host.makeDriver();
|
|
204
|
+
const path = host.path();
|
|
205
|
+
/** @type {any} */
|
|
206
|
+
let applied = null;
|
|
207
|
+
/** @type {unknown} */
|
|
208
|
+
let refused = null;
|
|
209
|
+
try {
|
|
210
|
+
applied = host.durable()
|
|
211
|
+
? await migrate({ driver, path }, [migration],
|
|
212
|
+
{ baseline, model: args.to, shadow: true })
|
|
213
|
+
: { applied: [], note: 'memory stores recreate instead of migrating' };
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
refused = error;
|
|
217
|
+
}
|
|
218
|
+
active();
|
|
219
|
+
state.model = refused === null ? args.to : baseline;
|
|
220
|
+
await acceptStore(await openStore(state.model,
|
|
221
|
+
{ driver, path, capture: true, operators: host.operators }));
|
|
222
|
+
host.announce({ store: 'migrated', applied: refused === null, ...storeInfo() });
|
|
223
|
+
if (refused !== null) throw refused;
|
|
224
|
+
const note = applied.note;
|
|
225
|
+
return {
|
|
226
|
+
planned: rendered,
|
|
227
|
+
losses: report?.losses ?? [],
|
|
228
|
+
// the runner answers the ids it applied, as strings — reading an
|
|
229
|
+
// `id` member off them published a list of nulls under a
|
|
230
|
+
// declaration of strings, and the page counted them as successes
|
|
231
|
+
applied: applied.applied ?? [],
|
|
232
|
+
...(note !== undefined ? { note } : {}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Subscribe a live query, as the stream binding's duck-typed
|
|
238
|
+
* subscription. The registration is held so the count is its size and
|
|
239
|
+
* so a reopen can end it.
|
|
240
|
+
* @param {any} input
|
|
241
|
+
*/
|
|
242
|
+
async function live(input) {
|
|
243
|
+
const query = await store().collection(input.collection)
|
|
244
|
+
.live(input.document, { externals: input.externals ?? {} });
|
|
245
|
+
if (disposed) { query.close(); active(); }
|
|
246
|
+
/** @type {any} */
|
|
247
|
+
const entry = { live: query };
|
|
248
|
+
entry.stop = releaser(entry);
|
|
249
|
+
state.lives.add(entry);
|
|
250
|
+
return {
|
|
251
|
+
snapshot: () => query.result,
|
|
252
|
+
subscribe: (/** @type {(emission: any) => void} */ cb) => query.subscribe(cb),
|
|
253
|
+
close: () => entry.stop(),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Run one query over a throwaway store: a fresh in-memory store on
|
|
259
|
+
* the given model, the documents inserted, the query executed, and
|
|
260
|
+
* the store closed whatever happened. Nothing here reads or writes
|
|
261
|
+
* the store this context holds, which is what lets a second executor
|
|
262
|
+
* be held to the engine's recorded answers from any tab — the corpus
|
|
263
|
+
* needs execution, not persistence.
|
|
264
|
+
*
|
|
265
|
+
* The empty sequence is `undefined`, which JSON cannot carry and
|
|
266
|
+
* `null` must not stand in for (null is an answer the engine can
|
|
267
|
+
* record). It crosses as a flag beside a null answer.
|
|
268
|
+
* @param {any} input
|
|
269
|
+
*/
|
|
270
|
+
async function oracle(input) {
|
|
271
|
+
const scratch = await openStore(input.model,
|
|
272
|
+
{ driver: host.makeScratchDriver(), path: ':memory:', operators: host.operators });
|
|
273
|
+
try {
|
|
274
|
+
const rows = scratch.collection(input.collection);
|
|
275
|
+
for (const document of input.documents) await rows.insert(document);
|
|
276
|
+
const options = { externals: input.externals ?? {} };
|
|
277
|
+
const answer = await rows.execute(input.query, options);
|
|
278
|
+
/** @type {any} */
|
|
279
|
+
const out = { answer: answer === undefined ? null : answer, empty: answer === undefined };
|
|
280
|
+
// the plan beside the answer, when asked: the same record
|
|
281
|
+
// `data.explain` gives over the studio's store, so a reader can
|
|
282
|
+
// see the pushdown a throwaway store ran, not only what it said
|
|
283
|
+
if (input.explain === true) out.explain = await rows.explain(input.query, options);
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
await scratch.close();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Every store rejection crosses as the declared `db` failure, so a
|
|
292
|
+
* genuine host bug is the only thing that answers the binding's JC2070.
|
|
293
|
+
* @param {(input: any) => any} fn */
|
|
294
|
+
const guard = (fn) => async (/** @type {any} */ input) => {
|
|
295
|
+
try {
|
|
296
|
+
active();
|
|
297
|
+
return await fn(input);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
return ContractFailure('db', {}, wireError(error));
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const collection = (/** @type {any} */ input) => store().collection(input.collection);
|
|
305
|
+
|
|
306
|
+
/** The operations, before either transport's policy is applied. */
|
|
307
|
+
const operations = {
|
|
308
|
+
'data.init': () => host.init(),
|
|
309
|
+
'data.open': (/** @type {any} */ input) => open(input),
|
|
310
|
+
'data.insert': (/** @type {any} */ input) => collection(input).insert(input.doc),
|
|
311
|
+
'data.delete': (/** @type {any} */ input) => collection(input).delete(input.key),
|
|
312
|
+
'data.rows': (/** @type {any} */ input) => collection(input)
|
|
313
|
+
.execute([{ $for: { it: '$[*]' }, $return: '$it' }]),
|
|
314
|
+
'data.execute': (/** @type {any} */ input) => collection(input)
|
|
315
|
+
.execute(input.document, { externals: input.externals ?? {} }),
|
|
316
|
+
'data.explain': (/** @type {any} */ input) => collection(input)
|
|
317
|
+
.explain(input.document, { externals: input.externals ?? {} }),
|
|
318
|
+
'data.oracle': (/** @type {any} */ input) => oracle(input),
|
|
319
|
+
'data.live': (/** @type {any} */ input) => live(input),
|
|
320
|
+
'data.lives': () => ({ count: state.lives.size }),
|
|
321
|
+
'data.migrate': (/** @type {any} */ input) => migrateTo(input),
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const handlers = Object.fromEntries(
|
|
325
|
+
Object.entries(operations).map(([id, fn]) => [id, guard(fn)]));
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
handlers,
|
|
329
|
+
// Refuse new calls immediately; an in-flight open closes its own late handle.
|
|
330
|
+
dispose: () => { disposed = true; return closeStore(); },
|
|
331
|
+
// The channel a CLIENT tab reaches the owner on serves the same store
|
|
332
|
+
// through the same table. A client open only attaches: it reads the
|
|
333
|
+
// current model without closing the owner's connection or live feeds.
|
|
334
|
+
clientHandlers: {
|
|
335
|
+
...handlers,
|
|
336
|
+
'data.open': guard((/** @type {any} */ input) => {
|
|
337
|
+
if (input.reset === true) {
|
|
338
|
+
throw ownerOnly('recreating the store unlinks the database the owning tab holds:'
|
|
339
|
+
+ ' run it in that tab, not from a client');
|
|
340
|
+
}
|
|
341
|
+
return storeInfo();
|
|
342
|
+
}),
|
|
343
|
+
'data.migrate': guard(() => {
|
|
344
|
+
throw ownerOnly('only the owning tab can migrate the store; run the migration in that tab');
|
|
345
|
+
}),
|
|
346
|
+
},
|
|
347
|
+
state,
|
|
348
|
+
};
|
|
349
|
+
}
|
package/src/data/host.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
export { createDataHandlers, wireError } from './handlers.js';
|
|
3
|
+
export { dataContract, dataContractDocument } from './contract.js';
|
|
4
|
+
export { BOOT_STAGES, BOOT_ERROR_CODE, DEFAULT_BOOT_BUDGETS, DataBootError, bootFailure, resolveBootBudgets, createStageRunner } from './boot-stages.js';
|
|
5
|
+
export { selectBrowserStorage, discoverStorageOwner } from './storage.js';
|
|
6
|
+
/** @typedef {import('./handlers.js').DataHost} DataHost */
|
|
7
|
+
export { createBrowserDataWorker } from './browser-worker.js';
|
|
8
|
+
export { createProjectDataWorker } from './project-worker.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/** A project model's private memory store over the same public Data contract. */
|
|
3
|
+
import { wasmDriver, sqlite3Handle } from '@jarenjs/db/wasm';
|
|
4
|
+
import { servePort } from '@jarenjs/contract/port';
|
|
5
|
+
import { createDataHandlers } from './handlers.js';
|
|
6
|
+
import { dataContract } from './contract.js';
|
|
7
|
+
/** @param {{ initialize: () => Promise<any>, scope: any, operators?: any }} env */
|
|
8
|
+
export function createProjectDataWorker(env) {
|
|
9
|
+
let sqlite3;
|
|
10
|
+
let disposed = false;
|
|
11
|
+
const ready = env.initialize();
|
|
12
|
+
const table = createDataHandlers({
|
|
13
|
+
init: async () => {
|
|
14
|
+
sqlite3 = await ready;
|
|
15
|
+
if (disposed) throw new Error('The project Data worker is disposed.');
|
|
16
|
+
return { topology: 'memory', vfs: 'memory', version: sqlite3.version.libVersion };
|
|
17
|
+
},
|
|
18
|
+
makeDriver: () => wasmDriver(sqlite3Handle(sqlite3)),
|
|
19
|
+
makeScratchDriver: () => wasmDriver(sqlite3Handle(sqlite3)),
|
|
20
|
+
path: () => ':memory:', vfs: () => 'memory', durable: () => false,
|
|
21
|
+
unlink: () => {}, announce: () => {},
|
|
22
|
+
operators: env.operators,
|
|
23
|
+
});
|
|
24
|
+
const server = servePort(dataContract, table.handlers, { channel: env.scope });
|
|
25
|
+
return { dispose() { disposed = true; server.close(); return table.dispose(); } };
|
|
26
|
+
}
|