@xmtp/wasm-bindings 0.0.2 → 0.0.4

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.
@@ -1,692 +1 @@
1
- /*
2
- 2022-09-16
3
-
4
- The author disclaims copyright to this source code. In place of a
5
- legal notice, here is a blessing:
6
-
7
- * May you do good and not evil.
8
- * May you find forgiveness for yourself and forgive others.
9
- * May you share freely, never taking more than you give.
10
-
11
- ***********************************************************************
12
-
13
- A Worker which manages asynchronous OPFS handles on behalf of a
14
- synchronous API which controls it via a combination of Worker
15
- messages, SharedArrayBuffer, and Atomics. It is the asynchronous
16
- counterpart of the API defined in sqlite3-vfs-opfs.js.
17
-
18
- Highly indebted to:
19
-
20
- https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/OriginPrivateFileSystemVFS.js
21
-
22
- for demonstrating how to use the OPFS APIs.
23
-
24
- This file is to be loaded as a Worker. It does not have any direct
25
- access to the sqlite3 JS/WASM bits, so any bits which it needs (most
26
- notably SQLITE_xxx integer codes) have to be imported into it via an
27
- initialization process.
28
-
29
- This file represents an implementation detail of a larger piece of
30
- code, and not a public interface. Its details may change at any time
31
- and are not intended to be used by any client-level code.
32
-
33
- 2022-11-27: Chrome v108 changes some async methods to synchronous, as
34
- documented at:
35
-
36
- https://developer.chrome.com/blog/sync-methods-for-accesshandles/
37
-
38
- Firefox v111 and Safari 16.4, both released in March 2023, also
39
- include this.
40
-
41
- We cannot change to the sync forms at this point without breaking
42
- clients who use Chrome v104-ish or higher. truncate(), getSize(),
43
- flush(), and close() are now (as of v108) synchronous. Calling them
44
- with an "await", as we have to for the async forms, is still legal
45
- with the sync forms but is superfluous. Calling the async forms with
46
- theFunc().then(...) is not compatible with the change to
47
- synchronous, but we do do not use those APIs that way. i.e. we don't
48
- _need_ to change anything for this, but at some point (after Chrome
49
- versions (approximately) 104-107 are extinct) should change our
50
- usage of those methods to remove the "await".
51
- */
52
- const wPost = (type, ...args) => postMessage({ type, payload: args });
53
- const installAsyncProxy = function () {
54
- const toss = function (...args) {
55
- throw new Error(args.join(' '));
56
- };
57
- if (globalThis.window === globalThis) {
58
- toss(
59
- 'This code cannot run from the main thread.',
60
- 'Load it as a Worker from a separate Worker.',
61
- );
62
- } else if (!navigator?.storage?.getDirectory) {
63
- toss('This API requires navigator.storage.getDirectory.');
64
- }
65
-
66
- const state = Object.create(null);
67
-
68
- state.verbose = 1;
69
-
70
- const loggers = {
71
- 0: console.error.bind(console),
72
- 1: console.warn.bind(console),
73
- 2: console.log.bind(console),
74
- };
75
- const logImpl = (level, ...args) => {
76
- if (state.verbose > level) loggers[level]('OPFS asyncer:', ...args);
77
- };
78
- const log = (...args) => logImpl(2, ...args);
79
- const warn = (...args) => logImpl(1, ...args);
80
- const error = (...args) => logImpl(0, ...args);
81
-
82
- const __openFiles = Object.create(null);
83
-
84
- const __implicitLocks = new Set();
85
-
86
- const getResolvedPath = function (filename, splitIt) {
87
- const p = new URL(filename, 'file://irrelevant').pathname;
88
- return splitIt ? p.split('/').filter((v) => !!v) : p;
89
- };
90
-
91
- const getDirForFilename = async function f(absFilename, createDirs = false) {
92
- const path = getResolvedPath(absFilename, true);
93
- const filename = path.pop();
94
- let dh = state.rootDir;
95
- for (const dirName of path) {
96
- if (dirName) {
97
- dh = await dh.getDirectoryHandle(dirName, { create: !!createDirs });
98
- }
99
- }
100
- return [dh, filename];
101
- };
102
-
103
- const closeSyncHandle = async (fh) => {
104
- if (fh.syncHandle) {
105
- log('Closing sync handle for', fh.filenameAbs);
106
- const h = fh.syncHandle;
107
- delete fh.syncHandle;
108
- delete fh.xLock;
109
- __implicitLocks.delete(fh.fid);
110
- return h.close();
111
- }
112
- };
113
-
114
- const closeSyncHandleNoThrow = async (fh) => {
115
- try {
116
- await closeSyncHandle(fh);
117
- } catch (e) {
118
- warn('closeSyncHandleNoThrow() ignoring:', e, fh);
119
- }
120
- };
121
-
122
- const releaseImplicitLocks = async () => {
123
- if (__implicitLocks.size) {
124
- for (const fid of __implicitLocks) {
125
- const fh = __openFiles[fid];
126
- await closeSyncHandleNoThrow(fh);
127
- log('Auto-unlocked', fid, fh.filenameAbs);
128
- }
129
- }
130
- };
131
-
132
- const releaseImplicitLock = async (fh) => {
133
- if (fh.releaseImplicitLocks && __implicitLocks.has(fh.fid)) {
134
- return closeSyncHandleNoThrow(fh);
135
- }
136
- };
137
-
138
- class GetSyncHandleError extends Error {
139
- constructor(errorObject, ...msg) {
140
- super(
141
- [...msg, ': ' + errorObject.name + ':', errorObject.message].join(' '),
142
- {
143
- cause: errorObject,
144
- },
145
- );
146
- this.name = 'GetSyncHandleError';
147
- }
148
- }
149
-
150
- GetSyncHandleError.convertRc = (e, rc) => {
151
- if (e instanceof GetSyncHandleError) {
152
- if (
153
- e.cause.name === 'NoModificationAllowedError' ||
154
- (e.cause.name === 'DOMException' &&
155
- 0 === e.cause.message.indexOf('Access Handles cannot'))
156
- ) {
157
- return state.sq3Codes.SQLITE_BUSY;
158
- } else if ('NotFoundError' === e.cause.name) {
159
- return state.sq3Codes.SQLITE_CANTOPEN;
160
- }
161
- } else if ('NotFoundError' === e?.name) {
162
- return state.sq3Codes.SQLITE_CANTOPEN;
163
- }
164
- return rc;
165
- };
166
-
167
- const getSyncHandle = async (fh, opName) => {
168
- if (!fh.syncHandle) {
169
- const t = performance.now();
170
- log('Acquiring sync handle for', fh.filenameAbs);
171
- const maxTries = 6,
172
- msBase = state.asyncIdleWaitTime * 2;
173
- let i = 1,
174
- ms = msBase;
175
- for (; true; ms = msBase * ++i) {
176
- try {
177
- fh.syncHandle = await fh.fileHandle.createSyncAccessHandle();
178
- break;
179
- } catch (e) {
180
- if (i === maxTries) {
181
- throw new GetSyncHandleError(
182
- e,
183
- 'Error getting sync handle for',
184
- opName + '().',
185
- maxTries,
186
- 'attempts failed.',
187
- fh.filenameAbs,
188
- );
189
- }
190
- warn(
191
- 'Error getting sync handle for',
192
- opName + '(). Waiting',
193
- ms,
194
- 'ms and trying again.',
195
- fh.filenameAbs,
196
- e,
197
- );
198
- Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms);
199
- }
200
- }
201
- log(
202
- 'Got',
203
- opName + '() sync handle for',
204
- fh.filenameAbs,
205
- 'in',
206
- performance.now() - t,
207
- 'ms',
208
- );
209
- if (!fh.xLock) {
210
- __implicitLocks.add(fh.fid);
211
- log(
212
- 'Acquired implicit lock for',
213
- opName + '()',
214
- fh.fid,
215
- fh.filenameAbs,
216
- );
217
- }
218
- }
219
- return fh.syncHandle;
220
- };
221
-
222
- const storeAndNotify = (opName, value) => {
223
- log(opName + '() => notify(', value, ')');
224
- Atomics.store(state.sabOPView, state.opIds.rc, value);
225
- Atomics.notify(state.sabOPView, state.opIds.rc);
226
- };
227
-
228
- const affirmNotRO = function (opName, fh) {
229
- if (fh.readOnly) toss(opName + '(): File is read-only: ' + fh.filenameAbs);
230
- };
231
-
232
- let flagAsyncShutdown = false;
233
-
234
- const vfsAsyncImpls = {
235
- 'opfs-async-shutdown': async () => {
236
- flagAsyncShutdown = true;
237
- storeAndNotify('opfs-async-shutdown', 0);
238
- },
239
- mkdir: async (dirname) => {
240
- let rc = 0;
241
- try {
242
- await getDirForFilename(dirname + '/filepart', true);
243
- } catch (e) {
244
- state.s11n.storeException(2, e);
245
- rc = state.sq3Codes.SQLITE_IOERR;
246
- }
247
- storeAndNotify('mkdir', rc);
248
- },
249
- xAccess: async (filename) => {
250
- let rc = 0;
251
- try {
252
- const [dh, fn] = await getDirForFilename(filename);
253
- await dh.getFileHandle(fn);
254
- } catch (e) {
255
- state.s11n.storeException(2, e);
256
- rc = state.sq3Codes.SQLITE_IOERR;
257
- }
258
- storeAndNotify('xAccess', rc);
259
- },
260
- xClose: async function (fid) {
261
- const opName = 'xClose';
262
- __implicitLocks.delete(fid);
263
- const fh = __openFiles[fid];
264
- let rc = 0;
265
- if (fh) {
266
- delete __openFiles[fid];
267
- await closeSyncHandle(fh);
268
- if (fh.deleteOnClose) {
269
- try {
270
- await fh.dirHandle.removeEntry(fh.filenamePart);
271
- } catch (e) {
272
- warn('Ignoring dirHandle.removeEntry() failure of', fh, e);
273
- }
274
- }
275
- } else {
276
- state.s11n.serialize();
277
- rc = state.sq3Codes.SQLITE_NOTFOUND;
278
- }
279
- storeAndNotify(opName, rc);
280
- },
281
- xDelete: async function (...args) {
282
- const rc = await vfsAsyncImpls.xDeleteNoWait(...args);
283
- storeAndNotify('xDelete', rc);
284
- },
285
- xDeleteNoWait: async function (filename, syncDir = 0, recursive = false) {
286
- let rc = 0;
287
- try {
288
- while (filename) {
289
- const [hDir, filenamePart] = await getDirForFilename(filename, false);
290
- if (!filenamePart) break;
291
- await hDir.removeEntry(filenamePart, { recursive });
292
- if (0x1234 !== syncDir) break;
293
- recursive = false;
294
- filename = getResolvedPath(filename, true);
295
- filename.pop();
296
- filename = filename.join('/');
297
- }
298
- } catch (e) {
299
- state.s11n.storeException(2, e);
300
- rc = state.sq3Codes.SQLITE_IOERR_DELETE;
301
- }
302
- return rc;
303
- },
304
- xFileSize: async function (fid) {
305
- const fh = __openFiles[fid];
306
- let rc = 0;
307
- try {
308
- const sz = await (await getSyncHandle(fh, 'xFileSize')).getSize();
309
- state.s11n.serialize(Number(sz));
310
- } catch (e) {
311
- state.s11n.storeException(1, e);
312
- rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR);
313
- }
314
- await releaseImplicitLock(fh);
315
- storeAndNotify('xFileSize', rc);
316
- },
317
- xLock: async function (fid, lockType) {
318
- const fh = __openFiles[fid];
319
- let rc = 0;
320
- const oldLockType = fh.xLock;
321
- fh.xLock = lockType;
322
- if (!fh.syncHandle) {
323
- try {
324
- await getSyncHandle(fh, 'xLock');
325
- __implicitLocks.delete(fid);
326
- } catch (e) {
327
- state.s11n.storeException(1, e);
328
- rc = GetSyncHandleError.convertRc(
329
- e,
330
- state.sq3Codes.SQLITE_IOERR_LOCK,
331
- );
332
- fh.xLock = oldLockType;
333
- }
334
- }
335
- storeAndNotify('xLock', rc);
336
- },
337
- xOpen: async function (fid, filename, flags, opfsFlags) {
338
- const opName = 'xOpen';
339
- const create = state.sq3Codes.SQLITE_OPEN_CREATE & flags;
340
- try {
341
- let hDir, filenamePart;
342
- try {
343
- [hDir, filenamePart] = await getDirForFilename(filename, !!create);
344
- } catch (e) {
345
- state.s11n.storeException(1, e);
346
- storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND);
347
- return;
348
- }
349
- if (state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags) {
350
- try {
351
- await hDir.removeEntry(filenamePart);
352
- } catch (e) {}
353
- }
354
- const hFile = await hDir.getFileHandle(filenamePart, { create });
355
- const fh = Object.assign(Object.create(null), {
356
- fid: fid,
357
- filenameAbs: filename,
358
- filenamePart: filenamePart,
359
- dirHandle: hDir,
360
- fileHandle: hFile,
361
- sabView: state.sabFileBufView,
362
- readOnly: create
363
- ? false
364
- : state.sq3Codes.SQLITE_OPEN_READONLY & flags,
365
- deleteOnClose: !!(state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags),
366
- });
367
- fh.releaseImplicitLocks =
368
- opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP ||
369
- state.opfsFlags.defaultUnlockAsap;
370
- __openFiles[fid] = fh;
371
- storeAndNotify(opName, 0);
372
- } catch (e) {
373
- error(opName, e);
374
- state.s11n.storeException(1, e);
375
- storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR);
376
- }
377
- },
378
- xRead: async function (fid, n, offset64) {
379
- let rc = 0,
380
- nRead;
381
- const fh = __openFiles[fid];
382
- try {
383
- nRead = (await getSyncHandle(fh, 'xRead')).read(
384
- fh.sabView.subarray(0, n),
385
- { at: Number(offset64) },
386
- );
387
- if (nRead < n) {
388
- fh.sabView.fill(0, nRead, n);
389
- rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ;
390
- }
391
- } catch (e) {
392
- error('xRead() failed', e, fh);
393
- state.s11n.storeException(1, e);
394
- rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR_READ);
395
- }
396
- await releaseImplicitLock(fh);
397
- storeAndNotify('xRead', rc);
398
- },
399
- xSync: async function (fid, flags) {
400
- const fh = __openFiles[fid];
401
- let rc = 0;
402
- if (!fh.readOnly && fh.syncHandle) {
403
- try {
404
- await fh.syncHandle.flush();
405
- } catch (e) {
406
- state.s11n.storeException(2, e);
407
- rc = state.sq3Codes.SQLITE_IOERR_FSYNC;
408
- }
409
- }
410
- storeAndNotify('xSync', rc);
411
- },
412
- xTruncate: async function (fid, size) {
413
- let rc = 0;
414
- const fh = __openFiles[fid];
415
- try {
416
- affirmNotRO('xTruncate', fh);
417
- await (await getSyncHandle(fh, 'xTruncate')).truncate(size);
418
- } catch (e) {
419
- error('xTruncate():', e, fh);
420
- state.s11n.storeException(2, e);
421
- rc = GetSyncHandleError.convertRc(
422
- e,
423
- state.sq3Codes.SQLITE_IOERR_TRUNCATE,
424
- );
425
- }
426
- await releaseImplicitLock(fh);
427
- storeAndNotify('xTruncate', rc);
428
- },
429
- xUnlock: async function (fid, lockType) {
430
- let rc = 0;
431
- const fh = __openFiles[fid];
432
- if (state.sq3Codes.SQLITE_LOCK_NONE === lockType && fh.syncHandle) {
433
- try {
434
- await closeSyncHandle(fh);
435
- } catch (e) {
436
- state.s11n.storeException(1, e);
437
- rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
438
- }
439
- }
440
- storeAndNotify('xUnlock', rc);
441
- },
442
- xWrite: async function (fid, n, offset64) {
443
- let rc;
444
- const fh = __openFiles[fid];
445
- try {
446
- affirmNotRO('xWrite', fh);
447
- rc =
448
- n ===
449
- (await getSyncHandle(fh, 'xWrite')).write(fh.sabView.subarray(0, n), {
450
- at: Number(offset64),
451
- })
452
- ? 0
453
- : state.sq3Codes.SQLITE_IOERR_WRITE;
454
- } catch (e) {
455
- error('xWrite():', e, fh);
456
- state.s11n.storeException(1, e);
457
- rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_IOERR_WRITE);
458
- }
459
- await releaseImplicitLock(fh);
460
- storeAndNotify('xWrite', rc);
461
- },
462
- };
463
-
464
- const initS11n = () => {
465
- if (state.s11n) return state.s11n;
466
- const textDecoder = new TextDecoder(),
467
- textEncoder = new TextEncoder('utf-8'),
468
- viewU8 = new Uint8Array(
469
- state.sabIO,
470
- state.sabS11nOffset,
471
- state.sabS11nSize,
472
- ),
473
- viewDV = new DataView(
474
- state.sabIO,
475
- state.sabS11nOffset,
476
- state.sabS11nSize,
477
- );
478
- state.s11n = Object.create(null);
479
- const TypeIds = Object.create(null);
480
- TypeIds.number = {
481
- id: 1,
482
- size: 8,
483
- getter: 'getFloat64',
484
- setter: 'setFloat64',
485
- };
486
- TypeIds.bigint = {
487
- id: 2,
488
- size: 8,
489
- getter: 'getBigInt64',
490
- setter: 'setBigInt64',
491
- };
492
- TypeIds.boolean = {
493
- id: 3,
494
- size: 4,
495
- getter: 'getInt32',
496
- setter: 'setInt32',
497
- };
498
- TypeIds.string = { id: 4 };
499
- const getTypeId = (v) =>
500
- TypeIds[typeof v] ||
501
- toss('Maintenance required: this value type cannot be serialized.', v);
502
- const getTypeIdById = (tid) => {
503
- switch (tid) {
504
- case TypeIds.number.id:
505
- return TypeIds.number;
506
- case TypeIds.bigint.id:
507
- return TypeIds.bigint;
508
- case TypeIds.boolean.id:
509
- return TypeIds.boolean;
510
- case TypeIds.string.id:
511
- return TypeIds.string;
512
- default:
513
- toss('Invalid type ID:', tid);
514
- }
515
- };
516
- state.s11n.deserialize = function (clear = false) {
517
- const argc = viewU8[0];
518
- const rc = argc ? [] : null;
519
- if (argc) {
520
- const typeIds = [];
521
- let offset = 1,
522
- i,
523
- n,
524
- v;
525
- for (i = 0; i < argc; ++i, ++offset) {
526
- typeIds.push(getTypeIdById(viewU8[offset]));
527
- }
528
- for (i = 0; i < argc; ++i) {
529
- const t = typeIds[i];
530
- if (t.getter) {
531
- v = viewDV[t.getter](offset, state.littleEndian);
532
- offset += t.size;
533
- } else {
534
- n = viewDV.getInt32(offset, state.littleEndian);
535
- offset += 4;
536
- v = textDecoder.decode(viewU8.slice(offset, offset + n));
537
- offset += n;
538
- }
539
- rc.push(v);
540
- }
541
- }
542
- if (clear) viewU8[0] = 0;
543
-
544
- return rc;
545
- };
546
- state.s11n.serialize = function (...args) {
547
- if (args.length) {
548
- const typeIds = [];
549
- let i = 0,
550
- offset = 1;
551
- viewU8[0] = args.length & 0xff;
552
- for (; i < args.length; ++i, ++offset) {
553
- typeIds.push(getTypeId(args[i]));
554
- viewU8[offset] = typeIds[i].id;
555
- }
556
- for (i = 0; i < args.length; ++i) {
557
- const t = typeIds[i];
558
- if (t.setter) {
559
- viewDV[t.setter](offset, args[i], state.littleEndian);
560
- offset += t.size;
561
- } else {
562
- const s = textEncoder.encode(args[i]);
563
- viewDV.setInt32(offset, s.byteLength, state.littleEndian);
564
- offset += 4;
565
- viewU8.set(s, offset);
566
- offset += s.byteLength;
567
- }
568
- }
569
- } else {
570
- viewU8[0] = 0;
571
- }
572
- };
573
-
574
- state.s11n.storeException = state.asyncS11nExceptions
575
- ? (priority, e) => {
576
- if (priority <= state.asyncS11nExceptions) {
577
- state.s11n.serialize([e.name, ': ', e.message].join(''));
578
- }
579
- }
580
- : () => {};
581
-
582
- return state.s11n;
583
- };
584
-
585
- const waitLoop = async function f() {
586
- const opHandlers = Object.create(null);
587
- for (let k of Object.keys(state.opIds)) {
588
- const vi = vfsAsyncImpls[k];
589
- if (!vi) continue;
590
- const o = Object.create(null);
591
- opHandlers[state.opIds[k]] = o;
592
- o.key = k;
593
- o.f = vi;
594
- }
595
- while (!flagAsyncShutdown) {
596
- try {
597
- if (
598
- 'not-equal' !==
599
- Atomics.wait(
600
- state.sabOPView,
601
- state.opIds.whichOp,
602
- 0,
603
- state.asyncIdleWaitTime,
604
- )
605
- ) {
606
- await releaseImplicitLocks();
607
- continue;
608
- }
609
- const opId = Atomics.load(state.sabOPView, state.opIds.whichOp);
610
- Atomics.store(state.sabOPView, state.opIds.whichOp, 0);
611
- const hnd =
612
- opHandlers[opId] ?? toss('No waitLoop handler for whichOp #', opId);
613
- const args = state.s11n.deserialize(true) || [];
614
-
615
- if (hnd.f) await hnd.f(...args);
616
- else error('Missing callback for opId', opId);
617
- } catch (e) {
618
- error('in waitLoop():', e);
619
- }
620
- }
621
- };
622
-
623
- navigator.storage
624
- .getDirectory()
625
- .then(function (d) {
626
- state.rootDir = d;
627
- globalThis.onmessage = function ({ data }) {
628
- switch (data.type) {
629
- case 'opfs-async-init': {
630
- const opt = data.args;
631
- for (const k in opt) state[k] = opt[k];
632
- state.verbose = opt.verbose ?? 1;
633
- state.sabOPView = new Int32Array(state.sabOP);
634
- state.sabFileBufView = new Uint8Array(
635
- state.sabIO,
636
- 0,
637
- state.fileBufferSize,
638
- );
639
- state.sabS11nView = new Uint8Array(
640
- state.sabIO,
641
- state.sabS11nOffset,
642
- state.sabS11nSize,
643
- );
644
- Object.keys(vfsAsyncImpls).forEach((k) => {
645
- if (!Number.isFinite(state.opIds[k])) {
646
- toss('Maintenance required: missing state.opIds[', k, ']');
647
- }
648
- });
649
- initS11n();
650
- log('init state', state);
651
- wPost('opfs-async-inited');
652
- waitLoop();
653
- break;
654
- }
655
- case 'opfs-async-restart':
656
- if (flagAsyncShutdown) {
657
- warn(
658
- 'Restarting after opfs-async-shutdown. Might or might not work.',
659
- );
660
- flagAsyncShutdown = false;
661
- waitLoop();
662
- }
663
- break;
664
- }
665
- };
666
- wPost('opfs-async-loaded');
667
- })
668
- .catch((e) => error('error initializing OPFS asyncer:', e));
669
- };
670
- if (!globalThis.SharedArrayBuffer) {
671
- wPost(
672
- 'opfs-unavailable',
673
- 'Missing SharedArrayBuffer API.',
674
- 'The server must emit the COOP/COEP response headers to enable that.',
675
- );
676
- } else if (!globalThis.Atomics) {
677
- wPost(
678
- 'opfs-unavailable',
679
- 'Missing Atomics API.',
680
- 'The server must emit the COOP/COEP response headers to enable that.',
681
- );
682
- } else if (
683
- !globalThis.FileSystemHandle ||
684
- !globalThis.FileSystemDirectoryHandle ||
685
- !globalThis.FileSystemFileHandle ||
686
- !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle ||
687
- !navigator?.storage?.getDirectory
688
- ) {
689
- wPost('opfs-unavailable', 'Missing required OPFS APIs.');
690
- } else {
691
- installAsyncProxy();
692
- }
1
+ const e=(e,...t)=>postMessage({type:e,payload:t}),t=function(){const t=function(...e){throw new Error(e.join(" "))};globalThis.window===globalThis?t("This code cannot run from the main thread.","Load it as a Worker from a separate Worker."):navigator?.storage?.getDirectory||t("This API requires navigator.storage.getDirectory.");const n=Object.create(null);n.verbose=1;const s={0:console.error.bind(console),1:console.warn.bind(console),2:console.log.bind(console)},a=(e,...t)=>{n.verbose>e&&s[e]("OPFS asyncer:",...t)},i=(...e)=>a(2,...e),o=(...e)=>a(1,...e),r=(...e)=>a(0,...e),c=Object.create(null),l=new Set,d=function(e,t){const n=new URL(e,"file://irrelevant").pathname;return t?n.split("/").filter((e=>!!e)):n},f=async function(e,t=!1){const s=d(e,!0),a=s.pop();let i=n.rootDir;for(const e of s)e&&(i=await i.getDirectoryHandle(e,{create:!!t}));return[i,a]},y=async e=>{if(e.syncHandle){i("Closing sync handle for",e.filenameAbs);const t=e.syncHandle;return delete e.syncHandle,delete e.xLock,l.delete(e.fid),t.close()}},u=async e=>{try{await y(e)}catch(t){o("closeSyncHandleNoThrow() ignoring:",t,e)}},E=async()=>{if(l.size)for(const e of l){const t=c[e];await u(t),i("Auto-unlocked",e,t.filenameAbs)}},b=async e=>{if(e.releaseImplicitLocks&&l.has(e.fid))return u(e)};class O extends Error{constructor(e,...t){super([...t,": "+e.name+":",e.message].join(" "),{cause:e}),this.name="GetSyncHandleError"}}O.convertRc=(e,t)=>{if(e instanceof O){if("NoModificationAllowedError"===e.cause.name||"DOMException"===e.cause.name&&0===e.cause.message.indexOf("Access Handles cannot"))return n.sq3Codes.SQLITE_BUSY;if("NotFoundError"===e.cause.name)return n.sq3Codes.SQLITE_CANTOPEN}else if("NotFoundError"===e?.name)return n.sq3Codes.SQLITE_CANTOPEN;return t};const g=async(e,t)=>{if(!e.syncHandle){const s=performance.now();i("Acquiring sync handle for",e.filenameAbs);const a=6,r=2*n.asyncIdleWaitTime;let c=1,d=r;for(;;d=r*++c)try{e.syncHandle=await e.fileHandle.createSyncAccessHandle();break}catch(s){if(c===a)throw new O(s,"Error getting sync handle for",t+"().",a,"attempts failed.",e.filenameAbs);o("Error getting sync handle for",t+"(). Waiting",d,"ms and trying again.",e.filenameAbs,s),Atomics.wait(n.sabOPView,n.opIds.retry,0,d)}i("Got",t+"() sync handle for",e.filenameAbs,"in",performance.now()-s,"ms"),e.xLock||(l.add(e.fid),i("Acquired implicit lock for",t+"()",e.fid,e.filenameAbs))}return e.syncHandle},h=(e,t)=>{i(e+"() => notify(",t,")"),Atomics.store(n.sabOPView,n.opIds.rc,t),Atomics.notify(n.sabOPView,n.opIds.rc)},w=function(e,n){n.readOnly&&t(e+"(): File is read-only: "+n.filenameAbs)};let p=!1;const I={"opfs-async-shutdown":async()=>{p=!0,h("opfs-async-shutdown",0)},mkdir:async e=>{let t=0;try{await f(e+"/filepart",!0)}catch(e){n.s11n.storeException(2,e),t=n.sq3Codes.SQLITE_IOERR}h("mkdir",t)},xAccess:async e=>{let t=0;try{const[t,n]=await f(e);await t.getFileHandle(n)}catch(e){n.s11n.storeException(2,e),t=n.sq3Codes.SQLITE_IOERR}h("xAccess",t)},xClose:async function(e){l.delete(e);const t=c[e];let s=0;if(t){if(delete c[e],await y(t),t.deleteOnClose)try{await t.dirHandle.removeEntry(t.filenamePart)}catch(e){o("Ignoring dirHandle.removeEntry() failure of",t,e)}}else n.s11n.serialize(),s=n.sq3Codes.SQLITE_NOTFOUND;h("xClose",s)},xDelete:async function(...e){const t=await I.xDeleteNoWait(...e);h("xDelete",t)},xDeleteNoWait:async function(e,t=0,s=!1){let a=0;try{for(;e;){const[n,a]=await f(e,!1);if(!a)break;if(await n.removeEntry(a,{recursive:s}),4660!==t)break;s=!1,(e=d(e,!0)).pop(),e=e.join("/")}}catch(e){n.s11n.storeException(2,e),a=n.sq3Codes.SQLITE_IOERR_DELETE}return a},xFileSize:async function(e){const t=c[e];let s=0;try{const e=await(await g(t,"xFileSize")).getSize();n.s11n.serialize(Number(e))}catch(e){n.s11n.storeException(1,e),s=O.convertRc(e,n.sq3Codes.SQLITE_IOERR)}await b(t),h("xFileSize",s)},xLock:async function(e,t){const s=c[e];let a=0;const i=s.xLock;if(s.xLock=t,!s.syncHandle)try{await g(s,"xLock"),l.delete(e)}catch(e){n.s11n.storeException(1,e),a=O.convertRc(e,n.sq3Codes.SQLITE_IOERR_LOCK),s.xLock=i}h("xLock",a)},xOpen:async function(e,t,s,a){const i="xOpen",o=n.sq3Codes.SQLITE_OPEN_CREATE&s;try{let r,l;try{[r,l]=await f(t,!!o)}catch(e){return n.s11n.storeException(1,e),void h(i,n.sq3Codes.SQLITE_NOTFOUND)}if(n.opfsFlags.OPFS_UNLINK_BEFORE_OPEN&a)try{await r.removeEntry(l)}catch(e){}const d=await r.getFileHandle(l,{create:o}),y=Object.assign(Object.create(null),{fid:e,filenameAbs:t,filenamePart:l,dirHandle:r,fileHandle:d,sabView:n.sabFileBufView,readOnly:!o&&n.sq3Codes.SQLITE_OPEN_READONLY&s,deleteOnClose:!!(n.sq3Codes.SQLITE_OPEN_DELETEONCLOSE&s)});y.releaseImplicitLocks=a&n.opfsFlags.OPFS_UNLOCK_ASAP||n.opfsFlags.defaultUnlockAsap,c[e]=y,h(i,0)}catch(e){r(i,e),n.s11n.storeException(1,e),h(i,n.sq3Codes.SQLITE_IOERR)}},xRead:async function(e,t,s){let a,i=0;const o=c[e];try{a=(await g(o,"xRead")).read(o.sabView.subarray(0,t),{at:Number(s)}),a<t&&(o.sabView.fill(0,a,t),i=n.sq3Codes.SQLITE_IOERR_SHORT_READ)}catch(e){r("xRead() failed",e,o),n.s11n.storeException(1,e),i=O.convertRc(e,n.sq3Codes.SQLITE_IOERR_READ)}await b(o),h("xRead",i)},xSync:async function(e,t){const s=c[e];let a=0;if(!s.readOnly&&s.syncHandle)try{await s.syncHandle.flush()}catch(e){n.s11n.storeException(2,e),a=n.sq3Codes.SQLITE_IOERR_FSYNC}h("xSync",a)},xTruncate:async function(e,t){let s=0;const a=c[e];try{w("xTruncate",a),await(await g(a,"xTruncate")).truncate(t)}catch(e){r("xTruncate():",e,a),n.s11n.storeException(2,e),s=O.convertRc(e,n.sq3Codes.SQLITE_IOERR_TRUNCATE)}await b(a),h("xTruncate",s)},xUnlock:async function(e,t){let s=0;const a=c[e];if(n.sq3Codes.SQLITE_LOCK_NONE===t&&a.syncHandle)try{await y(a)}catch(e){n.s11n.storeException(1,e),s=n.sq3Codes.SQLITE_IOERR_UNLOCK}h("xUnlock",s)},xWrite:async function(e,t,s){let a;const i=c[e];try{w("xWrite",i),a=t===(await g(i,"xWrite")).write(i.sabView.subarray(0,t),{at:Number(s)})?0:n.sq3Codes.SQLITE_IOERR_WRITE}catch(e){r("xWrite():",e,i),n.s11n.storeException(1,e),a=O.convertRc(e,n.sq3Codes.SQLITE_IOERR_WRITE)}await b(i),h("xWrite",a)}},m=async function(){const e=Object.create(null);for(let t of Object.keys(n.opIds)){const s=I[t];if(!s)continue;const a=Object.create(null);e[n.opIds[t]]=a,a.key=t,a.f=s}for(;!p;)try{if("not-equal"!==Atomics.wait(n.sabOPView,n.opIds.whichOp,0,n.asyncIdleWaitTime)){await E();continue}const s=Atomics.load(n.sabOPView,n.opIds.whichOp);Atomics.store(n.sabOPView,n.opIds.whichOp,0);const a=e[s]??t("No waitLoop handler for whichOp #",s),i=n.s11n.deserialize(!0)||[];a.f?await a.f(...i):r("Missing callback for opId",s)}catch(e){r("in waitLoop():",e)}};navigator.storage.getDirectory().then((function(s){n.rootDir=s,globalThis.onmessage=function({data:s}){switch(s.type){case"opfs-async-init":{const a=s.args;for(const e in a)n[e]=a[e];n.verbose=a.verbose??1,n.sabOPView=new Int32Array(n.sabOP),n.sabFileBufView=new Uint8Array(n.sabIO,0,n.fileBufferSize),n.sabS11nView=new Uint8Array(n.sabIO,n.sabS11nOffset,n.sabS11nSize),Object.keys(I).forEach((e=>{Number.isFinite(n.opIds[e])||t("Maintenance required: missing state.opIds[",e,"]")})),(()=>{if(n.s11n)return n.s11n;const e=new TextDecoder,s=new TextEncoder("utf-8"),a=new Uint8Array(n.sabIO,n.sabS11nOffset,n.sabS11nSize),i=new DataView(n.sabIO,n.sabS11nOffset,n.sabS11nSize);n.s11n=Object.create(null);const o=Object.create(null);o.number={id:1,size:8,getter:"getFloat64",setter:"setFloat64"},o.bigint={id:2,size:8,getter:"getBigInt64",setter:"setBigInt64"},o.boolean={id:3,size:4,getter:"getInt32",setter:"setInt32"},o.string={id:4};const r=e=>{switch(e){case o.number.id:return o.number;case o.bigint.id:return o.bigint;case o.boolean.id:return o.boolean;case o.string.id:return o.string;default:t("Invalid type ID:",e)}};n.s11n.deserialize=function(t=!1){const s=a[0],o=s?[]:null;if(s){const t=[];let c,l,d,f=1;for(c=0;c<s;++c,++f)t.push(r(a[f]));for(c=0;c<s;++c){const s=t[c];s.getter?(d=i[s.getter](f,n.littleEndian),f+=s.size):(l=i.getInt32(f,n.littleEndian),f+=4,d=e.decode(a.slice(f,f+l)),f+=l),o.push(d)}}return t&&(a[0]=0),o},n.s11n.serialize=function(...e){if(e.length){const c=[];let l=0,d=1;for(a[0]=255&e.length;l<e.length;++l,++d)c.push((r=e[l],o[typeof r]||t("Maintenance required: this value type cannot be serialized.",r))),a[d]=c[l].id;for(l=0;l<e.length;++l){const t=c[l];if(t.setter)i[t.setter](d,e[l],n.littleEndian),d+=t.size;else{const t=s.encode(e[l]);i.setInt32(d,t.byteLength,n.littleEndian),d+=4,a.set(t,d),d+=t.byteLength}}}else a[0]=0;var r},n.s11n.storeException=n.asyncS11nExceptions?(e,t)=>{e<=n.asyncS11nExceptions&&n.s11n.serialize([t.name,": ",t.message].join(""))}:()=>{},n.s11n})(),i("init state",n),e("opfs-async-inited"),m();break}case"opfs-async-restart":p&&(o("Restarting after opfs-async-shutdown. Might or might not work."),p=!1,m())}},e("opfs-async-loaded")})).catch((e=>r("error initializing OPFS asyncer:",e)))};globalThis.SharedArrayBuffer?globalThis.Atomics?globalThis.FileSystemHandle&&globalThis.FileSystemDirectoryHandle&&globalThis.FileSystemFileHandle&&globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle&&navigator?.storage?.getDirectory?t():e("opfs-unavailable","Missing required OPFS APIs."):e("opfs-unavailable","Missing Atomics API.","The server must emit the COOP/COEP response headers to enable that."):e("opfs-unavailable","Missing SharedArrayBuffer API.","The server must emit the COOP/COEP response headers to enable that.");