@statewalker/webrun-files-sqlite 0.10.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -55
- package/dist/blocks/sqlite-files-api.d.ts +3 -3
- package/dist/blocks/sqlite-files-api.d.ts.map +1 -1
- package/dist/bytes.d.ts +1 -1
- package/dist/bytes.d.ts.map +1 -1
- package/dist/cjs/index.cjs +81 -112
- package/dist/esm/index.js +82 -110
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/run-statements.d.ts +15 -0
- package/dist/run-statements.d.ts.map +1 -0
- package/dist/sqlar-files-api.d.ts +2 -2
- package/dist/sqlar-files-api.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/blocks/sqlite-files-api.ts +40 -31
- package/src/bytes.ts +1 -3
- package/src/index.ts +0 -9
- package/src/run-statements.ts +24 -0
- package/src/sqlar-files-api.ts +20 -25
- package/dist/drivers.d.ts +0 -73
- package/dist/drivers.d.ts.map +0 -1
- package/dist/sql.types.d.ts +0 -25
- package/dist/sql.types.d.ts.map +0 -1
- package/src/drivers.ts +0 -133
- package/src/sql.types.ts +0 -25
package/dist/esm/index.js
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
//#region ../../node_modules/.pnpm/@statewalker+db-sqlite@0.1.0/node_modules/@statewalker/db-sqlite/dist/index.mjs
|
|
2
|
+
/** Key under which a SQLite Db carries its transaction hook. Shared across bundles via Symbol.for. */
|
|
3
|
+
const SQLITE_TRANSACTION = Symbol.for("@statewalker/db-sqlite:transaction");
|
|
4
|
+
const NO_HOOK = "inTransaction: this Db has no SQLite transaction hook. It is not a @statewalker/db-sqlite* Db, or it wraps one (a wrapper object loses the hook - pass the driver's Db itself), or it is the handle of a running transaction (nesting is not supported).";
|
|
5
|
+
/**
|
|
6
|
+
* Run `cb` in one SQLite transaction: every statement issued through `tx` commits together, or
|
|
7
|
+
* none does if `cb` throws. Other calls on `db` wait until the transaction ends. `cb` must not do
|
|
8
|
+
* external I/O and must be idempotent: a Durable Object may run it more than once.
|
|
9
|
+
*/
|
|
10
|
+
function inTransaction(db, cb) {
|
|
11
|
+
const hook = db[SQLITE_TRANSACTION];
|
|
12
|
+
if (typeof hook !== "function") return Promise.reject(/* @__PURE__ */ new Error(NO_HOOK));
|
|
13
|
+
return hook(cb);
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
1
16
|
//#region ../webrun-files/dist/esm/index.js
|
|
2
17
|
/**
|
|
3
18
|
* The order every `FilesApi.list()` yields: paths compared by Unicode code
|
|
@@ -37,15 +52,31 @@ function normalizePath(filePath) {
|
|
|
37
52
|
}
|
|
38
53
|
//#endregion
|
|
39
54
|
//#region src/bytes.ts
|
|
40
|
-
/**
|
|
55
|
+
/** BLOBs arrive as Uint8Array from every @statewalker/db-sqlite driver; SQLAR may hold TEXT. */
|
|
41
56
|
function toBytes(value) {
|
|
42
57
|
if (value instanceof Uint8Array) return value;
|
|
43
|
-
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
44
|
-
if (Array.isArray(value)) return Uint8Array.from(value);
|
|
45
58
|
if (typeof value === "string") return new TextEncoder().encode(value);
|
|
46
59
|
return /* @__PURE__ */ new Uint8Array(0);
|
|
47
60
|
}
|
|
48
61
|
//#endregion
|
|
62
|
+
//#region src/run-statements.ts
|
|
63
|
+
/**
|
|
64
|
+
* Runs the statements in order on a transaction's Db and returns, per
|
|
65
|
+
* statement, the rows it changed — zero exactly when it changed nothing. The
|
|
66
|
+
* count is SQLite's `changes()` on the same connection, so index writes are
|
|
67
|
+
* not counted. Call it only inside `inTransaction`; it does nothing but SQL,
|
|
68
|
+
* so a retried transaction callback is safe.
|
|
69
|
+
*/
|
|
70
|
+
async function runStatements(tx, statements) {
|
|
71
|
+
const counts = [];
|
|
72
|
+
for (const { sql, params = [] } of statements) {
|
|
73
|
+
await tx.query(sql, params);
|
|
74
|
+
const [row] = await tx.query("SELECT changes() AS n");
|
|
75
|
+
counts.push(Number(row.n));
|
|
76
|
+
}
|
|
77
|
+
return counts;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
49
80
|
//#region src/blocks/byte-reader.ts
|
|
50
81
|
/**
|
|
51
82
|
* Pull bytes from a chunk source in bounded pieces.
|
|
@@ -352,33 +383,33 @@ async function* iterate(stream) {
|
|
|
352
383
|
//#endregion
|
|
353
384
|
//#region src/blocks/sqlite-files-api.ts
|
|
354
385
|
const MiB = 1048576;
|
|
355
|
-
/** Rows on Durable Objects
|
|
386
|
+
/** Rows on Durable Objects are capped at 2 MB; leave room for deflate's worst case. */
|
|
356
387
|
const MAX_BLOCK_SIZE_LIMIT = 1.5 * MiB;
|
|
357
388
|
const LIST_PAGE = 256;
|
|
358
389
|
/**
|
|
359
390
|
* A FilesApi over three SQLite tables: paths point at immutable contents, and
|
|
360
391
|
* a content is a sequence of bounded, optionally compressed blocks. Reads and
|
|
361
392
|
* writes stream one block at a time, so no file is ever held whole and no row
|
|
362
|
-
* outgrows the 2 MB limit of Durable Objects
|
|
393
|
+
* outgrows the 2 MB row limit of Durable Objects.
|
|
363
394
|
*
|
|
364
395
|
* Streaming happens outside transactions; every step that changes what a path
|
|
365
396
|
* shows is one transaction, so an interruption leaves at most an unreferenced
|
|
366
397
|
* content, which {@link sweep} removes.
|
|
367
398
|
*/
|
|
368
399
|
var SqliteFilesApi = class {
|
|
369
|
-
#
|
|
400
|
+
#db;
|
|
370
401
|
#codec;
|
|
371
402
|
#blockSize;
|
|
372
403
|
#paths;
|
|
373
404
|
#files;
|
|
374
405
|
#blocks;
|
|
375
406
|
#prefix;
|
|
376
|
-
constructor(
|
|
407
|
+
constructor(db, opts = {}) {
|
|
377
408
|
const prefix = opts.tablePrefix ?? "fs_";
|
|
378
409
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) throw new Error(`SqliteFilesApi: tablePrefix must be a plain SQL identifier, got ${prefix}`);
|
|
379
410
|
this.#blockSize = opts.blockSize ?? MiB;
|
|
380
411
|
if (!Number.isInteger(this.#blockSize) || this.#blockSize < 1 || this.#blockSize > MAX_BLOCK_SIZE_LIMIT) throw new Error(`SqliteFilesApi: blockSize must be an integer from 1 to ${MAX_BLOCK_SIZE_LIMIT}, got ${this.#blockSize}`);
|
|
381
|
-
this.#
|
|
412
|
+
this.#db = db;
|
|
382
413
|
this.#codec = opts.compression === void 0 ? availableWebCodec() : opts.compression;
|
|
383
414
|
this.#prefix = prefix;
|
|
384
415
|
this.#paths = `${prefix}paths`;
|
|
@@ -411,7 +442,7 @@ var SqliteFilesApi = class {
|
|
|
411
442
|
`CREATE INDEX IF NOT EXISTS ${this.#prefix}paths_fid ON ${this.#paths}(fid)`,
|
|
412
443
|
`CREATE INDEX IF NOT EXISTS ${this.#prefix}files_hash ON ${this.#files}(hash, size)`
|
|
413
444
|
];
|
|
414
|
-
for (const statement of statements) await this.#
|
|
445
|
+
for (const statement of statements) await this.#db.query(statement);
|
|
415
446
|
}
|
|
416
447
|
async *read(path, options = {}) {
|
|
417
448
|
options.signal?.throwIfAborted();
|
|
@@ -455,8 +486,12 @@ var SqliteFilesApi = class {
|
|
|
455
486
|
async write(path, content) {
|
|
456
487
|
const name = normalizePath(path);
|
|
457
488
|
const compression = this.#codec?.name ?? "none";
|
|
458
|
-
const fid =
|
|
459
|
-
|
|
489
|
+
const fid = await inTransaction(this.#db, async (tx) => (await tx.query(`INSERT INTO ${this.#files}(size, compression, block_size, hash, updated)
|
|
490
|
+
VALUES (NULL, ?, ?, NULL, ?) RETURNING fid`, [
|
|
491
|
+
compression,
|
|
492
|
+
this.#blockSize,
|
|
493
|
+
Date.now()
|
|
494
|
+
]))[0].fid);
|
|
460
495
|
const limit = this.#blockSize;
|
|
461
496
|
const reader = new ByteReader(content);
|
|
462
497
|
const hash = new Sha256();
|
|
@@ -477,7 +512,7 @@ var SqliteFilesApi = class {
|
|
|
477
512
|
}
|
|
478
513
|
};
|
|
479
514
|
const block = await collectBlock(this.#codec ? this.#codec.compress(pieces()) : pieces());
|
|
480
|
-
const [alive] = await this.#
|
|
515
|
+
const [alive] = await this.#transaction([{
|
|
481
516
|
sql: `UPDATE ${this.#files} SET updated = ? WHERE fid = ? AND size IS NULL`,
|
|
482
517
|
params: [Date.now(), fid]
|
|
483
518
|
}, {
|
|
@@ -494,14 +529,14 @@ var SqliteFilesApi = class {
|
|
|
494
529
|
size += raw;
|
|
495
530
|
}
|
|
496
531
|
const previous = await this.#entry(name);
|
|
497
|
-
const [finished] = await this.#
|
|
532
|
+
const [finished] = await this.#transaction(this.#finishWrite(name, fid, size, hash.digestHex(), compression, previous?.fid ?? null));
|
|
498
533
|
if (finished === 0) throw sweptDuringWrite(name);
|
|
499
534
|
} catch (error) {
|
|
500
535
|
try {
|
|
501
536
|
await reader.close();
|
|
502
537
|
} catch {}
|
|
503
538
|
try {
|
|
504
|
-
await this.#
|
|
539
|
+
await this.#transaction([{
|
|
505
540
|
sql: `DELETE FROM ${this.#files} WHERE fid = ? AND size IS NULL`,
|
|
506
541
|
params: [fid]
|
|
507
542
|
}, ...this.#deleteBlocksOfMissing(fid)]);
|
|
@@ -566,7 +601,7 @@ var SqliteFilesApi = class {
|
|
|
566
601
|
}
|
|
567
602
|
async mkdir(path) {
|
|
568
603
|
const now = Date.now();
|
|
569
|
-
for (const dir of ancestorsAndSelf$1(normalizePath(path))) await this.#
|
|
604
|
+
for (const dir of ancestorsAndSelf$1(normalizePath(path))) await this.#db.query(`INSERT INTO ${this.#paths}(path, fid, mtime) VALUES (?, NULL, ?) ON CONFLICT(path) DO NOTHING`, [dir, now]);
|
|
570
605
|
}
|
|
571
606
|
async stats(path) {
|
|
572
607
|
const name = normalizePath(path);
|
|
@@ -587,10 +622,10 @@ var SqliteFilesApi = class {
|
|
|
587
622
|
for (;;) {
|
|
588
623
|
const params = [...range.params, cursor];
|
|
589
624
|
if (!options.recursive) params.push(prefixLength + 1);
|
|
590
|
-
const rows = await this.#
|
|
625
|
+
const rows = await this.#db.query(`SELECT p.path, p.fid, p.mtime, f.size, f.compression, f.block_size
|
|
591
626
|
FROM ${this.#paths} p LEFT JOIN ${this.#files} f ON f.fid = p.fid
|
|
592
627
|
WHERE ${range.where} AND p.path > ? ${directOnly}
|
|
593
|
-
ORDER BY p.path LIMIT ${LIST_PAGE}`,
|
|
628
|
+
ORDER BY p.path LIMIT ${LIST_PAGE}`, params);
|
|
594
629
|
for (const row of rows) {
|
|
595
630
|
const stats = toStats$1(row);
|
|
596
631
|
if (stats) yield {
|
|
@@ -604,7 +639,7 @@ var SqliteFilesApi = class {
|
|
|
604
639
|
}
|
|
605
640
|
}
|
|
606
641
|
async remove(path) {
|
|
607
|
-
const counts = await this.#
|
|
642
|
+
const counts = await this.#transaction(this.#removeTree(normalizePath(path)));
|
|
608
643
|
return counts[counts.length - 1] > 0;
|
|
609
644
|
}
|
|
610
645
|
/**
|
|
@@ -694,7 +729,7 @@ var SqliteFilesApi = class {
|
|
|
694
729
|
]
|
|
695
730
|
}
|
|
696
731
|
];
|
|
697
|
-
const counts = await this.#
|
|
732
|
+
const counts = await this.#transaction(statements);
|
|
698
733
|
return counts[counts.length - 1] > 0;
|
|
699
734
|
}
|
|
700
735
|
/**
|
|
@@ -713,7 +748,7 @@ var SqliteFilesApi = class {
|
|
|
713
748
|
const eligible = `f.updated < ? AND NOT EXISTS (
|
|
714
749
|
SELECT 1 FROM ${this.#paths} p WHERE p.fid = f.fid
|
|
715
750
|
)`;
|
|
716
|
-
const [, contents] = await this.#
|
|
751
|
+
const [, contents] = await this.#transaction([{
|
|
717
752
|
sql: `DELETE FROM ${this.#blocks} WHERE fid IN (SELECT f.fid FROM ${this.#files} f WHERE ${eligible})`,
|
|
718
753
|
params: [cutoff]
|
|
719
754
|
}, {
|
|
@@ -722,10 +757,14 @@ var SqliteFilesApi = class {
|
|
|
722
757
|
}]);
|
|
723
758
|
return contents;
|
|
724
759
|
}
|
|
760
|
+
/** The statements as one transaction; per statement, the rows it changed. */
|
|
761
|
+
#transaction(statements) {
|
|
762
|
+
return inTransaction(this.#db, (tx) => runStatements(tx, statements));
|
|
763
|
+
}
|
|
725
764
|
async #entry(name) {
|
|
726
|
-
return (await this.#
|
|
765
|
+
return (await this.#db.query(`SELECT p.fid, p.mtime, f.size, f.compression, f.block_size
|
|
727
766
|
FROM ${this.#paths} p LEFT JOIN ${this.#files} f ON f.fid = p.fid
|
|
728
|
-
WHERE p.path = ?`, name))[0];
|
|
767
|
+
WHERE p.path = ?`, [name]))[0];
|
|
729
768
|
}
|
|
730
769
|
#decoder(name, compression) {
|
|
731
770
|
if (compression === "none") return null;
|
|
@@ -737,7 +776,7 @@ var SqliteFilesApi = class {
|
|
|
737
776
|
throw new Error(`sqlite-files: ${name} is compressed with ${compression}, and no codec reads it`);
|
|
738
777
|
}
|
|
739
778
|
async #block(fid, shift) {
|
|
740
|
-
return (await this.#
|
|
779
|
+
return (await this.#db.query(`SELECT shift, block FROM ${this.#blocks} WHERE fid = ? AND shift = ?`, [fid, shift]))[0];
|
|
741
780
|
}
|
|
742
781
|
/** A subquery selecting the content `fid` while it is still pending. */
|
|
743
782
|
#pending(fid) {
|
|
@@ -871,79 +910,6 @@ async function pipe(bytes, transform) {
|
|
|
871
910
|
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
872
911
|
}
|
|
873
912
|
//#endregion
|
|
874
|
-
//#region src/drivers.ts
|
|
875
|
-
/** node:sqlite — local development, tests and any Node deployment. */
|
|
876
|
-
var NodeSqlDriver = class {
|
|
877
|
-
#db;
|
|
878
|
-
constructor(db) {
|
|
879
|
-
this.#db = db;
|
|
880
|
-
}
|
|
881
|
-
all(sql, ...params) {
|
|
882
|
-
return this.#db.prepare(sql).all(...params);
|
|
883
|
-
}
|
|
884
|
-
run(sql, ...params) {
|
|
885
|
-
this.#db.prepare(sql).run(...params);
|
|
886
|
-
}
|
|
887
|
-
/** Synchronous from BEGIN to COMMIT, so no other call can interleave on the connection. */
|
|
888
|
-
transaction(statements) {
|
|
889
|
-
this.#db.exec("BEGIN IMMEDIATE");
|
|
890
|
-
try {
|
|
891
|
-
const counts = statements.map(({ sql, params = [] }) => Number(this.#db.prepare(sql).run(...params).changes));
|
|
892
|
-
this.#db.exec("COMMIT");
|
|
893
|
-
return counts;
|
|
894
|
-
} catch (error) {
|
|
895
|
-
this.#db.exec("ROLLBACK");
|
|
896
|
-
throw error;
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
};
|
|
900
|
-
/**
|
|
901
|
-
* Durable Object SQLite storage: pass `ctx.storage`. Synchronous; BLOBs come
|
|
902
|
-
* back as ArrayBuffer. Transactions use `transactionSync`, since `sql.exec`
|
|
903
|
-
* rejects `BEGIN`.
|
|
904
|
-
*/
|
|
905
|
-
var DoSqlDriver = class {
|
|
906
|
-
#storage;
|
|
907
|
-
constructor(storage) {
|
|
908
|
-
this.#storage = storage;
|
|
909
|
-
}
|
|
910
|
-
all(sql, ...params) {
|
|
911
|
-
return this.#storage.sql.exec(sql, ...params).toArray();
|
|
912
|
-
}
|
|
913
|
-
run(sql, ...params) {
|
|
914
|
-
this.#storage.sql.exec(sql, ...params).toArray();
|
|
915
|
-
}
|
|
916
|
-
/**
|
|
917
|
-
* Counts come from SQLite's `changes()` on the same connection, not from the
|
|
918
|
-
* cursor's `rowsWritten`, which also counts every index row a write touches.
|
|
919
|
-
*/
|
|
920
|
-
transaction(statements) {
|
|
921
|
-
return this.#storage.transactionSync(() => statements.map(({ sql, params = [] }) => {
|
|
922
|
-
this.#storage.sql.exec(sql, ...params).toArray();
|
|
923
|
-
const [row] = this.#storage.sql.exec("SELECT changes() AS n").toArray();
|
|
924
|
-
return row.n;
|
|
925
|
-
}));
|
|
926
|
-
}
|
|
927
|
-
};
|
|
928
|
-
/** Cloudflare D1. Asynchronous; BLOBs come back as number[]; transactions are `batch()`. */
|
|
929
|
-
var D1SqlDriver = class {
|
|
930
|
-
#db;
|
|
931
|
-
constructor(db) {
|
|
932
|
-
this.#db = db;
|
|
933
|
-
}
|
|
934
|
-
async all(sql, ...params) {
|
|
935
|
-
const { results } = await this.#db.prepare(sql).bind(...params).all();
|
|
936
|
-
return results;
|
|
937
|
-
}
|
|
938
|
-
async run(sql, ...params) {
|
|
939
|
-
await this.#db.prepare(sql).bind(...params).run();
|
|
940
|
-
}
|
|
941
|
-
async transaction(statements) {
|
|
942
|
-
if (statements.length === 0) return [];
|
|
943
|
-
return (await this.#db.batch(statements.map(({ sql, params = [] }) => this.#db.prepare(sql).bind(...params)))).map((result) => result.meta.changes);
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
//#endregion
|
|
947
913
|
//#region src/sqlar-files-api.ts
|
|
948
914
|
/**
|
|
949
915
|
* The table, verbatim from the SQLite Archive specification. Five columns,
|
|
@@ -970,22 +936,22 @@ const MAX_SYMLINK_HOPS = 8;
|
|
|
970
936
|
* missing path — and {@link symlink} / {@link readlink} expose the link itself.
|
|
971
937
|
*/
|
|
972
938
|
var SqlarFilesApi = class {
|
|
973
|
-
#
|
|
939
|
+
#db;
|
|
974
940
|
#codec;
|
|
975
|
-
constructor(
|
|
976
|
-
this.#
|
|
941
|
+
constructor(db, opts = {}) {
|
|
942
|
+
this.#db = db;
|
|
977
943
|
this.#codec = opts.codec ?? defaultCodec();
|
|
978
944
|
}
|
|
979
945
|
/** Creates the table if it is missing. Await it before any other call. */
|
|
980
946
|
async init() {
|
|
981
|
-
await this.#
|
|
947
|
+
await this.#db.query(SCHEMA);
|
|
982
948
|
}
|
|
983
949
|
/** A missing path or a directory yields nothing; a file yields one chunk. */
|
|
984
950
|
async *read(path, options = {}) {
|
|
985
951
|
options.signal?.throwIfAborted();
|
|
986
952
|
const resolved = await this.#resolve(toName(path));
|
|
987
953
|
if (resolved?.stats.kind !== "file") return;
|
|
988
|
-
const rows = await this.#
|
|
954
|
+
const rows = await this.#db.query("SELECT data FROM sqlar WHERE name = ?", [resolved.name]);
|
|
989
955
|
const content = await this.#content(resolved.name, resolved.stats.size, toBytes(rows[0]?.data));
|
|
990
956
|
options.signal?.throwIfAborted();
|
|
991
957
|
const start = options.start ?? 0;
|
|
@@ -1050,7 +1016,7 @@ var SqlarFilesApi = class {
|
|
|
1050
1016
|
if (resolved?.stats.kind !== "directory") return;
|
|
1051
1017
|
const base = resolved.name;
|
|
1052
1018
|
const range = descendants(base);
|
|
1053
|
-
const rows = await this.#
|
|
1019
|
+
const rows = await this.#db.query(`SELECT ${ROW_COLUMNS} FROM sqlar WHERE ${range.where} ORDER BY name`, range.params);
|
|
1054
1020
|
const prefixLength = base === "" ? 0 : base.length + 1;
|
|
1055
1021
|
const seen = /* @__PURE__ */ new Set();
|
|
1056
1022
|
for (const row of rows) {
|
|
@@ -1080,8 +1046,8 @@ var SqlarFilesApi = class {
|
|
|
1080
1046
|
}
|
|
1081
1047
|
async remove(path) {
|
|
1082
1048
|
const range = selfAndDescendants(toName(path));
|
|
1083
|
-
if ((await this.#
|
|
1084
|
-
await this.#
|
|
1049
|
+
if ((await this.#db.query(`SELECT 1 FROM sqlar WHERE ${range.where} LIMIT 1`, range.params)).length === 0) return false;
|
|
1050
|
+
await this.#db.query(`DELETE FROM sqlar WHERE ${range.where}`, range.params);
|
|
1085
1051
|
return true;
|
|
1086
1052
|
}
|
|
1087
1053
|
/**
|
|
@@ -1092,7 +1058,7 @@ var SqlarFilesApi = class {
|
|
|
1092
1058
|
const from = toName(source);
|
|
1093
1059
|
const to = toName(target);
|
|
1094
1060
|
const range = selfAndDescendants(from);
|
|
1095
|
-
const rows = await this.#
|
|
1061
|
+
const rows = await this.#db.query(`SELECT name, mode, sz, typeof(data) AS dtype, data FROM sqlar WHERE ${range.where}`, range.params);
|
|
1096
1062
|
if (rows.length === 0) return false;
|
|
1097
1063
|
await this.mkdir(parentName(to));
|
|
1098
1064
|
for (const row of rows) {
|
|
@@ -1146,16 +1112,22 @@ var SqlarFilesApi = class {
|
|
|
1146
1112
|
}
|
|
1147
1113
|
}
|
|
1148
1114
|
async #row(name) {
|
|
1149
|
-
return (await this.#
|
|
1115
|
+
return (await this.#db.query(`SELECT ${ROW_COLUMNS} FROM sqlar WHERE name = ?`, [name]))[0];
|
|
1150
1116
|
}
|
|
1151
1117
|
async #hasDescendants(name) {
|
|
1152
1118
|
const range = descendants(name);
|
|
1153
|
-
return (await this.#
|
|
1119
|
+
return (await this.#db.query(`SELECT 1 FROM sqlar WHERE ${range.where} LIMIT 1`, range.params)).length > 0;
|
|
1154
1120
|
}
|
|
1155
1121
|
async #put(name, mode, sz, data) {
|
|
1156
|
-
await this.#
|
|
1122
|
+
await this.#db.query(`INSERT INTO sqlar (name, mode, mtime, sz, data) VALUES (?, ?, ?, ?, ?)
|
|
1157
1123
|
ON CONFLICT(name) DO UPDATE SET
|
|
1158
|
-
mode = excluded.mode, mtime = excluded.mtime, sz = excluded.sz, data = excluded.data`,
|
|
1124
|
+
mode = excluded.mode, mtime = excluded.mtime, sz = excluded.sz, data = excluded.data`, [
|
|
1125
|
+
name,
|
|
1126
|
+
mode,
|
|
1127
|
+
Math.floor(Date.now() / 1e3),
|
|
1128
|
+
sz,
|
|
1129
|
+
data
|
|
1130
|
+
]);
|
|
1159
1131
|
}
|
|
1160
1132
|
};
|
|
1161
1133
|
const ROW_COLUMNS = "name, mode, mtime, sz, typeof(data) AS dtype, CASE WHEN sz = -1 THEN data END AS target";
|
|
@@ -1250,4 +1222,4 @@ async function concat(content) {
|
|
|
1250
1222
|
return out;
|
|
1251
1223
|
}
|
|
1252
1224
|
//#endregion
|
|
1253
|
-
export {
|
|
1225
|
+
export { SqlarFilesApi, SqliteFilesApi, defaultCodec, pakoCodec, pakoDeflateCodec, rawCodec, webCodec, webDeflateCodec };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export { SqliteFilesApi, type SqliteFilesApiOptions } from "./blocks/sqlite-files-api.js";
|
|
2
2
|
export { type PakoStreamModule, pakoDeflateCodec, type StreamCodec, webDeflateCodec, } from "./blocks/stream-codec.js";
|
|
3
3
|
export { type Codec, defaultCodec, type PakoModule, pakoCodec, rawCodec, webCodec, } from "./codec.js";
|
|
4
|
-
export { type D1Database, D1SqlDriver, DoSqlDriver, type DoStorage, NodeSqlDriver, type NodeSqliteDatabase, } from "./drivers.js";
|
|
5
|
-
export type { SqlDriver, SqlStatement } from "./sql.types.js";
|
|
6
4
|
export { SqlarFilesApi, type SqlarFilesApiOptions } from "./sqlar-files-api.js";
|
|
7
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EACL,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,KAAK,WAAW,EAChB,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,KAAK,EACV,YAAY,EACZ,KAAK,UAAU,EACf,SAAS,EACT,QAAQ,EACR,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EACL,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,KAAK,WAAW,EAChB,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,KAAK,EACV,YAAY,EACZ,KAAK,UAAU,EACf,SAAS,EACT,QAAQ,EACR,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Db } from "@statewalker/db-api";
|
|
2
|
+
/** One statement of a transaction. */
|
|
3
|
+
export interface SqlStatement {
|
|
4
|
+
sql: string;
|
|
5
|
+
params?: unknown[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Runs the statements in order on a transaction's Db and returns, per
|
|
9
|
+
* statement, the rows it changed — zero exactly when it changed nothing. The
|
|
10
|
+
* count is SQLite's `changes()` on the same connection, so index writes are
|
|
11
|
+
* not counted. Call it only inside `inTransaction`; it does nothing but SQL,
|
|
12
|
+
* so a retried transaction callback is safe.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runStatements(tx: Db, statements: SqlStatement[]): Promise<number[]>;
|
|
15
|
+
//# sourceMappingURL=run-statements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-statements.d.ts","sourceRoot":"","sources":["../src/run-statements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AAE9C,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQzF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { Db } from "@statewalker/db-api";
|
|
1
2
|
import type { FileInfo, FileStats, FilesApi, ListOptions, ReadOptions } from "@statewalker/webrun-files";
|
|
2
3
|
import { type Codec } from "./codec.js";
|
|
3
|
-
import type { SqlDriver } from "./sql.types.js";
|
|
4
4
|
export interface SqlarFilesApiOptions {
|
|
5
5
|
/** Defaults to {@link defaultCodec}. */
|
|
6
6
|
codec?: Codec;
|
|
@@ -14,7 +14,7 @@ export interface SqlarFilesApiOptions {
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class SqlarFilesApi implements FilesApi {
|
|
16
16
|
#private;
|
|
17
|
-
constructor(
|
|
17
|
+
constructor(db: Db, opts?: SqlarFilesApiOptions);
|
|
18
18
|
/** Creates the table if it is missing. Await it before any other call. */
|
|
19
19
|
init(): Promise<void>;
|
|
20
20
|
/** A missing path or a directory yields nothing; a file yields one chunk. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlar-files-api.d.ts","sourceRoot":"","sources":["../src/sqlar-files-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACZ,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAE,KAAK,KAAK,EAAgB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"sqlar-files-api.d.ts","sourceRoot":"","sources":["../src/sqlar-files-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACZ,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAE,KAAK,KAAK,EAAgB,MAAM,YAAY,CAAC;AAuBtD,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;;;;;GAMG;AACH,qBAAa,aAAc,YAAW,QAAQ;;IAI5C,YAAY,EAAE,EAAE,EAAE,EAAE,IAAI,GAAE,oBAAyB,EAGlD;IAED,0EAA0E;IACpE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1B;IAED,6EAA6E;IACtE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,aAAa,CAAC,UAAU,CAAC,CAc9E;IAEK,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GACxD,OAAO,CAAC,IAAI,CAAC,CAYf;IAED,gFAAgF;IAC1E,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvC;IAED,oEAAoE;IAC9D,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzD;IAED,wEAAwE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAGxD;IAEK,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAExD;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE3C;IAED;;;;;OAKG;IACH;;;;;OAKG;IACI,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,aAAa,CAAC,QAAQ,CAAC,CAO5E;IAwCK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAU3C;IAED;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAsB3D;IAEK,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAI3D;CAoEF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statewalker/webrun-files-sqlite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "SQLite-backed FilesApi implementations",
|
|
@@ -34,9 +34,13 @@
|
|
|
34
34
|
"src"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@statewalker/
|
|
37
|
+
"@statewalker/db-api": "^0.1.3",
|
|
38
|
+
"@statewalker/db-sqlite": "^0.1.0",
|
|
39
|
+
"@statewalker/webrun-files": "^0.10.3"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
42
|
+
"@statewalker/db-sqlite-do": "^0.1.0",
|
|
43
|
+
"@statewalker/db-sqlite-node": "^0.3.0",
|
|
40
44
|
"@types/node": "^26.6.4",
|
|
41
45
|
"pako": "^3.0.2",
|
|
42
46
|
"rimraf": "^6.1.3",
|