@livestore/sqlite-wasm 0.4.0-dev.2 → 0.4.0-dev.5

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 (74) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser/mod.d.ts +1 -0
  3. package/dist/browser/mod.d.ts.map +1 -1
  4. package/dist/browser/mod.js.map +1 -1
  5. package/dist/cf/BlockManager.d.ts +61 -0
  6. package/dist/cf/BlockManager.d.ts.map +1 -0
  7. package/dist/cf/BlockManager.js +157 -0
  8. package/dist/cf/BlockManager.js.map +1 -0
  9. package/dist/cf/CloudflareSqlVFS.d.ts +51 -0
  10. package/dist/cf/CloudflareSqlVFS.d.ts.map +1 -0
  11. package/dist/cf/CloudflareSqlVFS.js +351 -0
  12. package/dist/cf/CloudflareSqlVFS.js.map +1 -0
  13. package/dist/cf/CloudflareWorkerVFS.d.ts +72 -0
  14. package/dist/cf/CloudflareWorkerVFS.d.ts.map +1 -0
  15. package/dist/cf/CloudflareWorkerVFS.js +552 -0
  16. package/dist/cf/CloudflareWorkerVFS.js.map +1 -0
  17. package/dist/cf/mod.d.ts +43 -0
  18. package/dist/cf/mod.d.ts.map +1 -0
  19. package/dist/cf/mod.js +74 -0
  20. package/dist/cf/mod.js.map +1 -0
  21. package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.d.ts +2 -0
  22. package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.d.ts.map +1 -0
  23. package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.js +314 -0
  24. package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.js.map +1 -0
  25. package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.d.ts +2 -0
  26. package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.d.ts.map +1 -0
  27. package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.js +266 -0
  28. package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.js.map +1 -0
  29. package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.d.ts +2 -0
  30. package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.d.ts.map +1 -0
  31. package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.js +444 -0
  32. package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.js.map +1 -0
  33. package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.d.ts +2 -0
  34. package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.d.ts.map +1 -0
  35. package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.js +334 -0
  36. package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.js.map +1 -0
  37. package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.d.ts +2 -0
  38. package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.d.ts.map +1 -0
  39. package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.js +354 -0
  40. package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.js.map +1 -0
  41. package/dist/load-wasm/mod.node.d.ts.map +1 -1
  42. package/dist/load-wasm/mod.node.js +1 -2
  43. package/dist/load-wasm/mod.node.js.map +1 -1
  44. package/dist/load-wasm/mod.workerd.d.ts +2 -0
  45. package/dist/load-wasm/mod.workerd.d.ts.map +1 -0
  46. package/dist/load-wasm/mod.workerd.js +26 -0
  47. package/dist/load-wasm/mod.workerd.js.map +1 -0
  48. package/dist/make-sqlite-db.d.ts +1 -0
  49. package/dist/make-sqlite-db.d.ts.map +1 -1
  50. package/dist/make-sqlite-db.js.map +1 -1
  51. package/dist/node/NodeFS.d.ts +1 -2
  52. package/dist/node/NodeFS.d.ts.map +1 -1
  53. package/dist/node/NodeFS.js +1 -6
  54. package/dist/node/NodeFS.js.map +1 -1
  55. package/dist/node/mod.js +3 -8
  56. package/dist/node/mod.js.map +1 -1
  57. package/package.json +20 -7
  58. package/src/browser/mod.ts +1 -0
  59. package/src/cf/BlockManager.ts +225 -0
  60. package/src/cf/CloudflareSqlVFS.ts +450 -0
  61. package/src/cf/CloudflareWorkerVFS.ts +664 -0
  62. package/src/cf/README.md +60 -0
  63. package/src/cf/mod.ts +143 -0
  64. package/src/cf/test/README.md +224 -0
  65. package/src/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.ts +389 -0
  66. package/src/cf/test/async-storage/cloudflare-worker-vfs-core.test.ts +322 -0
  67. package/src/cf/test/async-storage/cloudflare-worker-vfs-integration.test.ts +567 -0
  68. package/src/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.ts +403 -0
  69. package/src/cf/test/sql/cloudflare-sql-vfs-core.test.ts +433 -0
  70. package/src/load-wasm/mod.node.ts +1 -2
  71. package/src/load-wasm/mod.workerd.ts +26 -0
  72. package/src/make-sqlite-db.ts +1 -0
  73. package/src/node/NodeFS.ts +1 -9
  74. package/src/node/mod.ts +3 -10
@@ -1,4 +1,5 @@
1
1
  import type { MakeSqliteDb, PersistenceInfo, SqliteDb } from '@livestore/common';
2
+ import type { SQLiteAPI } from '@livestore/wa-sqlite';
2
3
  import type { MemoryVFS } from '@livestore/wa-sqlite/src/examples/MemoryVFS.js';
3
4
  import type { AccessHandlePoolVFS } from './opfs/AccessHandlePoolVFS.ts';
