@poe-platform/safe-bash 0.1.824 → 0.1.825

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 (32) hide show
  1. package/dist/safe-bash/command-chunks/{chunk-PLVFJ6FF.js → chunk-4EA7WI3L.js} +9 -1
  2. package/dist/safe-bash/command-chunks/{chunk-3UDJYSAF.js → chunk-IX3B4HCD.js} +34 -2
  3. package/dist/safe-bash/command-chunks/{chunk-NNGWTDVJ.js → chunk-LFTGMUAV.js} +24 -81
  4. package/dist/safe-bash/command-chunks/{chunk-IKBTII7C.js → chunk-RZOHZGEZ.js} +126 -13
  5. package/dist/safe-bash/command-chunks/{chunk-3EBQOZET.js → chunk-WY6THI6J.js} +2 -2
  6. package/dist/safe-bash/command-chunks/{chunk-NW3S6T6R.js → chunk-XSZNL3GV.js} +1 -1
  7. package/dist/safe-bash/commands/llm/collections.browser.js +4 -2
  8. package/dist/safe-bash/commands/llm/collections.js +1 -1
  9. package/dist/safe-bash-command-llm/collections.d.ts +2 -0
  10. package/dist/safe-bash-command-llm/collections.js +146 -22
  11. package/dist/safe-bash-command-llm/command.js +1 -1
  12. package/dist/safe-bash-command-llm/import-sql.d.ts +9 -0
  13. package/dist/safe-bash-command-llm/index.js +1 -1
  14. package/dist/safe-bash-command-python/build-wheel.d.ts +11 -2
  15. package/dist/safe-bash-command-python/executor.js +5 -5
  16. package/dist/safe-bash-command-python/index.js +5 -5
  17. package/dist/safe-bash-command-python/jspi.js +2 -2
  18. package/dist/safe-bash-command-python/provisioning.d.ts +1 -1
  19. package/dist/safe-bash-command-python/provisioning.js +1 -1
  20. package/dist/safe-bash-command-python/worker.js +1 -1
  21. package/dist/safe-bash-sqlite-engine/safe-fs.d.ts +6 -0
  22. package/dist/safe-bash-sqlite-engine/safe-fs.js +1 -1
  23. package/dist/safe-bash-sqlite-engine/sqlite-attachments.d.ts +5 -0
  24. package/dist/safe-bash-sqlite-engine/sqlite-input-snapshots.d.ts +20 -0
  25. package/dist/safe-bash-sqlite-engine/sqlite-query-records.d.ts +9 -0
  26. package/dist/safe-bash-sqlite-engine/sqlite-read-session.d.ts +3 -16
  27. package/dist/safe-bash-sqlite-engine/sqlite-session.d.ts +4 -0
  28. package/dist/safe-bash-sqlite-engine/sqlite-statement.d.ts +4 -0
  29. package/dist/safe-bash-sqlite-engine/storage.d.ts +2 -0
  30. package/dist/safe-bash-sqlite-engine/storage.js +324 -31
  31. package/dist/safe-bash-sqlite-engine/types.d.ts +1 -0
  32. package/package.json +2 -2
@@ -9,3 +9,5 @@ export { readSqliteRecord } from './sqlite-record-read.js';
9
9
  export type { SqliteEditSnapshot } from './sqlite-edit-snapshot.js';
10
10
  export type { SqliteFinalizer } from './sqlite-finalization.js';
11
11
  export { withSqliteReadSession } from './sqlite-read-session.js';
12
+ export { withSqliteQueryRecords } from './sqlite-query-records.js';
13
+ export { prepareSqliteAttachments } from './sqlite-attachments.js';
@@ -2,7 +2,7 @@ import {
2
2
  createSqliteVfs,
3
3
  readSqliteFile,
4
4
  writeSqliteFile
5
- } from "../safe-bash/command-chunks/chunk-3UDJYSAF.js";
5
+ } from "../safe-bash/command-chunks/chunk-IX3B4HCD.js";
6
6
  import "../safe-bash/command-chunks/chunk-65DS4IOB.js";
