@exponential_app/engine 0.0.0-stage → 0.1.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.
Files changed (85) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +54 -0
  3. package/README.md +35 -2
  4. package/dist/capabilities.js +29 -0
  5. package/dist/capabilities.js.map +9 -0
  6. package/dist/chunks/index-3nf9f7pv.js +315 -0
  7. package/dist/chunks/index-3nf9f7pv.js.map +10 -0
  8. package/dist/chunks/index-3rgr16n2.js +443 -0
  9. package/dist/chunks/index-3rgr16n2.js.map +9 -0
  10. package/dist/chunks/index-79swfbsm.js +366 -0
  11. package/dist/chunks/index-79swfbsm.js.map +10 -0
  12. package/dist/chunks/index-knntt4cb.js +259 -0
  13. package/dist/chunks/index-knntt4cb.js.map +10 -0
  14. package/dist/chunks/index-m2845dn6.js +72 -0
  15. package/dist/chunks/index-m2845dn6.js.map +10 -0
  16. package/dist/chunks/index-pqpnz4ns.js +2213 -0
  17. package/dist/chunks/index-pqpnz4ns.js.map +9 -0
  18. package/dist/chunks/index-q4zws02e.js +2301 -0
  19. package/dist/chunks/index-q4zws02e.js.map +10 -0
  20. package/dist/chunks/index-s3xjr701.js +170 -0
  21. package/dist/chunks/index-s3xjr701.js.map +10 -0
  22. package/dist/chunks/index-sk8b0dh4.js +126 -0
  23. package/dist/chunks/index-sk8b0dh4.js.map +10 -0
  24. package/dist/chunks/index-tvsykgcv.js +3015 -0
  25. package/dist/chunks/index-tvsykgcv.js.map +19 -0
  26. package/dist/chunks/index-v0m7r4y3.js +6869 -0
  27. package/dist/chunks/index-v0m7r4y3.js.map +13 -0
  28. package/dist/consent.js +24 -0
  29. package/dist/consent.js.map +9 -0
  30. package/dist/index.js +654 -0
  31. package/dist/index.js.map +12 -0
  32. package/dist/lifecycle.js +11 -0
  33. package/dist/lifecycle.js.map +9 -0
  34. package/dist/outbox.js +27 -0
  35. package/dist/outbox.js.map +9 -0
  36. package/dist/pdf.js +15 -0
  37. package/dist/pdf.js.map +9 -0
  38. package/dist/providers.js +26 -0
  39. package/dist/providers.js.map +9 -0
  40. package/dist/runner.js +24 -0
  41. package/dist/runner.js.map +9 -0
  42. package/dist/tools.js +38 -0
  43. package/dist/tools.js.map +9 -0
  44. package/dist/types/domain-contract/src/app-records.d.ts +3148 -0
  45. package/dist/types/domain-contract/src/app-share.d.ts +222 -0
  46. package/dist/types/domain-contract/src/apps.d.ts +612 -0
  47. package/dist/types/eengine/fixtures/providers.json +55 -0
  48. package/dist/types/eengine/src/broker.d.ts +80 -0
  49. package/dist/types/eengine/src/capabilities.d.ts +88 -0
  50. package/dist/types/eengine/src/consent.d.ts +50 -0
  51. package/dist/types/eengine/src/index.d.ts +12 -0
  52. package/dist/types/eengine/src/instance.d.ts +131 -0
  53. package/dist/types/eengine/src/lifecycle.d.ts +64 -0
  54. package/dist/types/eengine/src/outbox.d.ts +3 -0
  55. package/dist/types/eengine/src/pdf.d.ts +36 -0
  56. package/dist/types/eengine/src/providers.d.ts +38 -0
  57. package/dist/types/eengine/src/runner.d.ts +82 -0
  58. package/dist/types/eengine/src/stores.d.ts +125 -0
  59. package/dist/types/eengine/src/synced.d.ts +59 -0
  60. package/dist/types/eengine/src/tools.d.ts +79 -0
  61. package/dist/types/eengine/src/web/index.d.ts +20 -0
  62. package/dist/types/eengine/src/web/mcp.d.ts +27 -0
  63. package/dist/types/eengine/src/web/platform.d.ts +91 -0
  64. package/dist/types/eengine/src/web/stores.d.ts +125 -0
  65. package/dist/types/emcp/fixtures/automations.json +521 -0
  66. package/dist/types/emcp/fixtures/hub.json +498 -0
  67. package/dist/types/emcp/src/access.d.ts +55 -0
  68. package/dist/types/emcp/src/audit.d.ts +22 -0
  69. package/dist/types/emcp/src/automations.d.ts +111 -0
  70. package/dist/types/emcp/src/exposes.d.ts +242 -0
  71. package/dist/types/emcp/src/hub.d.ts +63 -0
  72. package/dist/types/emcp/src/index.d.ts +7 -0
  73. package/dist/types/emcp/src/pure.d.ts +11 -0
  74. package/dist/types/emcp/src/upstream.d.ts +288 -0
  75. package/dist/types/emcp/src/vault.d.ts +34 -0
  76. package/dist/web/index.js +791 -0
  77. package/dist/web/index.js.map +13 -0
  78. package/fixtures/bundle.json +1165 -0
  79. package/fixtures/capabilities.json +434 -0
  80. package/fixtures/consent.json +146 -0
  81. package/fixtures/lifecycle.json +125 -0
  82. package/fixtures/outbox.json +22 -0
  83. package/fixtures/providers.json +55 -0
  84. package/fixtures/runner.json +2167 -0
  85. package/package.json +92 -4