4
5
  export * from './opfs/opfs-sah-pool.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/browser/mod.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAEhF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gDAAgD,CAAA;AAI/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAGxE,cAAc,yBAAyB,CAAA;AAEvC,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,WAAW,CAAA;IACjB,GAAG,EAAE,SAAS,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,eAAe,CAAA;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,WAAW,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,IAAI,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,mBAAmB,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,eAAe,CAAC;QAC/B,aAAa,EAAE,MAAM,CAAA;QACrB,mCAAmC;QACnC,YAAY,EAAE,MAAM,CAAA;KACrB,CAAC,CAAA;IACF,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,WAAW,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,IAAI,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,2BAA2B,GAAG,uBAAuB,CAAA;AAEvF,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,WAAW,CAAA;IACjB,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,IAAI,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,wFAAwF;IACxF,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,IAAI,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,wBAAwB,GAAG,oBAAoB,CAAA;AAE9E,MAAM,MAAM,eAAe,GAAG,YAAY,CACxC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,eAAe,CAAA;CAAE,EACvD,gBAAgB,EAChB,mBAAmB,CACpB,CAAA;AAED,eAAO,MAAM,eAAe,GACzB,aAAa;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,KAAG,eAqDnC,CAAA"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/browser/mod.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAEhF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gDAAgD,CAAA;AAI/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAGxE,cAAc,yBAAyB,CAAA;AAEvC,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,WAAW,CAAA;IACjB,GAAG,EAAE,SAAS,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,eAAe,CAAA;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,WAAW,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,IAAI,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,mBAAmB,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,eAAe,CAAC;QAC/B,aAAa,EAAE,MAAM,CAAA;QACrB,mCAAmC;QACnC,YAAY,EAAE,MAAM,CAAA;KACrB,CAAC,CAAA;IACF,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,WAAW,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,IAAI,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,2BAA2B,GAAG,uBAAuB,CAAA;AAEvF,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,WAAW,CAAA;IACjB,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,IAAI,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,wFAAwF;IACxF,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,IAAI,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,wBAAwB,GAAG,oBAAoB,CAAA;AAE9E,MAAM,MAAM,eAAe,GAAG,YAAY,CACxC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,eAAe,CAAA;CAAE,EACvD,gBAAgB,EAChB,mBAAmB,CACpB,CAAA;AAED,eAAO,MAAM,eAAe,GACzB,aAAa;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,KAAG,eAqDnC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"mod.js","sourceRoot":"","sources":["../../src/browser/mod.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAGtD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C,cAAc,yBAAyB,CAAA;AA+CvC,MAAM,CAAC,MAAM,eAAe,GAC1B,CAAC,EAAE,OAAO,EAA0B,EAAmB,EAAE,CACzD,CAAC,KAAuB,EAAE,EAAE,CAC1B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC/B,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;QAClD,OAAO,YAAY,CAA8B;YAC/C,OAAO;YACP,QAAQ,EAAE;gBACR,IAAI,EAAE,WAAW;gBACjB,GAAG;gBACH,SAAS;gBACT,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;gBAClB,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;gBAC5C,eAAe,EAAE;oBACf,QAAQ,EAAE,UAAU;iBACrB;aACF;SACF,CAAQ,CAAA;IACX,CAAC;IAED,wCAAwC;IACxC,MAAM,sBAAsB,GAAG,EAAE,CAAA;IAEjC,IAAI,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAA;IAE/B,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,sBAAsB,EAAE,CAAC;QACnD,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,yBAAyB,KAAK,CAAC,QAAQ,UAAU,sBAAsB,eAAe,KAAK,CAAC,QAAQ,CAAC,MAAM,cAAc,CAC1H,CAAA;QACD,UAAU,GAAG,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAA;IACvD,CAAC;IAED,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC;QAC3C,OAAO;QACP,SAAS,EAAE,KAAK,CAAC,aAAa;QAC9B,QAAQ,EAAE,UAAU;KACrB,CAAC,CAAA;IAEF,OAAO,YAAY,CAA0B;QAC3C,OAAO;QACP,QAAQ,EAAE;YACR,IAAI,EAAE,MAAM;YACZ,GAAG;YACH,SAAS;YACT,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,QAAQ,CAAC;YACrD,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;YAC5C,eAAe,EAAE;gBACf,QAAQ,EAAE,UAAU;gBACpB,aAAa,EAAE,KAAK,CAAC,aAAa;gBAClC,YAAY,EAAE,GAAG,CAAC,eAAe,CAAC,UAAU,CAAC;aAC9C;SACF;KACF,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"mod.js","sourceRoot":"","sources":["../../src/browser/mod.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAItD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C,cAAc,yBAAyB,CAAA;AA+CvC,MAAM,CAAC,MAAM,eAAe,GAC1B,CAAC,EAAE,OAAO,EAA0B,EAAmB,EAAE,CACzD,CAAC,KAAuB,EAAE,EAAE,CAC1B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC/B,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;QAClD,OAAO,YAAY,CAA8B;YAC/C,OAAO;YACP,QAAQ,EAAE;gBACR,IAAI,EAAE,WAAW;gBACjB,GAAG;gBACH,SAAS;gBACT,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;gBAClB,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;gBAC5C,eAAe,EAAE;oBACf,QAAQ,EAAE,UAAU;iBACrB;aACF;SACF,CAAQ,CAAA;IACX,CAAC;IAED,wCAAwC;IACxC,MAAM,sBAAsB,GAAG,EAAE,CAAA;IAEjC,IAAI,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAA;IAE/B,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,sBAAsB,EAAE,CAAC;QACnD,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,yBAAyB,KAAK,CAAC,QAAQ,UAAU,sBAAsB,eAAe,KAAK,CAAC,QAAQ,CAAC,MAAM,cAAc,CAC1H,CAAA;QACD,UAAU,GAAG,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAA;IACvD,CAAC;IAED,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC;QAC3C,OAAO;QACP,SAAS,EAAE,KAAK,CAAC,aAAa;QAC9B,QAAQ,EAAE,UAAU;KACrB,CAAC,CAAA;IAEF,OAAO,YAAY,CAA0B;QAC3C,OAAO;QACP,QAAQ,EAAE;YACR,IAAI,EAAE,MAAM;YACZ,GAAG;YACH,SAAS;YACT,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,QAAQ,CAAC;YACrD,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;YAC5C,eAAe,EAAE;gBACf,QAAQ,EAAE,UAAU;gBACpB,aAAa,EAAE,KAAK,CAAC,aAAa;gBAClC,YAAY,EAAE,GAAG,CAAC,eAAe,CAAC,UAAU,CAAC;aAC9C;SACF;KACF,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,61 @@