7
7
 
8
8
  // packages/safe-bash-sqlite-engine/src/sqlite-transaction.ts
@@ -21,7 +21,7 @@ function verifySqliteSnapshot(actual, expected) {
21
21
  }
22
22
 
23
23
  // packages/safe-bash-sqlite-engine/src/sqlite-sources.ts
24
- async function acquireSqliteSources(fs, path, signal) {
24
+ async function acquireSqliteSources(fs, path, signal, options = {}) {
25
25
  signal.throwIfAborted();
26
26
  const resolutions = [];
27
27
  const handles = [];
@@ -39,17 +39,18 @@ async function acquireSqliteSources(fs, path, signal) {
39
39
  signal.throwIfAborted();
40
40
  return true;
41
41
  };
42
- const resolve = async (sourcePath) => {
43
- const capabilities = await fs.capabilitiesFor?.(sourcePath, { signal, stagingResolution: true }) ?? fs.capabilities;
44
- if (capabilities.synchronousStagingResolution !== true || capabilities.retainedRead !== true || !fs.prepareStagingResolution || !fs.openReadFile) {
42
+ const resolve = async (sourcePath, followFinalSymlink = false) => {
43
+ const capabilities = await fs.capabilitiesFor?.(sourcePath, { signal, stagingResolution: true, followFinalSymlink }) ?? fs.capabilities;
44
+ if (capabilities.synchronousStagingResolution !== true || followFinalSymlink && capabilities.synchronousFollowedStagingResolution !== true || capabilities.retainedRead !== true || !fs.prepareStagingResolution || !fs.openReadFile) {
45
45
  throw new FsError2("ENOTSUP", { path: sourcePath, message: "SQLite source acquisition requires retained reads and synchronous binding guards" });
46
46
  }
47
- const resolution = await fs.prepareStagingResolution(sourcePath, { signal });
47
+ const resolution = await fs.prepareStagingResolution(sourcePath, { signal, followFinalSymlink });
48
48
  resolutions.push(resolution);
49
49
  return resolution;
50
50
  };
51
51
  try {
52
- const database = await resolve(path);
52
+ const follow = options.followFinalSymlink === true && (await fs.lstat(path, { signal })).type === "symlink";
53
+ const database = await resolve(path, follow);
53
54
  for (const suffix of ["wal", "journal", "shm"]) await resolve(`${database.path}-${suffix}`);
54
55
  validate();
55
56
  const sources = [];
@@ -66,9 +67,9 @@ async function acquireSqliteSources(fs, path, signal) {
66
67
  validate();
67
68
  const controls = (operationSignal) => ({ signal: operationSignal ? AbortSignal.any([signal, operationSignal]) : signal });
68
69
  const file = {
69
- async stat(options) {
70
+ async stat(options2) {
70
71
  validate();
71
- const operation = controls(options?.signal);
72
+ const operation = controls(options2?.signal);
72
73
  operation.signal.throwIfAborted();
73
74
  const stat = await handle.stat(operation);
74
75
  operation.signal.throwIfAborted();
@@ -76,11 +77,11 @@ async function acquireSqliteSources(fs, path, signal) {
76
77
  validate();
77
78
  return stat;
78
79
  },
79
- async read(position, maxBytes, options) {
80
+ async read(position, maxBytes, options2) {
80
81
  validate();
81
82
  if (!Number.isSafeInteger(position) || position < 0 || !Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new RangeError("Invalid SQLite source read");
82
83
  const count = Math.min(maxBytes, 16384);
83
- const operation = controls(options?.signal);
84
+ const operation = controls(options2?.signal);
84
85
  operation.signal.throwIfAborted();
85
86
  const bytes = await handle.read(position, count, operation);
86
87
  operation.signal.throwIfAborted();
@@ -379,7 +380,7 @@ async function withPrivateSqliteSession(options, operation) {
379
380
  const { createSqliteRuntime, FacadeVFS } = await import("./index.js");
380
381
  const runtime = await createSqliteRuntime({ signal });
381
382
  const vfs = "sqlite-private";
382
- const mxPathname = Math.max(4096, new TextEncoder().encode(path).length + 256);
383
+ const mxPathname = Math.max(4096, new TextEncoder().encode(path).length + 256, ...(options.filenames ?? []).map((file) => new TextEncoder().encode(file.name).length + 256));
383
384
  const code = runtime.module.vfs_register(Object.assign(new FacadeVFS(vfs, runtime.module), callbacks, { mxPathname }), true);
384
385
  if (code !== 0) throw new FsError5("EIO", { message: `SQLite VFS registration failed (${code})` });
385
386
  value = await withSqliteDatabase(runtime.module, { path, vfs, signal, check: callbacks.throwIfFailed }, (connection) => operation({ ...runtime, ...connection, check: callbacks.throwIfFailed }));
@@ -1258,9 +1259,20 @@ async function withSqliteStatement(module, options, operation) {
1258
1259
  const bindText = module.cwrap("sqlite3_bind_text", "number", ["number", "number", "number", "number", "number"]);
1259
1260
  const result = (code) => {
1260
1261
  check();
1261
- if (code !== 0) throw new FsError15("EIO", { message: `Native SQLite statement failed (${code})` });
1262
+ if (code === 0) return;
1263
+ let message = `Native SQLite statement failed (${code})`;
1264
+ if (options.single) {
1265
+ const errorMessage = module.cwrap("sqlite3_errmsg", "number", ["number"]);
1266
+ const pointer = errorMessage(database);
1267
+ let end = pointer;
1268
+ if (pointer > 0 && pointer < module.HEAPU8.length) {
1269
+ while (end < module.HEAPU8.length && end - pointer < 65536 && module.HEAPU8[end]) end++;
1270
+ if (end < module.HEAPU8.length && module.HEAPU8[end] === 0) message = decoder.decode(module.HEAPU8.subarray(pointer, end));
1271
+ }
1272
+ }
1273
+ throw new FsError15("EIO", { message });
1262
1274
  };
1263
- let out = 0, statement = 0, accepting = true, current, value;
1275
+ let out = 0, sqlPointer = 0, statement = 0, accepting = true, current, value;
1264
1276
  const errors = [];
1265
1277
  const resetCursor = async (failed, failure) => {
1266
1278
  try {
@@ -1358,16 +1370,126 @@ async function withSqliteStatement(module, options, operation) {
1358
1370
  const iterator = iterate();
1359
1371
  return iterator;
1360
1372
  };
1373
+ const records = () => {
1374
+ async function* iterate() {
1375
+ if (!accepting) throw new FsError15("EBADF", { message: "SQLite statement is closed" });
1376
+ if (current) throw new FsError15("EBUSY", { message: "SQLite cursor is already active" });
1377
+ current = iterator;
1378
+ let failed = false, failure;
1379
+ try {
1380
+ const readonly = module.cwrap("sqlite3_stmt_readonly", "number", ["number"]);
1381
+ if (!readonly(statement) || parameters(statement)) throw new TypeError("Expected an unbound read-only SQLite query");
1382
+ const size = count(statement);
1383
+ if (size > 2e3) throw new RangeError("SQLite record exceeds column budget");
1384
+ while (true) {
1385
+ await yieldTurn4(signal);
1386
+ check();
1387
+ const code = await step(statement);
1388
+ check();
1389
+ signal.throwIfAborted();
1390
+ if (code === 101) return;
1391
+ if (code !== 100) {
1392
+ result(code);
1393
+ return;
1394
+ }
1395
+ let active = true;
1396
+ try {
1397
+ const values = [];
1398
+ for (let column = 0; column < size; column++) {
1399
+ const kind = type(statement, column);
1400
+ if (kind === 5) {
1401
+ values.push(null);
1402
+ continue;
1403
+ }
1404
+ if (kind === 2) {
1405
+ values.push(real(statement, column));
1406
+ continue;
1407
+ }
1408
+ const pointer = kind === 4 ? blob(statement, column) : text(statement, column);
1409
+ const bytes = length(statement, column);
1410
+ if (bytes < 0 || bytes && (!pointer || pointer + bytes > module.HEAPU8.length)) throw new RangeError("Invalid SQLite field address");
1411
+ if (kind === 1) {
1412
+ if (bytes > 20) throw new RangeError("Invalid SQLite integer");
1413
+ values.push(BigInt(decoder.decode(module.HEAPU8.subarray(pointer, pointer + bytes))));
1414
+ continue;
1415
+ }
1416
+ if (kind !== 3 && kind !== 4) throw new TypeError("Unexpected SQLite field type");
1417
+ values.push({ type: kind === 3 ? "text" : "blob", size: bytes, bytes: { async *[Symbol.asyncIterator]() {
1418
+ const ready = () => {
1419
+ signal.throwIfAborted();
1420
+ check();
1421
+ if (!active || !accepting) throw new FsError15("EBADF", { message: "SQLite row lease is closed" });
1422
+ };
1423
+ ready();
1424
+ for (let offset = 0; offset < bytes; offset += 16384) {
1425
+ await yieldTurn4(signal);
1426
+ ready();
1427
+ yield module.HEAPU8.slice(pointer + offset, pointer + Math.min(offset + 16384, bytes));
1428
+ }
1429
+ } } });
1430
+ }
1431
+ yield values;
1432
+ } finally {
1433
+ active = false;
1434
+ }
1435
+ }
1436
+ } catch (error) {
1437
+ failed = true;
1438
+ failure = error;
1439
+ throw error;
1440
+ } finally {
1441
+ await resetCursor(failed, failure);
1442
+ }
1443
+ }
1444
+ const iterator = iterate();
1445
+ return iterator;
1446
+ };
1361
1447
  try {
1362
- out = module._malloc(4);
1448
+ out = module._malloc(options.single ? 8 : 4);
1363
1449
  if (!out) throw new RangeError("SQLite memory allocation failed");
1364
1450
  new DataView(module.HEAPU8.buffer).setInt32(out, 0, true);
1365
- const code = await prepare(database, sql, -1, out, 0);
1366
- statement = new DataView(module.HEAPU8.buffer).getInt32(out, true);
1451
+ let code;
1452
+ if (options.single) {
1453
+ const bytes = encoder.encode(sql);
1454
+ sqlPointer = module._malloc(bytes.length + 1);
1455
+ if (!sqlPointer) throw new RangeError("SQLite memory allocation failed");
1456
+ module.HEAPU8.set(bytes, sqlPointer);
1457
+ module.HEAPU8[sqlPointer + bytes.length] = 0;
1458
+ new DataView(module.HEAPU8.buffer).setInt32(out + 4, 0, true);
1459
+ const ownedPrepare = module.cwrap("sqlite3_prepare_v2", "number", ["number", "number", "number", "number", "number"], { async: true });
1460
+ code = await ownedPrepare(database, sqlPointer, bytes.length + 1, out, out + 4);
1461
+ statement = new DataView(module.HEAPU8.buffer).getInt32(out, true);
1462
+ result(code);
1463
+ const tail = new DataView(module.HEAPU8.buffer).getInt32(out + 4, true);
1464
+ if (tail < sqlPointer || tail > sqlPointer + bytes.length) throw new RangeError("Invalid SQLite statement tail");
1465
+ for (let position = tail; position < sqlPointer + bytes.length; position++) if (![9, 10, 11, 12, 13, 32].includes(module.HEAPU8[position])) throw new TypeError("Expected a single SQLite statement");
1466
+ } else {
1467
+ code = await prepare(database, sql, -1, out, 0);
1468
+ statement = new DataView(module.HEAPU8.buffer).getInt32(out, true);
1469
+ }
1367
1470
  result(code);
1368
1471
  signal.throwIfAborted();
1369
1472
  if (!statement) throw new FsError15("EIO", { message: "SQLite returned an empty statement" });
1370
- value = await operation({ rows });
1473
+ value = await operation({ rows, records, columns() {
1474
+ if (!accepting) throw new FsError15("EBADF", { message: "SQLite statement is closed" });
1475
+ signal.throwIfAborted();
1476
+ check();
1477
+ const names = [], size = count(statement);
1478
+ const name = module.cwrap("sqlite3_column_name", "number", ["number", "number"]);
1479
+ let budget = 65536;
1480
+ for (let index = 0; index < size; index++) {
1481
+ const pointer = name(statement, index);
1482
+ let end = pointer;
1483
+ if (pointer <= 0 || pointer >= module.HEAPU8.length) throw new RangeError("Invalid SQLite column name");
1484
+ while (end < module.HEAPU8.length && module.HEAPU8[end]) {
1485
+ if (--budget < 0) throw new RangeError("SQLite column names exceed byte budget");
1486
+ end++;
1487
+ }
1488
+ if (end === module.HEAPU8.length) throw new RangeError("Invalid SQLite column name");
1489
+ names.push(decoder.decode(module.HEAPU8.subarray(pointer, end)));
1490
+ }
1491
+ return names;
1492
+ } });
1371
1493
  } catch (error) {
1372
1494
  errors.push(error);
1373
1495
  }
@@ -1384,6 +1506,11 @@ async function withSqliteStatement(module, options, operation) {
1384
1506
  } catch (error) {
1385
1507
  if (!errors.includes(error)) errors.push(error);
1386
1508
  }
1509
+ if (sqlPointer) try {
1510
+ module._free(sqlPointer);
1511
+ } catch (error) {
1512
+ errors.push(error);
1513
+ }
1387
1514
  if (out) try {
1388
1515
  module._free(out);
1389
1516
  } catch (error) {
@@ -1677,9 +1804,9 @@ async function readSqliteRecord(record, options) {
1677
1804
  return values;
1678
1805
  }
1679
1806
 
1680
- // packages/safe-bash-sqlite-engine/src/sqlite-read-session.ts
1807
+ // packages/safe-bash-sqlite-engine/src/sqlite-input-snapshots.ts
1681
1808
  import { FsError as FsError19 } from "../safe-bash-contracts/index.js";
1682
- async function withSqliteReadSession(options, operation) {
1809
+ async function withSqliteInputSnapshots(options, operation) {
1683
1810
  const { fs, signal } = options;
1684
1811
  for (const limit of [options.maxFileBytes, options.maxIndexBytes, options.maxOpenFiles]) if (!Number.isSafeInteger(limit) || limit < 0) throw new RangeError("Invalid SQLite snapshot budget");
1685
1812
  const inputs = [{ path: options.path, alias: "main" }, ...(options.attachments ?? []).map((input) => ({ ...input }))];
@@ -1689,24 +1816,16 @@ async function withSqliteReadSession(options, operation) {
1689
1816
  let value;
1690
1817
  try {
1691
1818
  for (const [index, input] of inputs.entries()) {
1692
- const sources = await acquireSqliteSources(fs, input.path, signal);
1819
+ const sources = await acquireSqliteSources(fs, input.path, signal, { followFinalSymlink: true });
1693
1820
  try {
1694
1821
  if (!sources.database) throw new FsError19("ENOENT", { path: input.path });
1695
1822
  await copySqliteSnapshot({ ...options, sources, storage, path: `${storage.directory}/database-${index}` });
1823
+ input.path = sources.path;
1696
1824
  } finally {
1697
1825
  await sources.close();
1698
1826
  }
1699
1827
  }
1700
- value = await withPrivateSqliteSession({ ...options, fs: storage.fs, directory: storage.directory, path: storage.directory + "/database-0" }, async (session) => {
1701
- for (let index = 1; index < inputs.length; index++) {
1702
- const alias = inputs[index].alias;
1703
- await withSqliteStatement(session.module, { ...session, signal, sql: 'ATTACH DATABASE ? AS "' + alias.replaceAll('"', '""') + '"' }, async (statement) => {
1704
- for await (const ignored of statement.rows([`${storage.directory}/database-${index}`], [])) signal.throwIfAborted();
1705
- });
1706
- }
1707
- await session.execute("PRAGMA query_only=ON");
1708
- return operation(session);
1709
- });
1828
+ value = await operation(storage, inputs);
1710
1829
  } catch (error) {
1711
1830
  errors.push(error);
1712
1831
  }
@@ -1719,13 +1838,187 @@ async function withSqliteReadSession(options, operation) {
1719
1838
  if (errors.length) throw new AggregateError(errors, "SQLite read session and cleanup failed");
1720
1839
  return value;
1721
1840
  }
1841
+
1842
+ // packages/safe-bash-sqlite-engine/src/sqlite-read-session.ts
1843
+ async function withSqliteReadSession(options, operation) {
1844
+ const { signal } = options;
1845
+ return withSqliteInputSnapshots(options, async (storage, inputs) => {
1846
+ return withPrivateSqliteSession({ ...options, fs: storage.fs, directory: storage.directory, path: storage.directory + "/database-0", filenames: inputs.map((input, index) => ({ path: storage.directory + "/database-" + index, name: input.path })) }, async (session) => {
1847
+ await session.execute("PRAGMA query_only=ON");
1848
+ for (let index = 1; index < inputs.length; index++) {
1849
+ const alias = inputs[index].alias;
1850
+ await withSqliteStatement(session.module, { ...session, signal, sql: 'ATTACH DATABASE ? AS "' + alias.replaceAll('"', '""') + '"' }, async (statement) => {
1851
+ for await (const ignored of statement.rows([`${storage.directory}/database-${index}`], [])) signal.throwIfAborted();
1852
+ });
1853
+ }
1854
+ return operation(session);
1855
+ });
1856
+ });
1857
+ }
1858
+
1859
+ // packages/safe-bash-sqlite-engine/src/sqlite-query-records.ts
1860
+ import { yieldTurn as yieldTurn8 } from "../safe-bash-contracts/yield.js";
1861
+ import { FsError as FsError20 } from "../safe-bash-contracts/index.js";
1862
+ async function withSqliteQueryRecords(options, operation) {
1863
+ const { signal } = options;
1864
+ return withSqliteInputSnapshots(options, async (storage, inputs) => {
1865
+ const file = await storage.fs.open(storage.directory + "/results", { access: "readwrite", creation: "exclusive", signal });
1866
+ let accepting = true, size = 0, iterator, value;
1867
+ const errors = [];
1868
+ const check = () => {
1869
+ signal.throwIfAborted();
1870
+ if (!accepting) throw new FsError20("EBADF", { message: "SQLite query results are closed" });
1871
+ };
1872
+ try {
1873
+ let columns = [];
1874
+ await withPrivateSqliteSession({ ...options, fs: storage.fs, directory: storage.directory, path: storage.directory + "/database-0", filenames: inputs.map((input, index) => ({ path: storage.directory + "/database-" + index, name: input.path })) }, async (session) => {
1875
+ await session.execute("PRAGMA query_only=ON");
1876
+ for (let index = 1; index < inputs.length; index++) await withSqliteStatement(session.module, { ...session, signal, sql: 'ATTACH DATABASE ? AS "' + inputs[index].alias.replaceAll('"', '""') + '"' }, async (statement) => {
1877
+ for await (const ignored of statement.rows([storage.directory + "/database-" + index], [])) signal.throwIfAborted();
1878
+ });
1879
+ let progressFailure;
1880
+ session.module.progress_handler(session.database, 1e3, async () => {
1881
+ try {
1882
+ await yieldTurn8(signal);
1883
+ return 0;
1884
+ } catch (error) {
1885
+ progressFailure = error;
1886
+ return 1;
1887
+ }
1888
+ }, 0);
1889
+ try {
1890
+ columns = await withSqliteStatement(session.module, { ...session, signal, single: true, sql: options.sql }, async (statement) => statement.columns());
1891
+ await withSqliteStatement(session.module, { ...session, signal, single: true, sql: "SELECT * FROM (" + options.sql + ")" }, async (statement) => {
1892
+ for await (const row of statement.records()) {
1893
+ const record = sqliteRecord(row), end = size + 8 + record.size;
1894
+ if (!Number.isSafeInteger(end) || end > options.maxFileBytes) throw new FsError20("EFBIG", { message: "SQLite query results exceed file budget" });
1895
+ const header = new Uint8Array(8);
1896
+ new DataView(header.buffer).setBigUint64(0, BigInt(record.size));
1897
+ await writeSqliteFile(file, header, size, signal);
1898
+ size += 8;
1899
+ for await (const bytes of record.bytes(signal)) {
1900
+ await writeSqliteFile(file, bytes, size, signal);
1901
+ size += bytes.length;
1902
+ }
1903
+ if (size !== end) throw new FsError20("EIO", { message: "Invalid SQLite query record size" });
1904
+ }
1905
+ });
1906
+ } catch (error) {
1907
+ if (progressFailure !== void 0) throw progressFailure;
1908
+ throw error;
1909
+ } finally {
1910
+ session.module.progress_handler(session.database, 0, null, 0);
1911
+ }
1912
+ });
1913
+ const snapshot = await file.stat({ signal });
1914
+ verifySqliteSnapshot(snapshot, snapshot);
1915
+ const read = async (bytes, position) => {
1916
+ check();
1917
+ verifySqliteSnapshot(await file.stat({ signal }), snapshot);
1918
+ const complete = await readSqliteFile(file, bytes, position, signal);
1919
+ check();
1920
+ verifySqliteSnapshot(await file.stat({ signal }), snapshot);
1921
+ if (!complete) throw new FsError20("EIO", { message: "Truncated SQLite query record" });
1922
+ };
1923
+ const rows = async function* () {
1924
+ for (let position = 0; position < size; ) {
1925
+ check();
1926
+ const header = new Uint8Array(8);
1927
+ await read(header, position);
1928
+ const length = Number(new DataView(header.buffer).getBigUint64(0)), start = position + 8;
1929
+ if (!Number.isSafeInteger(length) || length < 1 || length > size - start) throw new FsError20("EIO", { message: "Invalid SQLite query record size" });
1930
+ yield await readSqliteRecord({ size: length, bytes(offset = 0, count = length - offset) {
1931
+ return { async *[Symbol.asyncIterator]() {
1932
+ check();
1933
+ if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(count) || offset < 0 || count < 0 || offset + count > length) throw new RangeError("Invalid SQLite query record range");
1934
+ for (let done = 0; done < count; ) {
1935
+ check();
1936
+ const bytes = new Uint8Array(Math.min(16384, count - done));
1937
+ await read(bytes, start + offset + done);
1938
+ check();
1939
+ done += bytes.length;
1940
+ yield bytes;
1941
+ }
1942
+ } };
1943
+ } }, { signal, maxColumns: 2e3 });
1944
+ position = start + length;
1945
+ }
1946
+ };
1947
+ iterator = rows();
1948
+ value = await operation({ [Symbol.asyncIterator]: () => iterator }, columns);
1949
+ } catch (error) {
1950
+ errors.push(error);
1951
+ }
1952
+ accepting = false;
1953
+ try {
1954
+ await iterator?.return(void 0);
1955
+ } catch (error) {
1956
+ if (!errors.includes(error)) errors.push(error);
1957
+ }
1958
+ try {
1959
+ await file.close();
1960
+ } catch (error) {
1961
+ errors.push(error);
1962
+ }
1963
+ if (errors.length === 1) throw errors[0];
1964
+ if (errors.length) throw new AggregateError(errors, "SQLite query result cleanup failed");
1965
+ return value;
1966
+ });
1967
+ }
1968
+
1969
+ // packages/safe-bash-sqlite-engine/src/sqlite-attachments.ts
1970
+ import { FsError as FsError21 } from "../safe-bash-contracts/index.js";
1971
+ async function prepareSqliteAttachments(options) {
1972
+ const { fs, signal } = options, attachments = options.attachments?.map((input) => ({ ...input })) ?? [];
1973
+ if (!attachments.length) return;
1974
+ if (!fs.open) throw new FsError21("ENOSYS", { message: "SQLite attachments require descriptor storage" });
1975
+ const ensure = async (path) => {
1976
+ try {
1977
+ await fs.stat(path, { signal });
1978
+ return;
1979
+ } catch (error) {
1980
+ if (!(error instanceof FsError21) || error.code !== "ENOENT") throw error;
1981
+ }
1982
+ const file = await fs.open(path, { access: "readwrite", creation: "ifMissing", signal });
1983
+ await file.close();
1984
+ };
1985
+ await ensure(options.path);
1986
+ await withSqliteInputSnapshots({ ...options, attachments: [] }, async (storage) => {
1987
+ await withPrivateSqliteSession({ ...options, fs: storage.fs, directory: storage.directory, path: storage.directory + "/database-0" }, async (session) => {
1988
+ const execute = async (sql, bindings = []) => withSqliteStatement(session.module, { ...session, signal, sql, single: true }, async (statement) => {
1989
+ for await (const ignored of statement.rows(bindings, [])) signal.throwIfAborted();
1990
+ });
1991
+ for (const [index, input] of attachments.entries()) {
1992
+ await execute("ATTACH DATABASE ? AS [" + input.alias + "]", [":memory:"]);
1993
+ try {
1994
+ await ensure(input.path);
1995
+ } catch (error) {
1996
+ signal.throwIfAborted();
1997
+ if (error instanceof FsError21 && ["ENOENT", "ENOTDIR", "EACCES", "EISDIR"].includes(error.code)) throw new FsError21("EIO", { message: "unable to open database: " + input.path });
1998
+ throw error;
1999
+ }
2000
+ const sources = await acquireSqliteSources(fs, input.path, signal, { followFinalSymlink: true });
2001
+ const path = storage.directory + "/database-" + (index + 1);
2002
+ try {
2003
+ await copySqliteSnapshot({ ...options, sources, storage, path });
2004
+ } finally {
2005
+ await sources.close();
2006
+ }
2007
+ await execute("DETACH DATABASE [" + input.alias + "]");
2008
+ await execute("ATTACH DATABASE ? AS [" + input.alias + "]", [path]);
2009
+ }
2010
+ });
2011
+ });
2012
+ }
1722
2013
  export {
1723
2014
  createPrivateSqliteStorage,
2015
+ prepareSqliteAttachments,
1724
2016
  readSqliteBlob,
1725
2017
  readSqliteRecord,
1726
2018
  sqliteRecord,
1727
2019
  transactSqlite,
1728
2020
  withPrivateSqliteSession,
2021
+ withSqliteQueryRecords,
1729
2022
  withSqliteReadSession,
1730
2023
  withSqliteStatement,
1731
2024
  writeSqliteBlob
@@ -5,6 +5,7 @@ export interface NativeSqliteModule {
5
5
  cwrap(name: string, result: string, arguments_: string[], options?: {
6
6
  async: boolean;
7
7
  }): unknown;
8
+ progress_handler(database: number, operations: number, callback: (() => number | Promise<number>) | null, context: number): void;
8
9
  vfs_register(vfs: object, makeDefault: boolean): number;
9
10
  }
10
11
  export interface SqliteRuntime {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-bash",
3
- "version": "0.1.824",
3
+ "version": "0.1.825",
4
4
  "description": "Extensible virtual shell with pluggable filesystems and streaming commands",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1415,7 +1415,7 @@
1415
1415
  "access": "public"
1416
1416
  },
1417
1417
  "dependencies": {
1418
- "@poe-platform/safe-fs": "0.1.824",
1418
+ "@poe-platform/safe-fs": "0.1.825",
1419
1419
  "pdf-lib": "1.17.1",
1420
1420
  "@types/node": "^25.2.2",
1421
1421
  "@formatjs/intl-numberformat": "9.4.0",