@@ -0,0 +1,791 @@
1
+ import {
2
+ LocalRecordStore2
3
+ } from "../chunks/index-s3xjr701.js";
4
+
5
+ // packages/eengine/src/web/mcp.ts
6
+ var ok = (structuredContent) => ({
7
+ content: [{ type: `text`, text: JSON.stringify(structuredContent) }],
8
+ structuredContent
9
+ });
10
+ var fail = (reason) => ({
11
+ content: [{ type: `text`, text: reason }],
12
+ isError: true
13
+ });
14
+ var notFound = () => fail(`notFound`);
15
+ var unavailable = () => fail(`unavailable`);
16
+ var failed = (message) => fail(`failed: ${message}`);
17
+ var errorMessage = (error) => {
18
+ const text = error instanceof Error ? error.message || error.name : String(error);
19
+ const line = text.replace(/\s+/g, ` `).trim();
20
+ return line.length > 200 ? `${line.slice(0, 199)}…` : line || `error`;
21
+ };
22
+ var isAbort = (error) => typeof error === `object` && error !== null && error.name === `AbortError`;
23
+ var ALPHABET = `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`;
24
+ var DECODE = new Int16Array(128).fill(-1);
25
+ for (let i = 0;i < ALPHABET.length; i++)
26
+ DECODE[ALPHABET.charCodeAt(i)] = i;
27
+ var encodeBase64 = (bytes) => {
28
+ let out = ``;
29
+ let i = 0;
30
+ for (;i + 2 < bytes.length; i += 3) {
31
+ const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
32
+ out += ALPHABET[n >> 18] + ALPHABET[n >> 12 & 63] + ALPHABET[n >> 6 & 63] + ALPHABET[n & 63];
33
+ }
34
+ const rest = bytes.length - i;
35
+ if (rest === 1) {
36
+ const n = bytes[i] << 16;
37
+ out += `${ALPHABET[n >> 18]}${ALPHABET[n >> 12 & 63]}==`;
38
+ } else if (rest === 2) {
39
+ const n = bytes[i] << 16 | bytes[i + 1] << 8;
40
+ out += `${ALPHABET[n >> 18]}${ALPHABET[n >> 12 & 63]}${ALPHABET[n >> 6 & 63]}=`;
41
+ }
42
+ return out;
43
+ };
44
+ var decodeBase64 = (text) => {
45
+ if (text.length % 4 !== 0)
46
+ return null;
47
+ const pad = text.endsWith(`==`) ? 2 : text.endsWith(`=`) ? 1 : 0;
48
+ const out = new Uint8Array(text.length / 4 * 3 - pad);
49
+ let o = 0;
50
+ for (let i = 0;i < text.length; i += 4) {
51
+ const last = i + 4 === text.length;
52
+ const v = [0, 0, 0, 0];
53
+ for (let j = 0;j < 4; j++) {
54
+ const c = text.charCodeAt(i + j);
55
+ if (c === 61 && last && j >= 4 - pad)
56
+ continue;
57
+ const d = c < 128 ? DECODE[c] : -1;
58
+ if (d < 0)
59
+ return null;
60
+ v[j] = d;
61
+ }
62
+ const n = v[0] << 18 | v[1] << 12 | v[2] << 6 | v[3];
63
+ out[o++] = n >> 16;
64
+ if (o < out.length)
65
+ out[o++] = n >> 8 & 255;
66
+ if (o < out.length)
67
+ out[o++] = n & 255;
68
+ if (last && pad === 2 && (v[1] & 15) !== 0)
69
+ return null;
70
+ if (last && pad === 1 && (v[2] & 3) !== 0)
71
+ return null;
72
+ }
73
+ return out;
74
+ };
75
+
76
+ // packages/eengine/src/web/platform.ts
77
+ var BLOB_URL_TTL_MS = 60000;
78
+ var browserPlatform = () => {
79
+ const w = globalThis;
80
+ const nav = w.navigator;
81
+ const platform = {
82
+ pickWithInput: ({ accept, multiple }) => new Promise((resolve, reject) => {
83
+ const activation = nav?.userActivation;
84
+ if (activation && !activation.isActive) {
85
+ reject(new Error(`the picker needs a user gesture`));
86
+ return;
87
+ }
88
+ const input = w.document.createElement(`input`);
89
+ input.type = `file`;
90
+ input.multiple = multiple;
91
+ if (accept)
92
+ input.accept = accept;
93
+ input.style.display = `none`;
94
+ const done = (files) => {
95
+ input.remove();
96
+ resolve(files);
97
+ };
98
+ input.addEventListener(`change`, () => done(Array.from(input.files ?? [])), { once: true });
99
+ input.addEventListener(`cancel`, () => done([]), { once: true });
100
+ w.document.body.append(input);
101
+ input.click();
102
+ }),
103
+ download: (name, blob) => {
104
+ const url = URL.createObjectURL(blob);
105
+ const a = w.document.createElement(`a`);
106
+ a.href = url;
107
+ a.download = name;
108
+ a.style.display = `none`;
109
+ w.document.body.append(a);
110
+ a.click();
111
+ a.remove();
112
+ w.setTimeout(() => URL.revokeObjectURL(url), BLOB_URL_TTL_MS);
113
+ },
114
+ print: (blob) => new Promise((resolve, reject) => {
115
+ const url = URL.createObjectURL(blob);
116
+ const frame = w.document.createElement(`iframe`);
117
+ frame.style.cssText = `position:fixed;width:0;height:0;border:0;opacity:0;pointer-events:none`;
118
+ let removed = false;
119
+ const remove = () => {
120
+ if (removed)
121
+ return;
122
+ removed = true;
123
+ frame.remove();
124
+ URL.revokeObjectURL(url);
125
+ };
126
+ frame.addEventListener(`load`, () => {
127
+ const win = frame.contentWindow;
128
+ if (!win) {
129
+ remove();
130
+ reject(new Error(`no print frame`));
131
+ return;
132
+ }
133
+ win.addEventListener(`afterprint`, () => w.setTimeout(remove, 0), { once: true });
134
+ w.setTimeout(remove, BLOB_URL_TTL_MS);
135
+ try {
136
+ win.focus();
137
+ win.print();
138
+ resolve();
139
+ } catch (error) {
140
+ remove();
141
+ reject(error);
142
+ }
143
+ }, { once: true });
144
+ frame.src = url;
145
+ w.document.body.append(frame);
146
+ }),
147
+ now: () => Date.now(),
148
+ setTimeout: (fn, ms) => w.setTimeout(fn, ms),
149
+ clearTimeout: (handle) => w.clearTimeout(handle),
150
+ focus: () => w.focus()
151
+ };
152
+ if (typeof w.showOpenFilePicker === `function`) {
153
+ const pick = w.showOpenFilePicker.bind(w);
154
+ platform.showOpenFilePicker = (options) => pick(options);
155
+ }
156
+ if (typeof w.showSaveFilePicker === `function`) {
157
+ const pick = w.showSaveFilePicker.bind(w);
158
+ platform.showSaveFilePicker = (options) => pick(options);
159
+ }
160
+ if (nav && typeof nav.share === `function`) {
161
+ platform.share = (data) => nav.share(data);
162
+ platform.canShare = (data) => typeof nav.canShare === `function` ? nav.canShare(data) : false;
163
+ }
164
+ if (nav?.storage && typeof nav.storage.getDirectory === `function`) {
165
+ platform.storageDirectory = () => nav.storage.getDirectory();
166
+ }
167
+ const N = w.Notification;
168
+ if (typeof N === `function`) {
169
+ platform.notifications = {
170
+ permission: () => N.permission,
171
+ requestPermission: () => N.requestPermission(),
172
+ show: (title, options) => new N(title, options)
173
+ };
174
+ }
175
+ return platform;
176
+ };
177
+ // packages/eengine/src/web/stores.ts
178
+ var SQLITE_SCHEMA = [
179
+ `CREATE TABLE IF NOT EXISTS app_records (app_id TEXT NOT NULL, id TEXT NOT NULL, kind TEXT NOT NULL, data TEXT NOT NULL, created_by TEXT, author_id TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (app_id, id))`,
180
+ `CREATE INDEX IF NOT EXISTS app_records_kind ON app_records (app_id, kind)`,
181
+ `CREATE TABLE IF NOT EXISTS app_record_events (seq INTEGER PRIMARY KEY AUTOINCREMENT, app_id TEXT NOT NULL, record_id TEXT NOT NULL, op TEXT NOT NULL, actor TEXT, write_id TEXT NOT NULL, patch TEXT NOT NULL, before TEXT NOT NULL, at TEXT NOT NULL)`,
182
+ `CREATE INDEX IF NOT EXISTS app_record_events_record ON app_record_events (app_id, record_id)`,
183
+ `CREATE TABLE IF NOT EXISTS app_record_writes (app_id TEXT NOT NULL, write_id TEXT NOT NULL, ids TEXT NOT NULL, PRIMARY KEY (app_id, write_id))`
184
+ ];
185
+ function answerSql(db, request) {
186
+ try {
187
+ if (request.op === `query`)
188
+ return { id: request.id, rows: db.query(request.sql, request.bind) };
189
+ db.run(`BEGIN IMMEDIATE`, []);
190
+ try {
191
+ for (const s of request.statements)
192
+ db.run(s.sql, s.bind ?? []);
193
+ db.run(`COMMIT`, []);
194
+ } catch (error) {
195
+ db.run(`ROLLBACK`, []);
196
+ throw error;
197
+ }
198
+ return { id: request.id };
199
+ } catch (error) {
200
+ return { id: request.id, error: error instanceof Error ? error.message : String(error) };
201
+ }
202
+ }
203
+ function serveSql(db, port) {
204
+ for (const s of SQLITE_SCHEMA)
205
+ db.run(s, []);
206
+ port.addEventListener(`message`, (event) => port.postMessage(answerSql(db, event.data)));
207
+ }
208
+ function sqlClient(port) {
209
+ let next = 0;
210
+ const pending = new Map;
211
+ port.addEventListener(`message`, (event) => {
212
+ const r = event.data;
213
+ pending.get(r.id)?.resolve(r);
214
+ pending.delete(r.id);
215
+ });
216
+ const send = (request) => new Promise((resolve, reject) => {
217
+ pending.set(request.id, {
218
+ resolve: (r) => r.error === undefined ? resolve(r) : reject(new Error(r.error))
219
+ });
220
+ port.postMessage(request);
221
+ });
222
+ return {
223
+ query: async (sql, bind = []) => (await send({ id: ++next, op: `query`, sql, bind })).rows ?? [],
224
+ transaction: async (statements) => void await send({ id: ++next, op: `transaction`, statements })
225
+ };
226
+ }
227
+ var json = (v) => JSON.stringify(v ?? null);
228
+ var parsed = (v) => {
229
+ try {
230
+ return typeof v === `string` ? JSON.parse(v) : null;
231
+ } catch {
232
+ return null;
233
+ }
234
+ };
235
+ var text = (v) => typeof v === `string` ? v : null;
236
+
237
+ class SqlitePersistence {
238
+ db;
239
+ constructor(db) {
240
+ this.db = db;
241
+ }
242
+ async load(app) {
243
+ const rows = await this.db.query(`SELECT id, kind, data, created_by, author_id, created_at, updated_at FROM app_records WHERE app_id = ? ORDER BY created_at, id`, [app]);
244
+ return rows.map((r) => ({
245
+ id: String(r.id),
246
+ kind: String(r.kind),
247
+ data: parsed(r.data) ?? {},
248
+ createdBy: text(r.created_by),
249
+ authorId: text(r.author_id),
250
+ createdAt: String(r.created_at),
251
+ updatedAt: String(r.updated_at)
252
+ }));
253
+ }
254
+ async ack(app, writeId) {
255
+ const [row] = await this.db.query(`SELECT ids FROM app_record_writes WHERE app_id = ? AND write_id = ?`, [app, writeId]);
256
+ return row ? parsed(row.ids) ?? [] : null;
257
+ }
258
+ async commit(app, c) {
259
+ const statements = [];
260
+ for (const id of c.deletes)
261
+ statements.push({ sql: `DELETE FROM app_records WHERE app_id = ? AND id = ?`, bind: [app, id] });
262
+ for (const r of c.upserts)
263
+ statements.push({
264
+ sql: `INSERT INTO app_records (app_id, id, kind, data, created_by, author_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (app_id, id) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,
265
+ bind: [app, r.id, r.kind, json(r.data), r.createdBy, r.authorId ?? null, r.createdAt, r.updatedAt]
266
+ });
267
+ for (const e of c.events)
268
+ statements.push({
269
+ sql: `INSERT INTO app_record_events (app_id, record_id, op, actor, write_id, patch, before, at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
270
+ bind: [app, e.recordId, e.op, e.actor, e.writeId, json(e.patch), json(e.before), e.at]
271
+ });
272
+ statements.push({ sql: `INSERT INTO app_record_writes (app_id, write_id, ids) VALUES (?, ?, ?)`, bind: [app, c.writeId, json(c.ids)] });
273
+ await this.db.transaction(statements);
274
+ }
275
+ async history(app, id) {
276
+ const rows = await this.db.query(`SELECT record_id, op, actor, write_id, patch, before, at FROM app_record_events WHERE app_id = ? AND record_id = ? ORDER BY seq`, [app, id]);
277
+ return rows.map((r) => ({ recordId: String(r.record_id), op: r.op, actor: text(r.actor), writeId: String(r.write_id), patch: parsed(r.patch), before: parsed(r.before), at: String(r.at) }));
278
+ }
279
+ }
280
+ var RECORDS = `records`;
281
+ var WRITES = `writes`;
282
+ var EVENTS = `events`;
283
+ var VAULT = `vault`;
284
+ var done = (request) => new Promise((resolve, reject) => {
285
+ request.onsuccess = () => resolve(request.result);
286
+ request.onerror = () => reject(request.error);
287
+ });
288
+ var committed = (tx) => new Promise((resolve, reject) => {
289
+ tx.oncomplete = () => resolve();
290
+ tx.onerror = () => reject(tx.error);
291
+ tx.onabort = () => reject(tx.error ?? new Error(`aborted`));
292
+ });
293
+ function openRunnerIdb(factory, name) {
294
+ const request = factory.open(name, 1);
295
+ request.onupgradeneeded = () => {
296
+ const db = request.result;
297
+ db.createObjectStore(RECORDS, { keyPath: [`appId`, `id`] }).createIndex(`app`, `appId`);
298
+ db.createObjectStore(WRITES, { keyPath: [`appId`, `writeId`] });
299
+ db.createObjectStore(EVENTS, { autoIncrement: true }).createIndex(`record`, [`appId`, `recordId`]);
300
+ db.createObjectStore(VAULT);
301
+ };
302
+ return done(request);
303
+ }
304
+
305
+ class IndexedDbPersistence {
306
+ db;
307
+ constructor(db) {
308
+ this.db = db;
309
+ }
310
+ async load(app) {
311
+ const all = await done(this.db.transaction(RECORDS).objectStore(RECORDS).index(`app`).getAll(app));
312
+ return all.map(({ appId: _, ...row }) => row).sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : a.id < b.id ? -1 : 1);
313
+ }
314
+ async ack(app, writeId) {
315
+ const row = await done(this.db.transaction(WRITES).objectStore(WRITES).get([app, writeId]));
316
+ return row ? row.ids : null;
317
+ }
318
+ async commit(app, c) {
319
+ const tx = this.db.transaction([RECORDS, WRITES, EVENTS], `readwrite`);
320
+ const records = tx.objectStore(RECORDS);
321
+ for (const id of c.deletes)
322
+ records.delete([app, id]);
323
+ for (const r of c.upserts)
324
+ records.put({ ...r, appId: app });
325
+ for (const e of c.events)
326
+ tx.objectStore(EVENTS).add({ ...e, appId: app });
327
+ tx.objectStore(WRITES).put({ appId: app, writeId: c.writeId, ids: c.ids });
328
+ await committed(tx);
329
+ }
330
+ async history(app, id) {
331
+ const all = await done(this.db.transaction(EVENTS).objectStore(EVENTS).index(`record`).getAll([app, id]));
332
+ return all.map(({ appId: _, ...e }) => e);
333
+ }
334
+ }
335
+
336
+ class IndexedDbVault {
337
+ db;
338
+ constructor(db) {
339
+ this.db = db;
340
+ }
341
+ async read(key) {
342
+ const v = await done(this.db.transaction(VAULT).objectStore(VAULT).get(key));
343
+ return typeof v === `string` ? v : null;
344
+ }
345
+ async write(key, value) {
346
+ const tx = this.db.transaction(VAULT, `readwrite`);
347
+ tx.objectStore(VAULT).put(value, key);
348
+ await committed(tx);
349
+ }
350
+ async delete(key) {
351
+ const tx = this.db.transaction(VAULT, `readwrite`);
352
+ tx.objectStore(VAULT).delete(key);
353
+ await committed(tx);
354
+ }
355
+ }
356
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
357
+ var notFound2 = (e) => e instanceof Error && (e.name === `NotFoundError` || e.name === `TypeMismatchError`);
358
+
359
+ class OpfsBlobStore {
360
+ root;
361
+ constructor(root) {
362
+ this.root = root;
363
+ }
364
+ async dir(app, create) {
365
+ if (!app || /[/\\]/.test(app) || app.startsWith(`.`))
366
+ throw new Error(`not an app id`);
367
+ return (await this.root()).getDirectoryHandle(app, { create });
368
+ }
369
+ async writeFile(dir, name, data) {
370
+ const writable = await (await dir.getFileHandle(name, { create: true })).createWritable();
371
+ await writable.write(data);
372
+ await writable.close();
373
+ }
374
+ async put(app, name, mimeType, bytes) {
375
+ const dir = await this.dir(app, true);
376
+ const id = crypto.randomUUID();
377
+ await this.writeFile(dir, id, bytes);
378
+ await this.writeFile(dir, `${id}.json`, JSON.stringify({ name, mime: mimeType }));
379
+ return id;
380
+ }
381
+ async get(app, id) {
382
+ if (!UUID.test(id))
383
+ return null;
384
+ try {
385
+ const file = await (await (await this.dir(app, false)).getFileHandle(id)).getFile();
386
+ return new Uint8Array(await file.arrayBuffer());
387
+ } catch (e) {
388
+ if (notFound2(e))
389
+ return null;
390
+ throw e;
391
+ }
392
+ }
393
+ async delete(app, id) {
394
+ if (!UUID.test(id))
395
+ return;
396
+ let dir;
397
+ try {
398
+ dir = await this.dir(app, false);
399
+ } catch (e) {
400
+ if (notFound2(e))
401
+ return;
402
+ throw e;
403
+ }
404
+ for (const name of [id, `${id}.json`])
405
+ await dir.removeEntry(name).catch((e) => notFound2(e) ? undefined : Promise.reject(e));
406
+ }
407
+ }
408
+ async function openWebStorage(options) {
409
+ const idb = await openRunnerIdb(options.indexedDB, options.idbName);
410
+ const vault = new IndexedDbVault(idb);
411
+ const blobs = new OpfsBlobStore(options.files);
412
+ if (options.openSqlite) {
413
+ try {
414
+ const db = await options.openSqlite();
415
+ return { records: new LocalRecordStore2(new SqlitePersistence(db)), backend: `sqlite`, vault, blobs };
416
+ } catch {}
417
+ }
418
+ return { records: new LocalRecordStore2(new IndexedDbPersistence(idb)), backend: `indexeddb`, vault, blobs };
419
+ }
420
+
421
+ // packages/eengine/src/web/index.ts
422
+ var PHOTOS_DIR = `photos`;
423
+ var MIME_BY_EXTENSION = {
424
+ avif: `image/avif`,
425
+ bmp: `image/bmp`,
426
+ csv: `text/csv`,
427
+ gif: `image/gif`,
428
+ heic: `image/heic`,
429
+ heif: `image/heif`,
430
+ html: `text/html`,
431
+ jpeg: `image/jpeg`,
432
+ jpg: `image/jpeg`,
433
+ json: `application/json`,
434
+ md: `text/markdown`,
435
+ pdf: `application/pdf`,
436
+ png: `image/png`,
437
+ svg: `image/svg+xml`,
438
+ tif: `image/tiff`,
439
+ tiff: `image/tiff`,
440
+ txt: `text/plain`,
441
+ webp: `image/webp`,
442
+ zip: `application/zip`
443
+ };
444
+ var OCTET = `application/octet-stream`;
445
+ var MAX_DELAY_MS = 2147483647;
446
+ var extensionMime = (name) => {
447
+ const dot = name.lastIndexOf(`.`);
448
+ return dot < 0 ? undefined : MIME_BY_EXTENSION[name.slice(dot + 1).toLowerCase()];
449
+ };
450
+ var codePoints = (text, max) => {
451
+ const chars = Array.from(text);
452
+ return chars.length > max ? chars.slice(0, max).join(``) : text;
453
+ };
454
+ var fileName = (name) => codePoints(name || `file`, 255);
455
+ var fileMime = (file) => {
456
+ const type = file.type && file.type.length <= 100 ? file.type : undefined;
457
+ return type ?? extensionMime(file.name) ?? OCTET;
458
+ };
459
+ var str = (args, key) => typeof args[key] === `string` ? args[key] : undefined;
460
+ var bytesOf = async (file) => new Uint8Array(await file.arrayBuffer());
461
+ var blobOf = (bytes, type) => new Blob([bytes], { type });
462
+ var pickerTypes = (accept) => {
463
+ const map = {};
464
+ for (const raw of accept) {
465
+ const entry = raw.trim();
466
+ if (entry.includes(`/`)) {
467
+ map[entry] ??= [];
468
+ } else {
469
+ const ext = entry.startsWith(`.`) ? entry : `.${entry}`;
470
+ const mime = extensionMime(ext) ?? OCTET;
471
+ (map[mime] ??= []).push(ext);
472
+ }
473
+ }
474
+ return [{ description: `Files`, accept: map }];
475
+ };
476
+ var pickFiles = async (platform2, accept, multiple) => {
477
+ if (platform2.showOpenFilePicker) {
478
+ const options = { multiple };
479
+ if (accept.length > 0) {
480
+ options.types = pickerTypes(accept);
481
+ options.excludeAcceptAllOption = true;
482
+ }
483
+ try {
484
+ const handles = await platform2.showOpenFilePicker(options);
485
+ return await Promise.all(handles.map((h) => h.getFile()));
486
+ } catch (error) {
487
+ if (isAbort(error))
488
+ return [];
489
+ if (!(error instanceof TypeError))
490
+ throw error;
491
+ }
492
+ }
493
+ return platform2.pickWithInput({ accept: accept.join(`,`), multiple });
494
+ };
495
+ var guard = (tools) => async (tool, args) => {
496
+ const run = Object.hasOwn(tools, tool) ? tools[tool] : undefined;
497
+ if (!run)
498
+ return unavailable();
499
+ try {
500
+ return await run(args ?? {});
501
+ } catch (error) {
502
+ return failed(errorMessage(error));
503
+ }
504
+ };
505
+ var filesCapability = (platform2) => ({
506
+ name: `files`,
507
+ call: guard({
508
+ open: async (args) => {
509
+ const accept = Array.isArray(args.accept) ? args.accept : [];
510
+ const files = await pickFiles(platform2, accept, args.multiple === true);
511
+ const picked = (args.multiple === true ? files : files.slice(0, 1)).slice(0, 100);
512
+ return ok({
513
+ files: await Promise.all(picked.map(async (f) => ({ name: fileName(f.name), mimeType: fileMime(f), data: encodeBase64(await bytesOf(f)) })))
514
+ });
515
+ },
516
+ save: async (args) => {
517
+ const name = str(args, `name`) ?? `file`;
518
+ const bytes = decodeBase64(str(args, `data`) ?? ``);
519
+ if (!bytes)
520
+ return failed(`bad base64`);
521
+ const blob = blobOf(bytes, str(args, `mimeType`) ?? OCTET);
522
+ if (platform2.showSaveFilePicker) {
523
+ let handle;
524
+ try {
525
+ handle = await platform2.showSaveFilePicker({ suggestedName: name });
526
+ } catch (error) {
527
+ if (isAbort(error))
528
+ return ok({ saved: false });
529
+ if (!(error instanceof TypeError))
530
+ throw error;
531
+ }
532
+ if (handle) {
533
+ const writable = await handle.createWritable();
534
+ await writable.write(blob);
535
+ await writable.close();
536
+ return ok({ saved: true });
537
+ }
538
+ }
539
+ platform2.download(name, blob);
540
+ return ok({ saved: true });
541
+ }
542
+ })
543
+ });
544
+ var parsePhotoId = (id) => {
545
+ const m = /^(\d{1,15})-(.+)$/.exec(id);
546
+ if (!m)
547
+ return null;
548
+ return { id, name: m[2], takenMs: Number(m[1]) };
549
+ };
550
+ var takenAt = (ms) => ms > 0 && ms <= 8640000000000000 ? new Date(ms).toISOString() : null;
551
+ var photoMeta = (entry, mime) => ({
552
+ id: entry.id,
553
+ name: fileName(entry.name),
554
+ mimeType: mime ?? extensionMime(entry.name) ?? OCTET,
555
+ takenAt: takenAt(entry.takenMs)
556
+ });
557
+ var safeName = (name) => {
558
+ const cleaned = codePoints(name.replace(/[/\\\u0000]/g, `_`), 200);
559
+ return cleaned === `` || cleaned === `.` || cleaned === `..` ? `photo` : cleaned;
560
+ };
561
+ var photosCapability = (platform2) => {
562
+ const library = async () => {
563
+ if (!platform2.storageDirectory)
564
+ return null;
565
+ const root = await platform2.storageDirectory();
566
+ return root.getDirectoryHandle(PHOTOS_DIR, { create: true });
567
+ };
568
+ const entries = async (dir) => {
569
+ const out = [];
570
+ for await (const handle of dir.values()) {
571
+ if (handle.kind !== `file`)
572
+ continue;
573
+ const entry = parsePhotoId(handle.name);
574
+ if (entry)
575
+ out.push(entry);
576
+ }
577
+ return out.sort((a, b) => b.takenMs - a.takenMs || (a.id < b.id ? 1 : a.id > b.id ? -1 : 0));
578
+ };
579
+ return {
580
+ name: `photos`,
581
+ call: guard({
582
+ pick: async (args) => {
583
+ const limit = typeof args.limit === `number` ? Math.max(1, Math.min(50, Math.floor(args.limit))) : 1;
584
+ const files = (await pickFiles(platform2, [`image/*`], limit > 1)).slice(0, limit);
585
+ const dir = files.length > 0 ? await library().catch(() => null) : null;
586
+ const photos = [];
587
+ for (const file of files) {
588
+ const bytes = await bytesOf(file);
589
+ const ms = Number.isSafeInteger(file.lastModified) && file.lastModified > 0 ? file.lastModified : 0;
590
+ const id = `${ms}-${safeName(file.name)}`;
591
+ if (dir) {
592
+ try {
593
+ const writable = await (await dir.getFileHandle(id, { create: true })).createWritable();
594
+ await writable.write(bytes);
595
+ await writable.close();
596
+ } catch {}
597
+ }
598
+ const meta = photoMeta({ id, name: safeName(file.name), takenMs: ms }, fileMime(file));
599
+ photos.push({ ...meta, data: encodeBase64(bytes) });
600
+ }
601
+ return ok({ photos });
602
+ },
603
+ list: async (args) => {
604
+ const limit = typeof args.limit === `number` ? Math.max(1, Math.min(100, Math.floor(args.limit))) : 50;
605
+ const cursor = str(args, `cursor`);
606
+ if (cursor !== undefined && !/^\d{1,9}$/.test(cursor))
607
+ return failed(`bad cursor`);
608
+ const dir = await library().catch(() => null);
609
+ if (!dir)
610
+ return ok({ photos: [], cursor: null });
611
+ const all = await entries(dir);
612
+ const offset = cursor === undefined ? 0 : Number(cursor);
613
+ const page = all.slice(offset, offset + limit);
614
+ const next = offset + limit < all.length ? String(offset + limit) : null;
615
+ return ok({ photos: page.map((e) => photoMeta(e)), cursor: next });
616
+ },
617
+ get: async (args) => {
618
+ const entry = parsePhotoId(str(args, `id`) ?? ``);
619
+ if (!entry)
620
+ return notFound();
621
+ const dir = await library().catch(() => null);
622
+ if (!dir)
623
+ return notFound();
624
+ let file;
625
+ try {
626
+ file = await (await dir.getFileHandle(entry.id)).getFile();
627
+ } catch {
628
+ return notFound();
629
+ }
630
+ return ok({ photo: { ...photoMeta(entry), data: encodeBase64(await bytesOf(file)) } });
631
+ }
632
+ })
633
+ };
634
+ };
635
+ var shareCapability = (platform2) => ({
636
+ name: `share`,
637
+ call: guard({
638
+ send: async (args) => {
639
+ if (!platform2.share)
640
+ return unavailable();
641
+ const data = {};
642
+ for (const key of [`title`, `text`, `url`]) {
643
+ const value = str(args, key);
644
+ if (value !== undefined)
645
+ data[key] = value;
646
+ }
647
+ const file = args.file;
648
+ if (file && typeof file === `object`) {
649
+ const bytes = decodeBase64(file.data ?? ``);
650
+ if (!bytes)
651
+ return failed(`bad base64`);
652
+ const shared = new File([bytes], file.name ?? `file`, { type: file.mimeType ?? OCTET });
653
+ if (platform2.canShare?.({ files: [shared] }))
654
+ data.files = [shared];
655
+ else if (data.text === undefined && data.url === undefined)
656
+ return failed(`this browser cannot share files`);
657
+ }
658
+ try {
659
+ await platform2.share(data);
660
+ } catch (error) {
661
+ if (isAbort(error))
662
+ return ok({ shared: false });
663
+ throw error;
664
+ }
665
+ return ok({ shared: true });
666
+ }
667
+ })
668
+ });
669
+ var printCapability = (platform2) => ({
670
+ name: `print`,
671
+ call: guard({
672
+ print: async (args) => {
673
+ const bytes = decodeBase64(str(args, `data`) ?? ``);
674
+ if (!bytes)
675
+ return failed(`bad base64`);
676
+ await platform2.print(blobOf(bytes, str(args, `mimeType`) ?? `application/pdf`));
677
+ return ok({ printed: true });
678
+ }
679
+ })
680
+ });
681
+ var notificationsCapability = (platform2, opts) => {
682
+ const api = platform2.notifications;
683
+ const posted = new Map;
684
+ const withdraw = (id) => {
685
+ const entry = posted.get(id);
686
+ if (!entry)
687
+ return false;
688
+ posted.delete(id);
689
+ if (entry.timer !== undefined)
690
+ platform2.clearTimeout(entry.timer);
691
+ if (entry.shown) {
692
+ entry.shown.onclose = null;
693
+ entry.shown.close();
694
+ }
695
+ return true;
696
+ };
697
+ const show = (id, entry, title, body, tap) => {
698
+ entry.timer = undefined;
699
+ const n = api.show(title, body === null ? { tag: id } : { body, tag: id });
700
+ entry.shown = n;
701
+ n.onclick = () => {
702
+ platform2.focus();
703
+ if (tap !== null && tap !== undefined)
704
+ opts.onTap?.(tap);
705
+ withdraw(id);
706
+ };
707
+ n.onclose = () => {
708
+ if (posted.get(id) === entry)
709
+ posted.delete(id);
710
+ };
711
+ };
712
+ const schedule = (id, entry, atMs, fire) => {
713
+ const delay = atMs - platform2.now();
714
+ if (delay <= 0)
715
+ return fire();
716
+ entry.timer = platform2.setTimeout(() => {
717
+ if (posted.get(id) === entry)
718
+ schedule(id, entry, atMs, fire);
719
+ }, Math.min(delay, MAX_DELAY_MS));
720
+ };
721
+ return {
722
+ name: `notifications`,
723
+ call: guard({
724
+ post: async (args) => {
725
+ const id = str(args, `id`) ?? ``;
726
+ const title = str(args, `title`) ?? ``;
727
+ const body = str(args, `body`) ?? null;
728
+ const at = str(args, `at`);
729
+ const atMs = at === undefined ? Number.NaN : Date.parse(at);
730
+ if (at !== undefined && Number.isNaN(atMs))
731
+ return failed(`bad at`);
732
+ let permission = api.permission();
733
+ if (permission === `default`)
734
+ permission = await api.requestPermission();
735
+ if (permission !== `granted`)
736
+ return failed(`notifications blocked`);
737
+ withdraw(id);
738
+ const entry = {};
739
+ posted.set(id, entry);
740
+ const tap = args.tap ?? null;
741
+ const fire = () => {
742
+ if (posted.get(id) === entry)
743
+ show(id, entry, title, body, tap);
744
+ };
745
+ if (Number.isNaN(atMs))
746
+ fire();
747
+ else
748
+ schedule(id, entry, atMs, fire);
749
+ return ok({});
750
+ },
751
+ cancel: async (args) => ok({ cancelled: withdraw(str(args, `id`) ?? ``) })
752
+ })
753
+ };
754
+ };
755
+ var webCapabilities = (platform2 = browserPlatform(), opts = {}) => {
756
+ const out = [photosCapability(platform2), filesCapability(platform2)];
757
+ if (platform2.share)
758
+ out.push(shareCapability(platform2));
759
+ out.push(printCapability(platform2));
760
+ if (platform2.notifications)
761
+ out.push(notificationsCapability(platform2, opts));
762
+ return out;
763
+ };
764
+ export {
765
+ IndexedDbPersistence,
766
+ IndexedDbVault,
767
+ OpfsBlobStore,
768
+ PHOTOS_DIR,
769
+ SQLITE_SCHEMA,
770
+ SqlitePersistence,
771
+ answerSql,
772
+ browserPlatform,
773
+ decodeBase64,
774
+ encodeBase64,
775
+ errorMessage,
776
+ fail,
777
+ failed,
778
+ isAbort,
779
+ notFound,
780
+ ok,
781
+ openRunnerIdb,
782
+ openWebStorage,
783
+ pickerTypes,
784
+ serveSql,
785
+ sqlClient,
786
+ unavailable,
787
+ webCapabilities
788
+ };
789
+
790
+ //# debugId=597992B2AF6EE6B164756E2164756E21
791
+ //# sourceMappingURL=index.js.map