1
+ import type { CfTypes } from '@livestore/common-cf';
2
+ export interface BlockRange {
3
+ startBlock: number;
4
+ endBlock: number;
5
+ startOffset: number;
6
+ endOffset: number;
7
+ }
8
+ export interface BlockData {
9
+ blockId: number;
10
+ data: Uint8Array;
11
+ }
12
+ /**
13
+ * BlockManager handles the conversion between file operations and block-based storage
14
+ * for the CloudflareSqlVFS. It manages fixed-size blocks stored in SQL tables.
15
+ */
16
+ export declare class BlockManager {
17
+ private readonly blockSize;
18
+ constructor(blockSize?: number);
19
+ /**
20
+ * Calculate which blocks are needed for a given file operation
21
+ */
22
+ calculateBlockRange(offset: number, length: number): BlockRange;
23
+ /**
24
+ * Read blocks from SQL storage and return as a Map
25
+ */
26
+ readBlocks(sql: CfTypes.SqlStorage, filePath: string, blockIds: number[]): Map<number, Uint8Array>;
27
+ /**
28
+ * Write blocks to SQL storage using exec for now (prepared statements later)
29
+ */
30
+ writeBlocks(sql: CfTypes.SqlStorage, filePath: string, blocks: Map<number, Uint8Array>): void;
31
+ /**
32
+ * Delete blocks at or after the specified block ID (used for truncation)
33
+ */
34
+ deleteBlocksAfter(sql: CfTypes.SqlStorage, filePath: string, startBlockId: number): void;
35
+ /**
36
+ * Split write data into blocks, handling partial blocks at boundaries
37
+ */
38
+ splitIntoBlocks(data: Uint8Array, offset: number): Map<number, {
39
+ blockId: number;
40
+ blockOffset: number;
41
+ data: Uint8Array;
42
+ }>;
43
+ /**
44
+ * Assemble read data from blocks into a continuous buffer
45
+ */
46
+ assembleBlocks(blocks: Map<number, Uint8Array>, range: BlockRange, requestedLength: number): Uint8Array;
47
+ /**
48
+ * Handle partial block writes by reading existing block, modifying, and returning complete block
49
+ */
50
+ mergePartialBlock(sql: CfTypes.SqlStorage, filePath: string, blockId: number, blockOffset: number, newData: Uint8Array): Uint8Array;
51
+ /**
52
+ * Get statistics about block usage for a file
53
+ */
54
+ getBlockStats(sql: CfTypes.SqlStorage, filePath: string): {
55
+ totalBlocks: number;
56
+ storedBlocks: number;
57
+ totalBytes: number;
58
+ };
59
+ getBlockSize(): number;
60
+ }
61
+ //# sourceMappingURL=BlockManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockManager.d.ts","sourceRoot":"","sources":["../../src/cf/BlockManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEnD,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,UAAU,CAAA;CACjB;AAED;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;gBAEtB,SAAS,GAAE,MAAkB;IAIzC;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU;IAc/D;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC;IAyBlG;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,IAAI;IAe7F;;OAEG;IACH,iBAAiB,CAAC,GAAG,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAIxF;;OAEG;IACH,eAAe,CACb,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,GACb,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC;IAyB1E;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,GAAG,UAAU;IA6BvG;;OAEG;IACH,iBAAiB,CACf,GAAG,EAAE,OAAO,CAAC,UAAU,EACvB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,UAAU,GAClB,UAAU;IAab;;OAEG;IACH,aAAa,CACX,GAAG,EAAE,OAAO,CAAC,UAAU,EACvB,QAAQ,EAAE,MAAM,GACf;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAmCpE,YAAY,IAAI,MAAM;CAGvB"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * BlockManager handles the conversion between file operations and block-based storage
3
+ * for the CloudflareSqlVFS. It manages fixed-size blocks stored in SQL tables.
4
+ */
5
+ export class BlockManager {
6
+ blockSize;
7
+ constructor(blockSize = 64 * 1024) {
8
+ this.blockSize = blockSize;
9
+ }
10
+ /**
11
+ * Calculate which blocks are needed for a given file operation
12
+ */
13
+ calculateBlockRange(offset, length) {
14
+ const startBlock = Math.floor(offset / this.blockSize);
15
+ const endBlock = Math.floor((offset + length - 1) / this.blockSize);
16
+ const startOffset = offset % this.blockSize;
17
+ const endOffset = ((offset + length - 1) % this.blockSize) + 1;
18
+ return {
19
+ startBlock,
20
+ endBlock,
21
+ startOffset,
22
+ endOffset,
23
+ };
24
+ }
25
+ /**
26
+ * Read blocks from SQL storage and return as a Map
27
+ */
28
+ readBlocks(sql, filePath, blockIds) {
29
+ const blocks = new Map();
30
+ if (blockIds.length === 0) {
31
+ return blocks;
32
+ }
33
+ // Build IN clause for efficient querying
34
+ const placeholders = blockIds.map(() => '?').join(',');
35
+ const query = `
36
+ SELECT block_id, block_data
37
+ FROM vfs_blocks
38
+ WHERE file_path = ? AND block_id IN (${placeholders})
39
+ ORDER BY block_id
40
+ `;
41
+ const cursor = sql.exec(query, filePath, ...blockIds);
42
+ for (const row of cursor) {
43
+ blocks.set(row.block_id, new Uint8Array(row.block_data));
44
+ }
45
+ return blocks;
46
+ }
47
+ /**
48
+ * Write blocks to SQL storage using exec for now (prepared statements later)
49
+ */
50
+ writeBlocks(sql, filePath, blocks) {
51
+ if (blocks.size === 0) {
52
+ return;
53
+ }
54
+ for (const [blockId, data] of blocks) {
55
+ sql.exec('INSERT OR REPLACE INTO vfs_blocks (file_path, block_id, block_data) VALUES (?, ?, ?)', filePath, blockId, data);
56
+ }
57
+ }
58
+ /**
59
+ * Delete blocks at or after the specified block ID (used for truncation)
60
+ */
61
+ deleteBlocksAfter(sql, filePath, startBlockId) {
62
+ sql.exec('DELETE FROM vfs_blocks WHERE file_path = ? AND block_id >= ?', filePath, startBlockId);
63
+ }
64
+ /**
65
+ * Split write data into blocks, handling partial blocks at boundaries
66
+ */
67
+ splitIntoBlocks(data, offset) {
68
+ const blocks = new Map();
69
+ let remainingData = data;
70
+ let currentOffset = offset;
71
+ while (remainingData.length > 0) {
72
+ const blockId = Math.floor(currentOffset / this.blockSize);
73
+ const blockOffset = currentOffset % this.blockSize;
74
+ const bytesToWrite = Math.min(remainingData.length, this.blockSize - blockOffset);
75
+ const blockData = remainingData.slice(0, bytesToWrite);
76
+ blocks.set(blockId, {
77
+ blockId,
78
+ blockOffset,
79
+ data: blockData,
80
+ });
81
+ remainingData = remainingData.slice(bytesToWrite);
82
+ currentOffset += bytesToWrite;
83
+ }
84
+ return blocks;
85
+ }
86
+ /**
87
+ * Assemble read data from blocks into a continuous buffer
88
+ */
89
+ assembleBlocks(blocks, range, requestedLength) {
90
+ const result = new Uint8Array(requestedLength);
91
+ let resultOffset = 0;
92
+ for (let blockId = range.startBlock; blockId <= range.endBlock; blockId++) {
93
+ const blockData = blocks.get(blockId);
94
+ if (!blockData) {
95
+ // Block not found - fill with zeros (sparse file behavior)
96
+ const zeroLength = Math.min(this.blockSize, requestedLength - resultOffset);
97
+ // result is already zero-filled by default
98
+ resultOffset += zeroLength;
99
+ continue;
100
+ }
101
+ // Calculate the slice of this block we need
102
+ const blockStartOffset = blockId === range.startBlock ? range.startOffset : 0;
103
+ const blockEndOffset = blockId === range.endBlock ? range.endOffset : blockData.length;
104
+ const sliceLength = blockEndOffset - blockStartOffset;
105
+ if (sliceLength > 0) {
106
+ const slice = blockData.slice(blockStartOffset, blockEndOffset);
107
+ result.set(slice, resultOffset);
108
+ resultOffset += sliceLength;
109
+ }
110
+ }
111
+ return result;
112
+ }
113
+ /**
114
+ * Handle partial block writes by reading existing block, modifying, and returning complete block
115
+ */
116
+ mergePartialBlock(sql, filePath, blockId, blockOffset, newData) {
117
+ // Read existing block data if it exists
118
+ const existingBlocks = this.readBlocks(sql, filePath, [blockId]);
119
+ const existingBlock = existingBlocks.get(blockId) || new Uint8Array(this.blockSize);
120
+ // Create a new block with the merged data
121
+ const mergedBlock = new Uint8Array(this.blockSize);
122
+ mergedBlock.set(existingBlock);
123
+ mergedBlock.set(newData, blockOffset);
124
+ return mergedBlock;
125
+ }
126
+ /**
127
+ * Get statistics about block usage for a file
128
+ */
129
+ getBlockStats(sql, filePath) {
130
+ const blockStatsCursor = sql.exec(`SELECT
131
+ COUNT(*) as stored_blocks,
132
+ COALESCE(SUM(LENGTH(block_data)), 0) as total_bytes
133
+ FROM vfs_blocks
134
+ WHERE file_path = ?`, filePath);
135
+ const result = blockStatsCursor.one();
136
+ // Get file size to calculate theoretical total blocks
137
+ const fileSizeCursor = sql.exec('SELECT file_size FROM vfs_files WHERE file_path = ?', filePath);
138
+ let fileSize = 0;
139
+ try {
140
+ const fileSizeResult = fileSizeCursor.one();
141
+ fileSize = fileSizeResult.file_size;
142
+ }
143
+ catch {
144
+ // File doesn't exist
145
+ }
146
+ const totalBlocks = Math.ceil(fileSize / this.blockSize);
147
+ return {
148
+ totalBlocks,
149
+ storedBlocks: result.stored_blocks,
150
+ totalBytes: result.total_bytes,
151
+ };
152
+ }
153
+ getBlockSize() {
154
+ return this.blockSize;
155
+ }
156
+ }
157
+ //# sourceMappingURL=BlockManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockManager.js","sourceRoot":"","sources":["../../src/cf/BlockManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { CfTypes } from '@livestore/common-cf';
2
+ import { FacadeVFS } from '../FacadeVFS.ts';
3
+ export interface SqlVfsOptions {
4
+ maxFiles?: number;
5
+ blockSize?: number;
6
+ }
7
+ /**
8
+ * VFS implementation using Cloudflare Durable Object SQL storage as the backend.
9
+ * This provides a synchronous VFS interface by leveraging SQL's synchronous API.
10
+ *
11
+ * Storage Strategy:
12
+ * - Files are stored as blocks in SQL tables for efficient I/O
13
+ * - File metadata stored in vfs_files table
14
+ * - File data stored as fixed-size blocks in vfs_blocks table
15
+ * - Synchronous operations via SQL's synchronous API
16
+ *
17
+ * Key advantages over async VFS:
18
+ * - No async/await complexity
19
+ * - Native SQL ACID properties
20
+ * - Efficient range queries for file operations
21
+ * - Built-in consistency and durability
22
+ */
23
+ export declare class CloudflareSqlVFS extends FacadeVFS {
24
+ #private;
25
+ log: null;
26
+ static create(name: string, sql: CfTypes.SqlStorage, module: any, options?: SqlVfsOptions): Promise<CloudflareSqlVFS>;
27
+ constructor(name: string, sql: CfTypes.SqlStorage, module: any, options?: SqlVfsOptions);
28
+ /**
29
+ * Initialize the VFS by setting up SQL schema
30
+ */
31
+ isReady(): Promise<boolean>;
32
+ jOpen(path: string, fileId: number, flags: number, pOutFlags: DataView): number;
33
+ jClose(fileId: number): number;
34
+ jRead(fileId: number, buffer: Uint8Array, offset: number): number;
35
+ jWrite(fileId: number, data: Uint8Array, offset: number): number;
36
+ jTruncate(fileId: number, size: number): number;
37
+ jSync(fileId: number, _flags: number): number;
38
+ jFileSize(fileId: number, pSize64: DataView): number;
39
+ jDelete(path: string, _syncDir: number): number;
40
+ jAccess(path: string, _flags: number, pResOut: DataView): number;
41
+ jSectorSize(_fileId: number): number;
42
+ jDeviceCharacteristics(_fileId: number): number;
43
+ getStats(): {
44
+ activeFiles: number;
45
+ openFiles: number;
46
+ maxFiles: number;
47
+ blockSize: number;
48
+ totalStoredBytes: number;
49
+ };
50
+ }
51
+ //# sourceMappingURL=CloudflareSqlVFS.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CloudflareSqlVFS.d.ts","sourceRoot":"","sources":["../../src/cf/CloudflareSqlVFS.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEnD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AA6B3C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,gBAAiB,SAAQ,SAAS;;IAC7C,GAAG,OAAO;WAUG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAE,aAAkB;gBAMvF,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAE,aAAkB;IAO3F;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IA2FjC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,GAAG,MAAM;IAqD/E,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAK9B,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAwBjE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAgDhE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAuC/C,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAS7C,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,MAAM;IAepD,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAU/C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,MAAM;IAWhE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIpC,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IA+B/C,QAAQ,IAAI;QACV,WAAW,EAAE,MAAM,CAAA;QACnB,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,gBAAgB,EAAE,MAAM,CAAA;KACzB;CAwBF"}
@@ -0,0 +1,351 @@
1
+ import * as VFS from '@livestore/wa-sqlite/src/VFS.js';
2
+ import { FacadeVFS } from "../FacadeVFS.js";
3
+ import { BlockManager } from "./BlockManager.js";
4
+ const SECTOR_SIZE = 4096;
5
+ // Block size for SQL-based storage (same as CloudflareWorkerVFS for consistency)
6
+ const BLOCK_SIZE = 64 * 1024; // 64 KiB
7
+ // Maximum number of open files
8
+ const DEFAULT_MAX_FILES = 100;
9
+ // These file types are expected to persist in the file system
10
+ const PERSISTENT_FILE_TYPES = VFS.SQLITE_OPEN_MAIN_DB | VFS.SQLITE_OPEN_MAIN_JOURNAL | VFS.SQLITE_OPEN_SUPER_JOURNAL | VFS.SQLITE_OPEN_WAL;
11
+ /**
12
+ * VFS implementation using Cloudflare Durable Object SQL storage as the backend.
13
+ * This provides a synchronous VFS interface by leveraging SQL's synchronous API.
14
+ *
15
+ * Storage Strategy:
16
+ * - Files are stored as blocks in SQL tables for efficient I/O
17
+ * - File metadata stored in vfs_files table
18
+ * - File data stored as fixed-size blocks in vfs_blocks table
19
+ * - Synchronous operations via SQL's synchronous API
20
+ *
21
+ * Key advantages over async VFS:
22
+ * - No async/await complexity
23
+ * - Native SQL ACID properties
24
+ * - Efficient range queries for file operations
25
+ * - Built-in consistency and durability
26
+ */
27
+ export class CloudflareSqlVFS extends FacadeVFS {
28
+ log = null;
29
+ #sql;
30
+ #initialized = false;
31
+ #blockManager;
32
+ // File management
33
+ #openFiles = new Map();
34
+ #maxFiles;
35
+ static async create(name, sql, module, options = {}) {
36
+ const vfs = new CloudflareSqlVFS(name, sql, module, options);
37
+ await vfs.isReady();
38
+ return vfs;
39
+ }
40
+ constructor(name, sql, module, options = {}) {
41
+ super(name, module);
42
+ this.#sql = sql;
43
+ this.#maxFiles = options.maxFiles || DEFAULT_MAX_FILES;
44
+ this.#blockManager = new BlockManager(options.blockSize || BLOCK_SIZE);
45
+ }
46
+ /**
47
+ * Initialize the VFS by setting up SQL schema
48
+ */
49
+ async isReady() {
50
+ if (this.#initialized) {
51
+ return true;
52
+ }
53
+ try {
54
+ // Initialize SQL schema
55
+ this.#initializeSchema();
56
+ // Clean up non-persistent files from previous sessions
57
+ this.#cleanupNonPersistentFiles();
58
+ this.#initialized = true;
59
+ return true;
60
+ }
61
+ catch (error) {
62
+ console.error('CloudflareSqlVFS initialization failed:', error);
63
+ return false;
64
+ }
65
+ }
66
+ /**
67
+ * Initialize the SQL schema for the VFS
68
+ */
69
+ #initializeSchema() {
70
+ // Execute each statement individually to avoid parsing issues
71
+ const statements = [
72
+ `CREATE TABLE IF NOT EXISTS vfs_files (
73
+ file_path TEXT PRIMARY KEY,
74
+ file_size INTEGER NOT NULL DEFAULT 0,
75
+ flags INTEGER NOT NULL DEFAULT 0,
76
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
77
+ modified_at INTEGER NOT NULL DEFAULT (unixepoch())
78
+ )`,
79
+ `CREATE TABLE IF NOT EXISTS vfs_blocks (
80
+ file_path TEXT NOT NULL,
81
+ block_id INTEGER NOT NULL,
82
+ block_data BLOB NOT NULL,
83
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
84
+ PRIMARY KEY (file_path, block_id),
85
+ FOREIGN KEY (file_path) REFERENCES vfs_files(file_path) ON DELETE CASCADE
86
+ )`,
87
+ `CREATE INDEX IF NOT EXISTS idx_vfs_blocks_range ON vfs_blocks(file_path, block_id)`,
88
+ `CREATE INDEX IF NOT EXISTS idx_vfs_files_modified ON vfs_files(modified_at)`,
89
+ `CREATE TRIGGER IF NOT EXISTS trg_vfs_files_update_modified
90
+ AFTER UPDATE OF file_size ON vfs_files
91
+ BEGIN
92
+ UPDATE vfs_files SET modified_at = unixepoch() WHERE file_path = NEW.file_path;
93
+ END`,
94
+ ];
95
+ for (const statement of statements) {
96
+ try {
97
+ this.#sql.exec(statement);
98
+ }
99
+ catch (error) {
100
+ console.error('Failed to execute schema statement:', statement);
101
+ throw error;
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * Clean up non-persistent files from previous sessions
107
+ */
108
+ #cleanupNonPersistentFiles() {
109
+ try {
110
+ const cursor = this.#sql.exec('SELECT file_path, flags FROM vfs_files');
111
+ const filesToDelete = [];
112
+ for (const row of cursor) {
113
+ // Check if file should be persistent
114
+ if (!(row.flags & PERSISTENT_FILE_TYPES)) {
115
+ filesToDelete.push(row.file_path);
116
+ }
117
+ }
118
+ // Delete non-persistent files
119
+ for (const filePath of filesToDelete) {
120
+ this.#sql.exec('DELETE FROM vfs_files WHERE file_path = ?', filePath);
121
+ }
122
+ }
123
+ catch (error) {
124
+ console.warn('Error during cleanup:', error);
125
+ }
126
+ }
127
+ // VFS Interface Implementation
128
+ jOpen(path, fileId, flags, pOutFlags) {
129
+ try {
130
+ if (this.#openFiles.size >= this.#maxFiles) {
131
+ return VFS.SQLITE_CANTOPEN;
132
+ }
133
+ // Check if file exists
134
+ const existingFile = this.#getFileMetadata(path);
135
+ if (!existingFile && !(flags & VFS.SQLITE_OPEN_CREATE)) {
136
+ return VFS.SQLITE_CANTOPEN;
137
+ }
138
+ let metadata;
139
+ if (existingFile) {
140
+ metadata = existingFile;
141
+ }
142
+ else {
143
+ // Create new file
144
+ const now = Math.floor(Date.now() / 1000);
145
+ metadata = {
146
+ path,
147
+ size: 0,
148
+ flags,
149
+ created: now,
150
+ modified: now,
151
+ };
152
+ this.#sql.exec('INSERT INTO vfs_files (file_path, file_size, flags, created_at, modified_at) VALUES (?, ?, ?, ?, ?)', path, 0, flags, now, now);
153
+ }
154
+ // Store file handle
155
+ this.#openFiles.set(fileId, {
156
+ path,
157
+ flags,
158
+ metadata,
159
+ });
160
+ pOutFlags.setInt32(0, flags, true);
161
+ return VFS.SQLITE_OK;
162
+ }
163
+ catch (error) {
164
+ console.error('jOpen error:', error);
165
+ return VFS.SQLITE_CANTOPEN;
166
+ }
167
+ }
168
+ jClose(fileId) {
169
+ this.#openFiles.delete(fileId);
170
+ return VFS.SQLITE_OK;
171
+ }
172
+ jRead(fileId, buffer, offset) {
173
+ try {
174
+ const handle = this.#openFiles.get(fileId);
175
+ if (!handle) {
176
+ return VFS.SQLITE_IOERR;
177
+ }
178
+ const range = this.#blockManager.calculateBlockRange(offset, buffer.length);
179
+ const blockIds = [];
180
+ for (let i = range.startBlock; i <= range.endBlock; i++) {
181
+ blockIds.push(i);
182
+ }
183
+ const blocks = this.#blockManager.readBlocks(this.#sql, handle.path, blockIds);
184
+ const data = this.#blockManager.assembleBlocks(blocks, range, buffer.length);
185
+ buffer.set(data);
186
+ return VFS.SQLITE_OK;
187
+ }
188
+ catch (error) {
189
+ console.error('jRead error:', error);
190
+ return VFS.SQLITE_IOERR;
191
+ }
192
+ }
193
+ jWrite(fileId, data, offset) {
194
+ try {
195
+ const handle = this.#openFiles.get(fileId);
196
+ if (!handle) {
197
+ return VFS.SQLITE_IOERR;
198
+ }
199
+ // Split write data into blocks
200
+ const writeBlocks = this.#blockManager.splitIntoBlocks(data, offset);
201
+ const finalBlocks = new Map();
202
+ for (const [blockId, blockInfo] of writeBlocks) {
203
+ let blockData;
204
+ if (blockInfo.blockOffset === 0 && blockInfo.data.length === this.#blockManager.getBlockSize()) {
205
+ // Full block write
206
+ blockData = blockInfo.data;
207
+ }
208
+ else {
209
+ // Partial block write - merge with existing data
210
+ blockData = this.#blockManager.mergePartialBlock(this.#sql, handle.path, blockInfo.blockId, blockInfo.blockOffset, blockInfo.data);
211
+ }
212
+ finalBlocks.set(blockId, blockData);
213
+ }
214
+ // Write blocks to SQL storage
215
+ this.#blockManager.writeBlocks(this.#sql, handle.path, finalBlocks);
216
+ // Update file size if necessary
217
+ const newSize = Math.max(handle.metadata.size, offset + data.length);
218
+ if (newSize !== handle.metadata.size) {
219
+ this.#sql.exec('UPDATE vfs_files SET file_size = ? WHERE file_path = ?', newSize, handle.path);
220
+ handle.metadata.size = newSize;
221
+ }
222
+ return VFS.SQLITE_OK;
223
+ }
224
+ catch (error) {
225
+ console.error('jWrite error:', error);
226
+ return VFS.SQLITE_IOERR;
227
+ }
228
+ }
229
+ jTruncate(fileId, size) {
230
+ try {
231
+ const handle = this.#openFiles.get(fileId);
232
+ if (!handle) {
233
+ return VFS.SQLITE_IOERR;
234
+ }
235
+ // Calculate which block contains the new end of file
236
+ const lastBlockId = Math.floor(size / this.#blockManager.getBlockSize());
237
+ // Delete blocks beyond the truncation point
238
+ this.#blockManager.deleteBlocksAfter(this.#sql, handle.path, lastBlockId + 1);
239
+ // If truncating within a block, we need to truncate that block's data
240
+ if (size % this.#blockManager.getBlockSize() !== 0) {
241
+ const existingBlocks = this.#blockManager.readBlocks(this.#sql, handle.path, [lastBlockId]);
242
+ const blockData = existingBlocks.get(lastBlockId);
243
+ if (blockData) {
244
+ const truncatedBlock = blockData.slice(0, size % this.#blockManager.getBlockSize());
245
+ const paddedBlock = new Uint8Array(this.#blockManager.getBlockSize());
246
+ paddedBlock.set(truncatedBlock);
247
+ const blocksToWrite = new Map([[lastBlockId, paddedBlock]]);
248
+ this.#blockManager.writeBlocks(this.#sql, handle.path, blocksToWrite);
249
+ }
250
+ }
251
+ // Update file metadata
252
+ this.#sql.exec('UPDATE vfs_files SET file_size = ? WHERE file_path = ?', size, handle.path);
253
+ handle.metadata.size = size;
254
+ return VFS.SQLITE_OK;
255
+ }
256
+ catch (error) {
257
+ console.error('jTruncate error:', error);
258
+ return VFS.SQLITE_IOERR;
259
+ }
260
+ }
261
+ jSync(fileId, _flags) {
262
+ // SQL storage provides immediate durability, so sync is effectively a no-op
263
+ const handle = this.#openFiles.get(fileId);
264
+ if (!handle) {
265
+ return VFS.SQLITE_IOERR;
266
+ }
267
+ return VFS.SQLITE_OK;
268
+ }
269
+ jFileSize(fileId, pSize64) {
270
+ try {
271
+ const handle = this.#openFiles.get(fileId);
272
+ if (!handle) {
273
+ return VFS.SQLITE_IOERR;
274
+ }
275
+ pSize64.setBigInt64(0, BigInt(handle.metadata.size), true);
276
+ return VFS.SQLITE_OK;
277
+ }
278
+ catch (error) {
279
+ console.error('jFileSize error:', error);
280
+ return VFS.SQLITE_IOERR;
281
+ }
282
+ }
283
+ jDelete(path, _syncDir) {
284
+ try {
285
+ this.#sql.exec('DELETE FROM vfs_files WHERE file_path = ?', path);
286
+ return VFS.SQLITE_OK;
287
+ }
288
+ catch (error) {
289
+ console.error('jDelete error:', error);
290
+ return VFS.SQLITE_IOERR;
291
+ }
292
+ }
293
+ jAccess(path, _flags, pResOut) {
294
+ try {
295
+ const metadata = this.#getFileMetadata(path);
296
+ pResOut.setInt32(0, metadata ? 1 : 0, true);
297
+ return VFS.SQLITE_OK;
298
+ }
299
+ catch (error) {
300
+ console.error('jAccess error:', error);
301
+ return VFS.SQLITE_IOERR;
302
+ }
303
+ }
304
+ jSectorSize(_fileId) {
305
+ return SECTOR_SIZE;
306
+ }
307
+ jDeviceCharacteristics(_fileId) {
308
+ return VFS.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
309
+ }
310
+ // Helper methods
311
+ #getFileMetadata(path) {
312
+ try {
313
+ const cursor = this.#sql.exec('SELECT file_path, file_size, flags, created_at, modified_at FROM vfs_files WHERE file_path = ?', path);
314
+ const row = cursor.one();
315
+ return {
316
+ path: row.file_path,
317
+ size: row.file_size,
318
+ flags: row.flags,
319
+ created: row.created_at,
320
+ modified: row.modified_at,
321
+ };
322
+ }
323
+ catch {
324
+ return undefined;
325
+ }
326
+ }
327
+ // Statistics and debugging
328
+ getStats() {
329
+ try {
330
+ const cursor = this.#sql.exec('SELECT COUNT(*) as total_files, COALESCE(SUM(LENGTH(block_data)), 0) as total_bytes FROM vfs_files LEFT JOIN vfs_blocks USING (file_path)');
331
+ const stats = cursor.one();
332
+ return {
333
+ activeFiles: stats.total_files,
334
+ openFiles: this.#openFiles.size,
335
+ maxFiles: this.#maxFiles,
336
+ blockSize: this.#blockManager.getBlockSize(),
337
+ totalStoredBytes: stats.total_bytes,
338
+ };
339
+ }
340
+ catch {
341
+ return {
342
+ activeFiles: 0,
343
+ openFiles: this.#openFiles.size,
344
+ maxFiles: this.#maxFiles,
345
+ blockSize: this.#blockManager.getBlockSize(),
346
+ totalStoredBytes: 0,
347
+ };
348
+ }
349
+ }
350
+ }
351
+ //# sourceMappingURL=CloudflareSqlVFS.js.map