@walkeros/server-store-fs 4.8.1 → 4.8.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @walkeros/server-store-fs
2
2
 
3
+ ## 4.8.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 7e5fc27: A path that is a directory is a miss, like a missing file, instead of
8
+ a failed read. A read that fails for another reason, such as a permission
9
+ error, still throws and is reported by the collector.
10
+ - 2374f54: A read, write or delete that fails (403, 5xx, network) now throws
11
+ instead of looking like a missing key, and a stored value that cannot be
12
+ decoded throws too. The collector reports a failed read as a miss with one
13
+ error line. S3 and Sheets check their bucket or spreadsheet at the first
14
+ operation instead of at startup.
15
+ - Updated dependencies [2374f54]
16
+ - Updated dependencies [7e5fc27]
17
+ - Updated dependencies [2374f54]
18
+ - Updated dependencies [964590a]
19
+ - Updated dependencies [4100ede]
20
+ - Updated dependencies [2374f54]
21
+ - Updated dependencies [2374f54]
22
+ - Updated dependencies [15eb7b2]
23
+ - Updated dependencies [33a2779]
24
+ - Updated dependencies [33a2779]
25
+ - @walkeros/core@4.8.2
26
+
3
27
  ## 4.8.1
4
28
 
5
29
  ### Patch Changes
package/README.md CHANGED
@@ -28,7 +28,10 @@ npm install @walkeros/server-store-fs
28
28
  "default": {
29
29
  "config": { "platform": "server" },
30
30
  "stores": {
31
- "assets": { "package": "@walkeros/server-store-fs", "config": {} }
31
+ "assets": {
32
+ "package": "@walkeros/server-store-fs",
33
+ "config": { "settings": { "basePath": "./assets" } }
34
+ }
32
35
  }
33
36
  }
34
37
  }
package/dist/dev.d.mts CHANGED
@@ -4,7 +4,7 @@ import { Flow, Store, Hint } from '@walkeros/core';
4
4
 
5
5
  declare const SettingsSchema: z.ZodObject<{
6
6
  basePath: z.ZodString;
7
- }, z.core.$strip>;
7
+ }, z.core.$strict>;
8
8
  type Settings = z.infer<typeof SettingsSchema>;
9
9
 
10
10
  declare const settings: _walkeros_core_dev.JSONSchema;
@@ -22,12 +22,15 @@ declare const readExistingFile: Flow.StepExample;
22
22
  declare const writeNewFile: Flow.StepExample;
23
23
  /** Path traversal attempt is rejected for security. */
24
24
  declare const pathTraversalRejection: Flow.StepExample;
25
+ /** A read that fails for another reason than a missing file. */
26
+ declare const failedRead: Flow.StepExample;
25
27
 
28
+ declare const step_failedRead: typeof failedRead;
26
29
  declare const step_pathTraversalRejection: typeof pathTraversalRejection;
27
30
  declare const step_readExistingFile: typeof readExistingFile;
28
31
  declare const step_writeNewFile: typeof writeNewFile;
29
32
  declare namespace step {
30
- export { step_pathTraversalRejection as pathTraversalRejection, step_readExistingFile as readExistingFile, step_writeNewFile as writeNewFile };
33
+ export { step_failedRead as failedRead, step_pathTraversalRejection as pathTraversalRejection, step_readExistingFile as readExistingFile, step_writeNewFile as writeNewFile };
31
34
  }
32
35
 
33
36
  /** Serve static files from a local directory, byte-exact (file mode) */
package/dist/dev.d.ts CHANGED
@@ -4,7 +4,7 @@ import { Flow, Store, Hint } from '@walkeros/core';
4
4
 
5
5
  declare const SettingsSchema: z.ZodObject<{
6
6
  basePath: z.ZodString;
7
- }, z.core.$strip>;
7
+ }, z.core.$strict>;
8
8
  type Settings = z.infer<typeof SettingsSchema>;
9
9
 
10
10
  declare const settings: _walkeros_core_dev.JSONSchema;
@@ -22,12 +22,15 @@ declare const readExistingFile: Flow.StepExample;
22
22
  declare const writeNewFile: Flow.StepExample;
23
23
  /** Path traversal attempt is rejected for security. */
24
24
  declare const pathTraversalRejection: Flow.StepExample;
25
+ /** A read that fails for another reason than a missing file. */
26
+ declare const failedRead: Flow.StepExample;
25
27
 
28
+ declare const step_failedRead: typeof failedRead;
26
29
  declare const step_pathTraversalRejection: typeof pathTraversalRejection;
27
30
  declare const step_readExistingFile: typeof readExistingFile;
28
31
  declare const step_writeNewFile: typeof writeNewFile;
29
32
  declare namespace step {
30
- export { step_pathTraversalRejection as pathTraversalRejection, step_readExistingFile as readExistingFile, step_writeNewFile as writeNewFile };
33
+ export { step_failedRead as failedRead, step_pathTraversalRejection as pathTraversalRejection, step_readExistingFile as readExistingFile, step_writeNewFile as writeNewFile };
31
34
  }
32
35
 
33
36
  /** Serve static files from a local directory, byte-exact (file mode) */
package/dist/dev.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,s=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,i=(e,r)=>{for(var s in r)t(e,s,{get:r[s],enumerable:!0})},o={};i(o,{examples:()=>p,hints:()=>y,schemas:()=>c}),module.exports=(e=o,((e,i,o,c)=>{if(i&&"object"==typeof i||"function"==typeof i)for(let n of s(i))a.call(e,n)||n===o||t(e,n,{get:()=>i[n],enumerable:!(c=r(i,n))||c.enumerable});return e})(t({},"__esModule",{value:!0}),e));var c={};i(c,{SettingsSchema:()=>d,settings:()=>u});var n=require("@walkeros/core/dev"),l=require("@walkeros/core/dev"),d=l.z.object({basePath:l.z.string().min(1).describe("Root directory for file operations. All keys are resolved relative to this path.")}),u=(0,n.zodToSchema)(d),p={};i(p,{staticAssets:()=>m,step:()=>f,structuredState:()=>v});var f={};i(f,{pathTraversalRejection:()=>b,readExistingFile:()=>h,writeNewFile:()=>g});var h={title:"Read file",description:"Read an existing file and receive its raw bytes byte-exact",in:{operation:"get",key:"walker.js"},out:[["get","walker.js",'Bytes<console.log("walkerOS")>']]},g={title:"Write file",description:"Write creates intermediate directories automatically",in:{operation:"set",key:"js/custom/tracker.js",value:"Buffer<(function(){...})()>"},out:[["set","js/custom/tracker.js","Buffer<(function(){...})()>"]]},b={public:!1,description:"Path traversal via .. segments is rejected with a warning",in:{operation:"get",key:"../../etc/passwd"},out:[["get","../../etc/passwd",void 0]]},m={settings:{basePath:"./public"},file:!0},v={settings:{basePath:"./state"}},y={"file-mode":{text:"Default mode is structured: values are StoreValue data round-tripped through the shared core codec, good for session state, lookups, and cached responses. Set config.file: true to persist raw bytes byte-exact (set accepts Uint8Array or string, get hands the exact bytes back), the mode for serving assets such as walker.js via the file transformer. One store instance is exactly one mode. Do not pair file: true with a cache; the cache wraps structured stores and flow_validate flags the combination."}};//# sourceMappingURL=dev.js.map
1
+ "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,s=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,i=(e,r)=>{for(var s in r)t(e,s,{get:r[s],enumerable:!0})},o={};i(o,{examples:()=>p,hints:()=>v,schemas:()=>n}),module.exports=(e=o,((e,i,o,n)=>{if(i&&"object"==typeof i||"function"==typeof i)for(let c of s(i))a.call(e,c)||c===o||t(e,c,{get:()=>i[c],enumerable:!(n=r(i,c))||n.enumerable});return e})(t({},"__esModule",{value:!0}),e));var n={};i(n,{SettingsSchema:()=>d,settings:()=>u});var c=require("@walkeros/core/dev"),l=require("@walkeros/core/dev"),d=l.z.object({basePath:l.z.string().min(1).describe("Root directory for file operations. All keys are resolved relative to this path.")}).strict(),u=(0,c.zodToSchema)(d),p={};i(p,{staticAssets:()=>w,step:()=>f,structuredState:()=>y});var f={};i(f,{failedRead:()=>b,pathTraversalRejection:()=>m,readExistingFile:()=>h,writeNewFile:()=>g});var h={title:"Read file",description:"Read an existing file and receive its raw bytes byte-exact",in:{operation:"get",key:"walker.js"},out:[["get","walker.js",'Bytes<console.log("walkerOS")>']]},g={title:"Write file",description:"Write creates intermediate directories automatically",in:{operation:"set",key:"js/custom/tracker.js",value:"Buffer<(function(){...})()>"},out:[["set","js/custom/tracker.js","Buffer<(function(){...})()>"]]},m={public:!1,description:"Path traversal via .. segments is rejected with a warning",in:{operation:"get",key:"../../etc/passwd"},out:[["get","../../etc/passwd",void 0]]},b={public:!1,description:"The file system fails the read with another error than a missing file (permission denied, a broken disk): get throws a StepError instead of answering a miss.",in:{operation:"get",key:"private.json"},respond:{error:"network"},outcome:{status:"failed",code:"transport"}},w={settings:{basePath:"./public"},file:!0},y={settings:{basePath:"./state"}},v={"file-mode":{text:"Default mode is structured: values are StoreValue data round-tripped through the shared core codec, good for session state, lookups, and cached responses. Set config.file: true to persist raw bytes byte-exact (set accepts Uint8Array or string, get hands the exact bytes back), the mode for serving assets such as walker.js via the file transformer. One store instance is exactly one mode. Do not pair file: true with a cache; the cache wraps structured stores and flow_validate flags the combination."}};//# sourceMappingURL=dev.js.map
package/dist/dev.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\nexport { hints } from './hints';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\n\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nexport const SettingsSchema = z.object({\n basePath: z\n .string()\n .min(1)\n .describe(\n 'Root directory for file operations. All keys are resolved relative to this path.',\n ),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Serve static files from a local directory, byte-exact (file mode) */\nexport const staticAssets: Store.Config = {\n settings: {\n basePath: './public',\n },\n file: true,\n};\n\n/** Structured key-value store on disk (default mode) */\nexport const structuredState: Store.Config = {\n settings: {\n basePath: './state',\n },\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a file that exists in the store directory (file mode, byte-exact). */\nexport const readExistingFile: Flow.StepExample = {\n title: 'Read file',\n description: 'Read an existing file and receive its raw bytes byte-exact',\n in: { operation: 'get', key: 'walker.js' },\n out: [['get', 'walker.js', 'Bytes<console.log(\"walkerOS\")>']],\n};\n\n/** Write a file with auto-created parent directories. */\nexport const writeNewFile: Flow.StepExample = {\n title: 'Write file',\n description: 'Write creates intermediate directories automatically',\n in: {\n operation: 'set',\n key: 'js/custom/tracker.js',\n value: 'Buffer<(function(){...})()>',\n },\n out: [['set', 'js/custom/tracker.js', 'Buffer<(function(){...})()>']],\n};\n\n/** Path traversal attempt is rejected for security. */\nexport const pathTraversalRejection: Flow.StepExample = {\n public: false,\n description: 'Path traversal via .. segments is rejected with a warning',\n in: { operation: 'get', key: '../../etc/passwd' },\n out: [['get', '../../etc/passwd', undefined]],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'file-mode': {\n text: 'Default mode is structured: values are StoreValue data round-tripped through the shared core codec, good for session state, lookups, and cached responses. Set config.file: true to persist raw bytes byte-exact (set accepts Uint8Array or string, get hands the exact bytes back), the mode for serving assets such as walker.js via the file transformer. One store instance is exactly one mode. Do not pair file: true with a cache; the cache wraps structured stores and flow_validate flags the combination.',\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,cAA4B;;;ACA5B,iBAAkB;AAEX,IAAM,iBAAiB,aAAE,OAAO;AAAA,EACrC,UAAU,aACP,OAAO,EACP,IAAI,CAAC,EACL;AAAA,IACC;AAAA,EACF;AACJ,CAAC;;;ADJM,IAAM,eAAW,yBAAY,cAAc;;;AELlD;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,mBAAqC;AAAA,EAChD,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,CAAC,CAAC,OAAO,aAAa,gCAAgC,CAAC;AAC9D;AAGO,IAAM,eAAiC;AAAA,EAC5C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,OAAO;AAAA,EACT;AAAA,EACA,KAAK,CAAC,CAAC,OAAO,wBAAwB,6BAA6B,CAAC;AACtE;AAGO,IAAM,yBAA2C;AAAA,EACtD,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,mBAAmB;AAAA,EAChD,KAAK,CAAC,CAAC,OAAO,oBAAoB,MAAS,CAAC;AAC9C;;;ADzBO,IAAM,eAA6B;AAAA,EACxC,UAAU;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA,MAAM;AACR;AAGO,IAAM,kBAAgC;AAAA,EAC3C,UAAU;AAAA,IACR,UAAU;AAAA,EACZ;AACF;;;AEbO,IAAM,QAAoB;AAAA,EAC/B,aAAa;AAAA,IACX,MAAM;AAAA,EACR;AACF;","names":["import_dev"]}
1
+ {"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\nexport { hints } from './hints';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\n\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nexport const SettingsSchema = z\n .object({\n basePath: z\n .string()\n .min(1)\n .describe(\n 'Root directory for file operations. All keys are resolved relative to this path.',\n ),\n })\n .strict();\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Serve static files from a local directory, byte-exact (file mode) */\nexport const staticAssets: Store.Config = {\n settings: {\n basePath: './public',\n },\n file: true,\n};\n\n/** Structured key-value store on disk (default mode) */\nexport const structuredState: Store.Config = {\n settings: {\n basePath: './state',\n },\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a file that exists in the store directory (file mode, byte-exact). */\nexport const readExistingFile: Flow.StepExample = {\n title: 'Read file',\n description: 'Read an existing file and receive its raw bytes byte-exact',\n in: { operation: 'get', key: 'walker.js' },\n out: [['get', 'walker.js', 'Bytes<console.log(\"walkerOS\")>']],\n};\n\n/** Write a file with auto-created parent directories. */\nexport const writeNewFile: Flow.StepExample = {\n title: 'Write file',\n description: 'Write creates intermediate directories automatically',\n in: {\n operation: 'set',\n key: 'js/custom/tracker.js',\n value: 'Buffer<(function(){...})()>',\n },\n out: [['set', 'js/custom/tracker.js', 'Buffer<(function(){...})()>']],\n};\n\n/** Path traversal attempt is rejected for security. */\nexport const pathTraversalRejection: Flow.StepExample = {\n public: false,\n description: 'Path traversal via .. segments is rejected with a warning',\n in: { operation: 'get', key: '../../etc/passwd' },\n out: [['get', '../../etc/passwd', undefined]],\n};\n\n/** A read that fails for another reason than a missing file. */\nexport const failedRead: Flow.StepExample = {\n public: false,\n description:\n 'The file system fails the read with another error than a missing file (permission denied, a broken disk): get throws a StepError instead of answering a miss.',\n in: { operation: 'get', key: 'private.json' },\n respond: { error: 'network' },\n outcome: { status: 'failed', code: 'transport' },\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'file-mode': {\n text: 'Default mode is structured: values are StoreValue data round-tripped through the shared core codec, good for session state, lookups, and cached responses. Set config.file: true to persist raw bytes byte-exact (set accepts Uint8Array or string, get hands the exact bytes back), the mode for serving assets such as walker.js via the file transformer. One store instance is exactly one mode. Do not pair file: true with a cache; the cache wraps structured stores and flow_validate flags the combination.',\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,cAA4B;;;ACA5B,iBAAkB;AAEX,IAAM,iBAAiB,aAC3B,OAAO;AAAA,EACN,UAAU,aACP,OAAO,EACP,IAAI,CAAC,EACL;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EACA,OAAO;;;ADNH,IAAM,eAAW,yBAAY,cAAc;;;AELlD;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,mBAAqC;AAAA,EAChD,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,CAAC,CAAC,OAAO,aAAa,gCAAgC,CAAC;AAC9D;AAGO,IAAM,eAAiC;AAAA,EAC5C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,OAAO;AAAA,EACT;AAAA,EACA,KAAK,CAAC,CAAC,OAAO,wBAAwB,6BAA6B,CAAC;AACtE;AAGO,IAAM,yBAA2C;AAAA,EACtD,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,mBAAmB;AAAA,EAChD,KAAK,CAAC,CAAC,OAAO,oBAAoB,MAAS,CAAC;AAC9C;AAGO,IAAM,aAA+B;AAAA,EAC1C,QAAQ;AAAA,EACR,aACE;AAAA,EACF,IAAI,EAAE,WAAW,OAAO,KAAK,eAAe;AAAA,EAC5C,SAAS,EAAE,OAAO,UAAU;AAAA,EAC5B,SAAS,EAAE,QAAQ,UAAU,MAAM,YAAY;AACjD;;;ADnCO,IAAM,eAA6B;AAAA,EACxC,UAAU;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA,MAAM;AACR;AAGO,IAAM,kBAAgC;AAAA,EAC3C,UAAU;AAAA,IACR,UAAU;AAAA,EACZ;AACF;;;AEbO,IAAM,QAAoB;AAAA,EAC/B,aAAa;AAAA,IACX,MAAM;AAAA,EACR;AACF;","names":["import_dev"]}
package/dist/dev.mjs CHANGED
@@ -1 +1 @@
1
- var e=Object.defineProperty,t=(t,s)=>{for(var a in s)e(t,a,{get:s[a],enumerable:!0})},s={};t(s,{SettingsSchema:()=>i,settings:()=>o});import{zodToSchema as a}from"@walkeros/core/dev";import{z as r}from"@walkeros/core/dev";var i=r.object({basePath:r.string().min(1).describe("Root directory for file operations. All keys are resolved relative to this path.")}),o=a(i),c={};t(c,{staticAssets:()=>f,step:()=>n,structuredState:()=>p});var n={};t(n,{pathTraversalRejection:()=>u,readExistingFile:()=>l,writeNewFile:()=>d});var l={title:"Read file",description:"Read an existing file and receive its raw bytes byte-exact",in:{operation:"get",key:"walker.js"},out:[["get","walker.js",'Bytes<console.log("walkerOS")>']]},d={title:"Write file",description:"Write creates intermediate directories automatically",in:{operation:"set",key:"js/custom/tracker.js",value:"Buffer<(function(){...})()>"},out:[["set","js/custom/tracker.js","Buffer<(function(){...})()>"]]},u={public:!1,description:"Path traversal via .. segments is rejected with a warning",in:{operation:"get",key:"../../etc/passwd"},out:[["get","../../etc/passwd",void 0]]},f={settings:{basePath:"./public"},file:!0},p={settings:{basePath:"./state"}},h={"file-mode":{text:"Default mode is structured: values are StoreValue data round-tripped through the shared core codec, good for session state, lookups, and cached responses. Set config.file: true to persist raw bytes byte-exact (set accepts Uint8Array or string, get hands the exact bytes back), the mode for serving assets such as walker.js via the file transformer. One store instance is exactly one mode. Do not pair file: true with a cache; the cache wraps structured stores and flow_validate flags the combination."}};export{c as examples,h as hints,s as schemas};//# sourceMappingURL=dev.mjs.map
1
+ import{createRequire as e}from"module";e(import.meta.url);var t=Object.defineProperty,r=(e,r)=>{for(var s in r)t(e,s,{get:r[s],enumerable:!0})},s={};r(s,{SettingsSchema:()=>o,settings:()=>n});import{zodToSchema as a}from"@walkeros/core/dev";import{z as i}from"@walkeros/core/dev";var o=i.object({basePath:i.string().min(1).describe("Root directory for file operations. All keys are resolved relative to this path.")}).strict(),n=a(o),c={};r(c,{staticAssets:()=>h,step:()=>l,structuredState:()=>m});var l={};r(l,{failedRead:()=>u,pathTraversalRejection:()=>f,readExistingFile:()=>d,writeNewFile:()=>p});var d={title:"Read file",description:"Read an existing file and receive its raw bytes byte-exact",in:{operation:"get",key:"walker.js"},out:[["get","walker.js",'Bytes<console.log("walkerOS")>']]},p={title:"Write file",description:"Write creates intermediate directories automatically",in:{operation:"set",key:"js/custom/tracker.js",value:"Buffer<(function(){...})()>"},out:[["set","js/custom/tracker.js","Buffer<(function(){...})()>"]]},f={public:!1,description:"Path traversal via .. segments is rejected with a warning",in:{operation:"get",key:"../../etc/passwd"},out:[["get","../../etc/passwd",void 0]]},u={public:!1,description:"The file system fails the read with another error than a missing file (permission denied, a broken disk): get throws a StepError instead of answering a miss.",in:{operation:"get",key:"private.json"},respond:{error:"network"},outcome:{status:"failed",code:"transport"}},h={settings:{basePath:"./public"},file:!0},m={settings:{basePath:"./state"}},g={"file-mode":{text:"Default mode is structured: values are StoreValue data round-tripped through the shared core codec, good for session state, lookups, and cached responses. Set config.file: true to persist raw bytes byte-exact (set accepts Uint8Array or string, get hands the exact bytes back), the mode for serving assets such as walker.js via the file transformer. One store instance is exactly one mode. Do not pair file: true with a cache; the cache wraps structured stores and flow_validate flags the combination."}};export{c as examples,g as hints,s as schemas};//# sourceMappingURL=dev.mjs.map
package/dist/dev.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\n\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nexport const SettingsSchema = z.object({\n basePath: z\n .string()\n .min(1)\n .describe(\n 'Root directory for file operations. All keys are resolved relative to this path.',\n ),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Serve static files from a local directory, byte-exact (file mode) */\nexport const staticAssets: Store.Config = {\n settings: {\n basePath: './public',\n },\n file: true,\n};\n\n/** Structured key-value store on disk (default mode) */\nexport const structuredState: Store.Config = {\n settings: {\n basePath: './state',\n },\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a file that exists in the store directory (file mode, byte-exact). */\nexport const readExistingFile: Flow.StepExample = {\n title: 'Read file',\n description: 'Read an existing file and receive its raw bytes byte-exact',\n in: { operation: 'get', key: 'walker.js' },\n out: [['get', 'walker.js', 'Bytes<console.log(\"walkerOS\")>']],\n};\n\n/** Write a file with auto-created parent directories. */\nexport const writeNewFile: Flow.StepExample = {\n title: 'Write file',\n description: 'Write creates intermediate directories automatically',\n in: {\n operation: 'set',\n key: 'js/custom/tracker.js',\n value: 'Buffer<(function(){...})()>',\n },\n out: [['set', 'js/custom/tracker.js', 'Buffer<(function(){...})()>']],\n};\n\n/** Path traversal attempt is rejected for security. */\nexport const pathTraversalRejection: Flow.StepExample = {\n public: false,\n description: 'Path traversal via .. segments is rejected with a warning',\n in: { operation: 'get', key: '../../etc/passwd' },\n out: [['get', '../../etc/passwd', undefined]],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'file-mode': {\n text: 'Default mode is structured: values are StoreValue data round-tripped through the shared core codec, good for session state, lookups, and cached responses. Set config.file: true to persist raw bytes byte-exact (set accepts Uint8Array or string, get hands the exact bytes back), the mode for serving assets such as walker.js via the file transformer. One store instance is exactly one mode. Do not pair file: true with a cache; the cache wraps structured stores and flow_validate flags the combination.',\n },\n};\n"],"mappings":";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;;;ACA5B,SAAS,SAAS;AAEX,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,UAAU,EACP,OAAO,EACP,IAAI,CAAC,EACL;AAAA,IACC;AAAA,EACF;AACJ,CAAC;;;ADJM,IAAM,WAAW,YAAY,cAAc;;;AELlD;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,mBAAqC;AAAA,EAChD,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,CAAC,CAAC,OAAO,aAAa,gCAAgC,CAAC;AAC9D;AAGO,IAAM,eAAiC;AAAA,EAC5C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,OAAO;AAAA,EACT;AAAA,EACA,KAAK,CAAC,CAAC,OAAO,wBAAwB,6BAA6B,CAAC;AACtE;AAGO,IAAM,yBAA2C;AAAA,EACtD,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,mBAAmB;AAAA,EAChD,KAAK,CAAC,CAAC,OAAO,oBAAoB,MAAS,CAAC;AAC9C;;;ADzBO,IAAM,eAA6B;AAAA,EACxC,UAAU;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA,MAAM;AACR;AAGO,IAAM,kBAAgC;AAAA,EAC3C,UAAU;AAAA,IACR,UAAU;AAAA,EACZ;AACF;;;AEbO,IAAM,QAAoB;AAAA,EAC/B,aAAa;AAAA,IACX,MAAM;AAAA,EACR;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\n\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nexport const SettingsSchema = z\n .object({\n basePath: z\n .string()\n .min(1)\n .describe(\n 'Root directory for file operations. All keys are resolved relative to this path.',\n ),\n })\n .strict();\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Serve static files from a local directory, byte-exact (file mode) */\nexport const staticAssets: Store.Config = {\n settings: {\n basePath: './public',\n },\n file: true,\n};\n\n/** Structured key-value store on disk (default mode) */\nexport const structuredState: Store.Config = {\n settings: {\n basePath: './state',\n },\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a file that exists in the store directory (file mode, byte-exact). */\nexport const readExistingFile: Flow.StepExample = {\n title: 'Read file',\n description: 'Read an existing file and receive its raw bytes byte-exact',\n in: { operation: 'get', key: 'walker.js' },\n out: [['get', 'walker.js', 'Bytes<console.log(\"walkerOS\")>']],\n};\n\n/** Write a file with auto-created parent directories. */\nexport const writeNewFile: Flow.StepExample = {\n title: 'Write file',\n description: 'Write creates intermediate directories automatically',\n in: {\n operation: 'set',\n key: 'js/custom/tracker.js',\n value: 'Buffer<(function(){...})()>',\n },\n out: [['set', 'js/custom/tracker.js', 'Buffer<(function(){...})()>']],\n};\n\n/** Path traversal attempt is rejected for security. */\nexport const pathTraversalRejection: Flow.StepExample = {\n public: false,\n description: 'Path traversal via .. segments is rejected with a warning',\n in: { operation: 'get', key: '../../etc/passwd' },\n out: [['get', '../../etc/passwd', undefined]],\n};\n\n/** A read that fails for another reason than a missing file. */\nexport const failedRead: Flow.StepExample = {\n public: false,\n description:\n 'The file system fails the read with another error than a missing file (permission denied, a broken disk): get throws a StepError instead of answering a miss.',\n in: { operation: 'get', key: 'private.json' },\n respond: { error: 'network' },\n outcome: { status: 'failed', code: 'transport' },\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'file-mode': {\n text: 'Default mode is structured: values are StoreValue data round-tripped through the shared core codec, good for session state, lookups, and cached responses. Set config.file: true to persist raw bytes byte-exact (set accepts Uint8Array or string, get hands the exact bytes back), the mode for serving assets such as walker.js via the file transformer. One store instance is exactly one mode. Do not pair file: true with a cache; the cache wraps structured stores and flow_validate flags the combination.',\n },\n};\n"],"mappings":";;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;;;ACA5B,SAAS,SAAS;AAEX,IAAM,iBAAiB,EAC3B,OAAO;AAAA,EACN,UAAU,EACP,OAAO,EACP,IAAI,CAAC,EACL;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EACA,OAAO;;;ADNH,IAAM,WAAW,YAAY,cAAc;;;AELlD;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,mBAAqC;AAAA,EAChD,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,CAAC,CAAC,OAAO,aAAa,gCAAgC,CAAC;AAC9D;AAGO,IAAM,eAAiC;AAAA,EAC5C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,OAAO;AAAA,EACT;AAAA,EACA,KAAK,CAAC,CAAC,OAAO,wBAAwB,6BAA6B,CAAC;AACtE;AAGO,IAAM,yBAA2C;AAAA,EACtD,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,mBAAmB;AAAA,EAChD,KAAK,CAAC,CAAC,OAAO,oBAAoB,MAAS,CAAC;AAC9C;AAGO,IAAM,aAA+B;AAAA,EAC1C,QAAQ;AAAA,EACR,aACE;AAAA,EACF,IAAI,EAAE,WAAW,OAAO,KAAK,eAAe;AAAA,EAC5C,SAAS,EAAE,OAAO,UAAU;AAAA,EAC5B,SAAS,EAAE,QAAQ,UAAU,MAAM,YAAY;AACjD;;;ADnCO,IAAM,eAA6B;AAAA,EACxC,UAAU;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA,MAAM;AACR;AAGO,IAAM,kBAAgC;AAAA,EAC3C,UAAU;AAAA,IACR,UAAU;AAAA,EACZ;AACF;;;AEbO,IAAM,QAAoB;AAAA,EAC/B,aAAa;AAAA,IACX,MAAM;AAAA,EACR;AACF;","names":[]}
package/dist/index.d.mts CHANGED
@@ -4,7 +4,21 @@ interface FsStoreSettings {
4
4
  /** Root directory for file lookups. All keys resolved relative to this. */
5
5
  basePath: string;
6
6
  }
7
+ /** The file operations the store uses; `fs/promises` by default. */
8
+ interface FsStoreFs {
9
+ readFile(path: string): Promise<Uint8Array>;
10
+ writeFile(path: string, data: Uint8Array | string): Promise<void>;
11
+ mkdir(path: string, options: {
12
+ recursive: true;
13
+ }): Promise<unknown>;
14
+ unlink(path: string): Promise<void>;
15
+ }
16
+ interface FsStoreEnv {
17
+ /** Replaces `fs/promises`, for tests and simulation. */
18
+ fs?: FsStoreFs;
19
+ }
7
20
 
8
- declare const storeFsInit: Store.Init<Store.Types<FsStoreSettings>>;
21
+ type Types = Store.Types<FsStoreSettings, FsStoreEnv>;
22
+ declare const storeFsInit: Store.Init<Types>;
9
23
 
10
24
  export { type FsStoreSettings, storeFsInit as default, storeFsInit };
package/dist/index.d.ts CHANGED
@@ -4,7 +4,21 @@ interface FsStoreSettings {
4
4
  /** Root directory for file lookups. All keys resolved relative to this. */
5
5
  basePath: string;
6
6
  }
7
+ /** The file operations the store uses; `fs/promises` by default. */
8
+ interface FsStoreFs {
9
+ readFile(path: string): Promise<Uint8Array>;
10
+ writeFile(path: string, data: Uint8Array | string): Promise<void>;
11
+ mkdir(path: string, options: {
12
+ recursive: true;
13
+ }): Promise<unknown>;
14
+ unlink(path: string): Promise<void>;
15
+ }
16
+ interface FsStoreEnv {
17
+ /** Replaces `fs/promises`, for tests and simulation. */
18
+ fs?: FsStoreFs;
19
+ }
7
20
 
8
- declare const storeFsInit: Store.Init<Store.Types<FsStoreSettings>>;
21
+ type Types = Store.Types<FsStoreSettings, FsStoreEnv>;
22
+ declare const storeFsInit: Store.Init<Types>;
9
23
 
10
24
  export { type FsStoreSettings, storeFsInit as default, storeFsInit };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,r=Object.create,t=Object.defineProperty,i=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,s=(e,r,o,s)=>{if(r&&"object"==typeof r||"function"==typeof r)for(let c of n(r))a.call(e,c)||c===o||t(e,c,{get:()=>r[c],enumerable:!(s=i(r,c))||s.enumerable});return e},c=(e,i,n)=>(n=null!=e?r(o(e)):{},s(!i&&e&&e.__esModule?n:t(n,"default",{value:e,enumerable:!0}),e)),l={};((e,r)=>{for(var i in r)t(e,i,{get:r[i],enumerable:!0})})(l,{default:()=>g,storeFsInit:()=>g}),module.exports=(e=l,s(t({},"__esModule",{value:!0}),e));var u=c(require("fs/promises")),f=c(require("path")),d=require("@walkeros/core");var g=e=>{const r=e.config.settings,t=f.resolve(r.basePath),i=!0===e.config.file;function n(r){if(!function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(r))return void e.logger.warn("Path traversal rejected",{key:r});const i=f.join(t,r);return i.startsWith(t+f.sep)||i===t?i:void 0}return{type:"fs",config:e.config,async get(r){const t=n(r);if(!t)return;let o;try{o=await u.readFile(t)}catch{return}if(i)return o;try{return(0,d.deserializeStoreValue)(o)}catch(t){return void e.logger.debug("structured decode failed, degrading to miss",{key:r,error:t instanceof Error?t.message:String(t)})}},async set(e,r){const t=n(e);if(t)if(await u.mkdir(f.dirname(t),{recursive:!0}),i){if(!(r instanceof Uint8Array)&&"string"!=typeof r)throw new Error("fs store in file mode persists Uint8Array or string values only, received "+typeof r);await u.writeFile(t,r)}else await u.writeFile(t,(0,d.serializeStoreValue)(r))},async delete(e){const r=n(e);if(r)try{await u.unlink(r)}catch{}}}};//# sourceMappingURL=index.js.map
1
+ "use strict";var e,t=Object.create,r=Object.defineProperty,o=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,i=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,a=(e,t,i,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of n(t))s.call(e,c)||c===i||r(e,c,{get:()=>t[c],enumerable:!(a=o(t,c))||a.enumerable});return e},c=(e,o,n)=>(n=null!=e?t(i(e)):{},a(!o&&e&&e.__esModule?n:r(n,"default",{value:e,enumerable:!0}),e)),f={};((e,t)=>{for(var o in t)r(e,o,{get:t[o],enumerable:!0})})(f,{default:()=>h,storeFsInit:()=>h}),module.exports=(e=f,a(r({},"__esModule",{value:!0}),e));var u=c(require("fs/promises")),l=c(require("path")),d=require("@walkeros/core"),p=require("@walkeros/core/node");function y(e){if("object"==typeof e&&null!==e&&"code"in e)return"string"==typeof e.code?e.code:void 0}function g(e){const t=y(e);return"ENOENT"===t||"ENOTDIR"===t||"EISDIR"===t}function w(e,t){const r=y(t);return new d.StepError(`fs store ${e} failed${r?` (${r})`:""}`,{code:"transport",cause:t})}var h=e=>{const t=e.config.settings||{};if(!t.basePath)throw new d.StepError("Config settings basePath missing",{code:"config"});const r={...e.config,settings:{basePath:t.basePath}},o=l.resolve(t.basePath),n=!0===e.config.file,i=e.env.fs??u;function s(t){if(!function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(t))return void e.logger.warn("Path traversal rejected",{key:t});const r=l.join(o,t);return r.startsWith(o+l.sep)||r===o?r:void 0}return{type:"fs",config:r,async get(e){const t=s(e);if(!t)return;let r;try{r=await i.readFile(t)}catch(e){if(g(e))return;throw w("read",e)}if(n)return r;try{return(0,p.deserializeStoreValue)(r)}catch(e){throw new d.StepError("fs store value could not be decoded",{code:"decode",retryable:!1,cause:e})}},async set(e,t){const r=s(e);if(!r)return;let o;if(n){if(!(t instanceof Uint8Array||"string"==typeof t))throw new d.StepError("fs store in file mode persists Uint8Array or string values only, received "+typeof t,{code:"config"});o=t}else o=(0,p.serializeStoreValue)(t);try{await i.mkdir(l.dirname(r),{recursive:!0}),await i.writeFile(r,o)}catch(e){throw w("write",e)}},async delete(e){const t=s(e);if(t)try{await i.unlink(t)}catch(e){if(!g(e))throw w("delete",e)}}}};//# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/store.ts"],"sourcesContent":["export { storeFsInit } from './store';\nexport type { FsStoreSettings } from './types';\nexport { storeFsInit as default } from './store';\n","import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport type { Store } from '@walkeros/core';\nimport { serializeStoreValue, deserializeStoreValue } from '@walkeros/core';\nimport type { FsStoreSettings } from './types';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nexport const storeFsInit: Store.Init<Store.Types<FsStoreSettings>> = (\n context,\n) => {\n const settings = context.config.settings as FsStoreSettings;\n const basePath = path.resolve(settings.basePath);\n // One mode per instance, decided at init. file:true persists raw bytes\n // byte-exact; the default structured mode round-trips StoreValue through the\n // shared core codec (utf8 JSON, binary leaves base64-tagged).\n const fileMode = context.config.file === true;\n\n function resolvePath(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Path traversal rejected', { key });\n return undefined;\n }\n const resolved = path.join(basePath, key);\n if (!resolved.startsWith(basePath + path.sep) && resolved !== basePath)\n return undefined;\n return resolved;\n }\n\n return {\n type: 'fs',\n config: context.config as Store.Config<Store.Types<FsStoreSettings>>,\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const filePath = resolvePath(key);\n if (!filePath) return undefined;\n let bytes: Buffer;\n try {\n bytes = await fs.readFile(filePath);\n } catch {\n return undefined;\n }\n // File mode hands the raw bytes back untouched (Buffer is a Uint8Array,\n // a valid StoreValue leaf). Structured mode decodes the utf8-JSON payload\n // to a StoreValue (binary leaves become Uint8Array). A corrupt or empty\n // payload degrades to a miss rather than throwing a raw SyntaxError.\n if (fileMode) return bytes;\n try {\n return deserializeStoreValue(bytes);\n } catch (err) {\n context.logger.debug('structured decode failed, degrading to miss', {\n key,\n error: err instanceof Error ? err.message : String(err),\n });\n return undefined;\n }\n },\n\n async set(key: string, value: Store.StoreValue): Promise<void> {\n const filePath = resolvePath(key);\n if (!filePath) return;\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n\n if (fileMode) {\n // Byte-exact passthrough: accept Uint8Array (Buffer included) or string.\n if (!(value instanceof Uint8Array) && typeof value !== 'string') {\n throw new Error(\n `fs store in file mode persists Uint8Array or string values only, received ${typeof value}`,\n );\n }\n await fs.writeFile(filePath, value);\n return;\n }\n\n // Structured mode: any StoreValue serializes to a utf8-JSON byte payload.\n await fs.writeFile(filePath, serializeStoreValue(value));\n },\n\n async delete(key: string): Promise<void> {\n const filePath = resolvePath(key);\n if (!filePath) return;\n try {\n await fs.unlink(filePath);\n } catch {\n /* ignore */\n }\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,SAAoB;AACpB,WAAsB;AAEtB,kBAA2D;AAG3D,SAAS,WAAW,KAAsB;AACxC,MAAI,CAAC,OAAO,IAAI,WAAW,GAAG,KAAK,IAAI,WAAW,IAAI,EAAG,QAAO;AAChE,SAAO,CAAC,IAAI,MAAM,OAAO,EAAE,SAAS,IAAI;AAC1C;AAEO,IAAM,cAAwD,CACnE,YACG;AACH,QAAM,WAAW,QAAQ,OAAO;AAChC,QAAM,WAAgB,aAAQ,SAAS,QAAQ;AAI/C,QAAM,WAAW,QAAQ,OAAO,SAAS;AAEzC,WAAS,YAAY,KAAiC;AACpD,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,cAAQ,OAAO,KAAK,2BAA2B,EAAE,IAAI,CAAC;AACtD,aAAO;AAAA,IACT;AACA,UAAM,WAAgB,UAAK,UAAU,GAAG;AACxC,QAAI,CAAC,SAAS,WAAW,WAAgB,QAAG,KAAK,aAAa;AAC5D,aAAO;AACT,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,QAAQ;AAAA,IAEhB,MAAM,IAAI,KAAoD;AAC5D,YAAM,WAAW,YAAY,GAAG;AAChC,UAAI,CAAC,SAAU,QAAO;AACtB,UAAI;AACJ,UAAI;AACF,gBAAQ,MAAS,YAAS,QAAQ;AAAA,MACpC,QAAQ;AACN,eAAO;AAAA,MACT;AAKA,UAAI,SAAU,QAAO;AACrB,UAAI;AACF,mBAAO,mCAAsB,KAAK;AAAA,MACpC,SAAS,KAAK;AACZ,gBAAQ,OAAO,MAAM,+CAA+C;AAAA,UAClE;AAAA,UACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,MAAM,IAAI,KAAa,OAAwC;AAC7D,YAAM,WAAW,YAAY,GAAG;AAChC,UAAI,CAAC,SAAU;AACf,YAAS,SAAW,aAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAE1D,UAAI,UAAU;AAEZ,YAAI,EAAE,iBAAiB,eAAe,OAAO,UAAU,UAAU;AAC/D,gBAAM,IAAI;AAAA,YACR,6EAA6E,OAAO,KAAK;AAAA,UAC3F;AAAA,QACF;AACA,cAAS,aAAU,UAAU,KAAK;AAClC;AAAA,MACF;AAGA,YAAS,aAAU,cAAU,iCAAoB,KAAK,CAAC;AAAA,IACzD;AAAA,IAEA,MAAM,OAAO,KAA4B;AACvC,YAAM,WAAW,YAAY,GAAG;AAChC,UAAI,CAAC,SAAU;AACf,UAAI;AACF,cAAS,UAAO,QAAQ;AAAA,MAC1B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/store.ts"],"sourcesContent":["export { storeFsInit } from './store';\nexport type { FsStoreSettings } from './types';\nexport { storeFsInit as default } from './store';\n","import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport type { Store } from '@walkeros/core';\nimport { StepError } from '@walkeros/core';\nimport {\n serializeStoreValue,\n deserializeStoreValue,\n} from '@walkeros/core/node';\nimport type { FsStoreEnv, FsStoreFs, FsStoreSettings } from './types';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\n/** The error code of a failed file operation (`ENOENT`, `EACCES`). */\nfunction errorCode(err: unknown): string | undefined {\n // Duck-typed: errors from node:fs may come from another realm.\n if (typeof err !== 'object' || err === null || !('code' in err)) return;\n return typeof err.code === 'string' ? err.code : undefined;\n}\n\n/**\n * No file is stored at the key: it does not exist, a directory on its path\n * is a file, or the key names a directory. A miss.\n */\nfunction isMissing(err: unknown): boolean {\n const code = errorCode(err);\n return code === 'ENOENT' || code === 'ENOTDIR' || code === 'EISDIR';\n}\n\n/** A failed file operation as a StepError, its message without the key. */\nfunction storeError(operation: string, err: unknown): StepError {\n const code = errorCode(err);\n return new StepError(\n `fs store ${operation} failed${code ? ` (${code})` : ''}`,\n { code: 'transport', cause: err },\n );\n}\n\ntype Types = Store.Types<FsStoreSettings, FsStoreEnv>;\n\nexport const storeFsInit: Store.Init<Types> = (context) => {\n const settings: Partial<FsStoreSettings> = context.config.settings || {};\n if (!settings.basePath)\n throw new StepError('Config settings basePath missing', {\n code: 'config',\n });\n const config: Store.Config<Types> = {\n ...context.config,\n settings: { basePath: settings.basePath },\n };\n const basePath = path.resolve(settings.basePath);\n // One mode per instance, decided at init. file:true persists raw bytes\n // byte-exact; the default structured mode round-trips StoreValue through the\n // shared core codec (utf8 JSON, binary leaves base64-tagged).\n const fileMode = context.config.file === true;\n // An injected fs (tests, simulate); the file system otherwise.\n const files: FsStoreFs = context.env.fs ?? fs;\n\n function resolvePath(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Path traversal rejected', { key });\n return undefined;\n }\n const resolved = path.join(basePath, key);\n if (!resolved.startsWith(basePath + path.sep) && resolved !== basePath)\n return undefined;\n return resolved;\n }\n\n return {\n type: 'fs',\n config,\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const filePath = resolvePath(key);\n if (!filePath) return undefined;\n let bytes: Uint8Array;\n try {\n bytes = await files.readFile(filePath);\n } catch (err) {\n // A missing file is a miss; any other failed read is reported.\n if (isMissing(err)) return undefined;\n throw storeError('read', err);\n }\n // File mode hands the raw bytes back untouched (Buffer is a Uint8Array,\n // a valid StoreValue leaf). Structured mode decodes the utf8-JSON\n // payload; a stored value that does not decode (corrupt or empty) is a\n // failed read, never a miss. The message names no value.\n if (fileMode) return bytes;\n try {\n return deserializeStoreValue(bytes);\n } catch (err) {\n throw new StepError('fs store value could not be decoded', {\n code: 'decode',\n retryable: false,\n cause: err,\n });\n }\n },\n\n async set(key: string, value: Store.StoreValue): Promise<void> {\n const filePath = resolvePath(key);\n if (!filePath) return;\n\n // Byte-exact passthrough in file mode: Uint8Array (Buffer included) or\n // string. Structured mode: any StoreValue as a utf8-JSON byte payload.\n let bytes: Uint8Array | string;\n if (!fileMode) bytes = serializeStoreValue(value);\n else if (value instanceof Uint8Array || typeof value === 'string')\n bytes = value;\n else\n throw new StepError(\n `fs store in file mode persists Uint8Array or string values only, received ${typeof value}`,\n { code: 'config' },\n );\n\n try {\n await files.mkdir(path.dirname(filePath), { recursive: true });\n await files.writeFile(filePath, bytes);\n } catch (err) {\n throw storeError('write', err);\n }\n },\n\n async delete(key: string): Promise<void> {\n const filePath = resolvePath(key);\n if (!filePath) return;\n try {\n await files.unlink(filePath);\n } catch (err) {\n // Deleting a missing file is a no-op; any other failure is reported.\n if (!isMissing(err)) throw storeError('delete', err);\n }\n },\n };\n};\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import*as r from"fs/promises";import*as t from"path";import{serializeStoreValue as e,deserializeStoreValue as i}from"@walkeros/core";var s=s=>{const n=s.config.settings,a=t.resolve(n.basePath),o=!0===s.config.file;function c(r){if(!function(r){return!(!r||r.startsWith("/")||r.startsWith("\\")||r.split(/[/\\]/).includes(".."))}(r))return void s.logger.warn("Path traversal rejected",{key:r});const e=t.join(a,r);return e.startsWith(a+t.sep)||e===a?e:void 0}return{type:"fs",config:s.config,async get(t){const e=c(t);if(!e)return;let n;try{n=await r.readFile(e)}catch{return}if(o)return n;try{return i(n)}catch(r){return void s.logger.debug("structured decode failed, degrading to miss",{key:t,error:r instanceof Error?r.message:String(r)})}},async set(i,s){const n=c(i);if(n)if(await r.mkdir(t.dirname(n),{recursive:!0}),o){if(!(s instanceof Uint8Array)&&"string"!=typeof s)throw new Error("fs store in file mode persists Uint8Array or string values only, received "+typeof s);await r.writeFile(n,s)}else await r.writeFile(n,e(s))},async delete(t){const e=c(t);if(e)try{await r.unlink(e)}catch{}}}};export{s as default,s as storeFsInit};//# sourceMappingURL=index.mjs.map
1
+ import{createRequire as t}from"module";t(import.meta.url);import*as e from"fs/promises";import*as r from"path";import{StepError as o}from"@walkeros/core";import{serializeStoreValue as i,deserializeStoreValue as n}from"@walkeros/core/node";function s(t){if("object"==typeof t&&null!==t&&"code"in t)return"string"==typeof t.code?t.code:void 0}function a(t){const e=s(t);return"ENOENT"===e||"ENOTDIR"===e||"EISDIR"===e}function c(t,e){const r=s(e);return new o(`fs store ${t} failed${r?` (${r})`:""}`,{code:"transport",cause:e})}var f=t=>{const s=t.config.settings||{};if(!s.basePath)throw new o("Config settings basePath missing",{code:"config"});const f={...t.config,settings:{basePath:s.basePath}},d=r.resolve(s.basePath),u=!0===t.config.file,l=t.env.fs??e;function h(e){if(!function(t){return!(!t||t.startsWith("/")||t.startsWith("\\")||t.split(/[/\\]/).includes(".."))}(e))return void t.logger.warn("Path traversal rejected",{key:e});const o=r.join(d,e);return o.startsWith(d+r.sep)||o===d?o:void 0}return{type:"fs",config:f,async get(t){const e=h(t);if(!e)return;let r;try{r=await l.readFile(e)}catch(t){if(a(t))return;throw c("read",t)}if(u)return r;try{return n(r)}catch(t){throw new o("fs store value could not be decoded",{code:"decode",retryable:!1,cause:t})}},async set(t,e){const n=h(t);if(!n)return;let s;if(u){if(!(e instanceof Uint8Array||"string"==typeof e))throw new o("fs store in file mode persists Uint8Array or string values only, received "+typeof e,{code:"config"});s=e}else s=i(e);try{await l.mkdir(r.dirname(n),{recursive:!0}),await l.writeFile(n,s)}catch(t){throw c("write",t)}},async delete(t){const e=h(t);if(e)try{await l.unlink(e)}catch(t){if(!a(t))throw c("delete",t)}}}};export{f as default,f as storeFsInit};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/store.ts"],"sourcesContent":["import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport type { Store } from '@walkeros/core';\nimport { serializeStoreValue, deserializeStoreValue } from '@walkeros/core';\nimport type { FsStoreSettings } from './types';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nexport const storeFsInit: Store.Init<Store.Types<FsStoreSettings>> = (\n context,\n) => {\n const settings = context.config.settings as FsStoreSettings;\n const basePath = path.resolve(settings.basePath);\n // One mode per instance, decided at init. file:true persists raw bytes\n // byte-exact; the default structured mode round-trips StoreValue through the\n // shared core codec (utf8 JSON, binary leaves base64-tagged).\n const fileMode = context.config.file === true;\n\n function resolvePath(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Path traversal rejected', { key });\n return undefined;\n }\n const resolved = path.join(basePath, key);\n if (!resolved.startsWith(basePath + path.sep) && resolved !== basePath)\n return undefined;\n return resolved;\n }\n\n return {\n type: 'fs',\n config: context.config as Store.Config<Store.Types<FsStoreSettings>>,\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const filePath = resolvePath(key);\n if (!filePath) return undefined;\n let bytes: Buffer;\n try {\n bytes = await fs.readFile(filePath);\n } catch {\n return undefined;\n }\n // File mode hands the raw bytes back untouched (Buffer is a Uint8Array,\n // a valid StoreValue leaf). Structured mode decodes the utf8-JSON payload\n // to a StoreValue (binary leaves become Uint8Array). A corrupt or empty\n // payload degrades to a miss rather than throwing a raw SyntaxError.\n if (fileMode) return bytes;\n try {\n return deserializeStoreValue(bytes);\n } catch (err) {\n context.logger.debug('structured decode failed, degrading to miss', {\n key,\n error: err instanceof Error ? err.message : String(err),\n });\n return undefined;\n }\n },\n\n async set(key: string, value: Store.StoreValue): Promise<void> {\n const filePath = resolvePath(key);\n if (!filePath) return;\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n\n if (fileMode) {\n // Byte-exact passthrough: accept Uint8Array (Buffer included) or string.\n if (!(value instanceof Uint8Array) && typeof value !== 'string') {\n throw new Error(\n `fs store in file mode persists Uint8Array or string values only, received ${typeof value}`,\n );\n }\n await fs.writeFile(filePath, value);\n return;\n }\n\n // Structured mode: any StoreValue serializes to a utf8-JSON byte payload.\n await fs.writeFile(filePath, serializeStoreValue(value));\n },\n\n async delete(key: string): Promise<void> {\n const filePath = resolvePath(key);\n if (!filePath) return;\n try {\n await fs.unlink(filePath);\n } catch {\n /* ignore */\n }\n },\n };\n};\n"],"mappings":";AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAEtB,SAAS,qBAAqB,6BAA6B;AAG3D,SAAS,WAAW,KAAsB;AACxC,MAAI,CAAC,OAAO,IAAI,WAAW,GAAG,KAAK,IAAI,WAAW,IAAI,EAAG,QAAO;AAChE,SAAO,CAAC,IAAI,MAAM,OAAO,EAAE,SAAS,IAAI;AAC1C;AAEO,IAAM,cAAwD,CACnE,YACG;AACH,QAAM,WAAW,QAAQ,OAAO;AAChC,QAAM,WAAgB,aAAQ,SAAS,QAAQ;AAI/C,QAAM,WAAW,QAAQ,OAAO,SAAS;AAEzC,WAAS,YAAY,KAAiC;AACpD,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,cAAQ,OAAO,KAAK,2BAA2B,EAAE,IAAI,CAAC;AACtD,aAAO;AAAA,IACT;AACA,UAAM,WAAgB,UAAK,UAAU,GAAG;AACxC,QAAI,CAAC,SAAS,WAAW,WAAgB,QAAG,KAAK,aAAa;AAC5D,aAAO;AACT,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,QAAQ;AAAA,IAEhB,MAAM,IAAI,KAAoD;AAC5D,YAAM,WAAW,YAAY,GAAG;AAChC,UAAI,CAAC,SAAU,QAAO;AACtB,UAAI;AACJ,UAAI;AACF,gBAAQ,MAAS,YAAS,QAAQ;AAAA,MACpC,QAAQ;AACN,eAAO;AAAA,MACT;AAKA,UAAI,SAAU,QAAO;AACrB,UAAI;AACF,eAAO,sBAAsB,KAAK;AAAA,MACpC,SAAS,KAAK;AACZ,gBAAQ,OAAO,MAAM,+CAA+C;AAAA,UAClE;AAAA,UACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,MAAM,IAAI,KAAa,OAAwC;AAC7D,YAAM,WAAW,YAAY,GAAG;AAChC,UAAI,CAAC,SAAU;AACf,YAAS,SAAW,aAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAE1D,UAAI,UAAU;AAEZ,YAAI,EAAE,iBAAiB,eAAe,OAAO,UAAU,UAAU;AAC/D,gBAAM,IAAI;AAAA,YACR,6EAA6E,OAAO,KAAK;AAAA,UAC3F;AAAA,QACF;AACA,cAAS,aAAU,UAAU,KAAK;AAClC;AAAA,MACF;AAGA,YAAS,aAAU,UAAU,oBAAoB,KAAK,CAAC;AAAA,IACzD;AAAA,IAEA,MAAM,OAAO,KAA4B;AACvC,YAAM,WAAW,YAAY,GAAG;AAChC,UAAI,CAAC,SAAU;AACf,UAAI;AACF,cAAS,UAAO,QAAQ;AAAA,MAC1B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/store.ts"],"sourcesContent":["import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport type { Store } from '@walkeros/core';\nimport { StepError } from '@walkeros/core';\nimport {\n serializeStoreValue,\n deserializeStoreValue,\n} from '@walkeros/core/node';\nimport type { FsStoreEnv, FsStoreFs, FsStoreSettings } from './types';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\n/** The error code of a failed file operation (`ENOENT`, `EACCES`). */\nfunction errorCode(err: unknown): string | undefined {\n // Duck-typed: errors from node:fs may come from another realm.\n if (typeof err !== 'object' || err === null || !('code' in err)) return;\n return typeof err.code === 'string' ? err.code : undefined;\n}\n\n/**\n * No file is stored at the key: it does not exist, a directory on its path\n * is a file, or the key names a directory. A miss.\n */\nfunction isMissing(err: unknown): boolean {\n const code = errorCode(err);\n return code === 'ENOENT' || code === 'ENOTDIR' || code === 'EISDIR';\n}\n\n/** A failed file operation as a StepError, its message without the key. */\nfunction storeError(operation: string, err: unknown): StepError {\n const code = errorCode(err);\n return new StepError(\n `fs store ${operation} failed${code ? ` (${code})` : ''}`,\n { code: 'transport', cause: err },\n );\n}\n\ntype Types = Store.Types<FsStoreSettings, FsStoreEnv>;\n\nexport const storeFsInit: Store.Init<Types> = (context) => {\n const settings: Partial<FsStoreSettings> = context.config.settings || {};\n if (!settings.basePath)\n throw new StepError('Config settings basePath missing', {\n code: 'config',\n });\n const config: Store.Config<Types> = {\n ...context.config,\n settings: { basePath: settings.basePath },\n };\n const basePath = path.resolve(settings.basePath);\n // One mode per instance, decided at init. file:true persists raw bytes\n // byte-exact; the default structured mode round-trips StoreValue through the\n // shared core codec (utf8 JSON, binary leaves base64-tagged).\n const fileMode = context.config.file === true;\n // An injected fs (tests, simulate); the file system otherwise.\n const files: FsStoreFs = context.env.fs ?? fs;\n\n function resolvePath(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Path traversal rejected', { key });\n return undefined;\n }\n const resolved = path.join(basePath, key);\n if (!resolved.startsWith(basePath + path.sep) && resolved !== basePath)\n return undefined;\n return resolved;\n }\n\n return {\n type: 'fs',\n config,\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const filePath = resolvePath(key);\n if (!filePath) return undefined;\n let bytes: Uint8Array;\n try {\n bytes = await files.readFile(filePath);\n } catch (err) {\n // A missing file is a miss; any other failed read is reported.\n if (isMissing(err)) return undefined;\n throw storeError('read', err);\n }\n // File mode hands the raw bytes back untouched (Buffer is a Uint8Array,\n // a valid StoreValue leaf). Structured mode decodes the utf8-JSON\n // payload; a stored value that does not decode (corrupt or empty) is a\n // failed read, never a miss. The message names no value.\n if (fileMode) return bytes;\n try {\n return deserializeStoreValue(bytes);\n } catch (err) {\n throw new StepError('fs store value could not be decoded', {\n code: 'decode',\n retryable: false,\n cause: err,\n });\n }\n },\n\n async set(key: string, value: Store.StoreValue): Promise<void> {\n const filePath = resolvePath(key);\n if (!filePath) return;\n\n // Byte-exact passthrough in file mode: Uint8Array (Buffer included) or\n // string. Structured mode: any StoreValue as a utf8-JSON byte payload.\n let bytes: Uint8Array | string;\n if (!fileMode) bytes = serializeStoreValue(value);\n else if (value instanceof Uint8Array || typeof value === 'string')\n bytes = value;\n else\n throw new StepError(\n `fs store in file mode persists Uint8Array or string values only, received ${typeof value}`,\n { code: 'config' },\n );\n\n try {\n await files.mkdir(path.dirname(filePath), { recursive: true });\n await files.writeFile(filePath, bytes);\n } catch (err) {\n throw storeError('write', err);\n }\n },\n\n async delete(key: string): Promise<void> {\n const filePath = resolvePath(key);\n if (!filePath) return;\n try {\n await files.unlink(filePath);\n } catch (err) {\n // Deleting a missing file is a no-op; any other failure is reported.\n if (!isMissing(err)) throw storeError('delete', err);\n }\n },\n };\n};\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$meta": {
3
3
  "package": "@walkeros/server-store-fs",
4
- "version": "4.8.1",
4
+ "version": "4.8.2",
5
5
  "type": "store",
6
6
  "platform": [
7
7
  "server"
@@ -35,6 +35,21 @@
35
35
  "file": true
36
36
  },
37
37
  "step": {
38
+ "failedRead": {
39
+ "public": false,
40
+ "description": "The file system fails the read with another error than a missing file (permission denied, a broken disk): get throws a StepError instead of answering a miss.",
41
+ "in": {
42
+ "operation": "get",
43
+ "key": "private.json"
44
+ },
45
+ "respond": {
46
+ "error": "network"
47
+ },
48
+ "outcome": {
49
+ "status": "failed",
50
+ "code": "transport"
51
+ }
52
+ },
38
53
  "pathTraversalRejection": {
39
54
  "public": false,
40
55
  "description": "Path traversal via .. segments is rejected with a warning",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walkeros/server-store-fs",
3
3
  "description": "Filesystem store for walkerOS server - reads and writes files via the Store interface",
4
- "version": "4.8.1",
4
+ "version": "4.8.2",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -33,10 +33,11 @@
33
33
  "update": "npx npm-check-updates -u && npm update"
34
34
  },
35
35
  "dependencies": {
36
- "@walkeros/core": "4.8.1"
36
+ "@walkeros/core": "4.8.2"
37
37
  },
38
38
  "devDependencies": {
39
- "@walkeros/core": "4.8.1"
39
+ "@walkeros/collector": "4.8.2",
40
+ "@walkeros/core": "4.8.2"
40
41
  },
41
42
  "repository": {
42
43
  "url": "git+https://github.com/elbwalker/walkerOS.git",