@walkeros/server-store-s3 4.7.0-next-1790722899482 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @walkeros/server-store-s3
2
2
 
3
- ## 4.7.0-next-1790722899482
3
+ ## 4.7.0
4
4
 
5
5
  ### Patch Changes
6
6
 
@@ -10,9 +10,16 @@
10
10
  types of BigQuery, Firehose, PostHog, SQS and Pub/Sub are structural.
11
11
  `@walkeros/cli` exports `resolveExportName` and `selectDevExamples` and drops
12
12
  `findExample` and `compareOutput`.
13
+ - e860006: `state` gains `mapping` to shape what a lookup returns or a write
14
+ stores, merged into the target. An undeclared `state.store`, or a `file: true`
15
+ store, is now a validation error in `walkeros validate` and deploy preflight;
16
+ at runtime the entry logs an error or is skipped with a warning. The s3 and
17
+ gcs stores report their `file` mode.
13
18
  - Updated dependencies [74821ed]
14
19
  - Updated dependencies [06b498a]
15
20
  - Updated dependencies [74821ed]
21
+ - Updated dependencies [64b06de]
22
+ - Updated dependencies [4b4937f]
16
23
  - Updated dependencies [74821ed]
17
24
  - Updated dependencies [74821ed]
18
25
  - Updated dependencies [74821ed]
@@ -22,9 +29,11 @@
22
29
  - Updated dependencies [74821ed]
23
30
  - Updated dependencies [74821ed]
24
31
  - Updated dependencies [74821ed]
32
+ - Updated dependencies [e860006]
25
33
  - Updated dependencies [74821ed]
26
34
  - Updated dependencies [74821ed]
27
- - @walkeros/core@4.7.0-next-1790722899482
35
+ - Updated dependencies [4f89234]
36
+ - @walkeros/core@4.7.0
28
37
 
29
38
  ## 4.6.1
30
39
 
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,i=Object.prototype.hasOwnProperty,s={};((e,r)=>{for(var n in r)t(e,n,{get:r[n],enumerable:!0})})(s,{DEFAULT_SETUP:()=>d,default:()=>w,setup:()=>y,storeS3Init:()=>g}),module.exports=(e=s,((e,s,o,c)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let u of n(s))i.call(e,u)||u===o||t(e,u,{get:()=>s[u],enumerable:!(c=r(s,u))||c.enumerable});return e})(t({},"__esModule",{value:!0}),e));var o=require("s3mini"),c=require("@walkeros/core");var u="application/octet-stream",a={js:"application/javascript",mjs:"application/javascript",json:"application/json",css:"text/css",html:"text/html",txt:"text/plain",svg:"image/svg+xml",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",ico:"image/x-icon",wasm:"application/wasm",map:"application/json"};var g=async e=>{!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("storeS3Init: settings.bucket is required (non-empty string)");if("string"!=typeof e.endpoint||0===e.endpoint.length)throw new Error("storeS3Init: settings.endpoint is required (non-empty string)");if("string"!=typeof e.accessKeyId||0===e.accessKeyId.length)throw new Error("storeS3Init: settings.accessKeyId is required (non-empty string)");if("string"!=typeof e.secretAccessKey||0===e.secretAccessKey.length)throw new Error("storeS3Init: settings.secretAccessKey is required (non-empty string)")}(e.config.settings);const t=e.config.settings,r=function(e){if(!e)return"";const t=e.replace(/^\/+|\/+$/g,"");return t?t+"/":""}(t.prefix),n=!0===e.config.file,i=e.env?.fetch??e.config.env?.fetch,s=new o.S3mini({endpoint:(g=t.endpoint,p=t.bucket,`${g.replace(/\/+$/,"")}/${p}`),accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey,region:t.region||"auto",...i?{fetch:i}:{}});var g,p;function f(t){if(function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(t))return r+t;e.logger.warn("Invalid key rejected",{key:t})}if(!await s.bucketExists()){const r=e.id??"s3",n=`S3 bucket not found: ${t.bucket} at ${t.endpoint}. Run "walkeros setup store.${r}" to create it.`;throw e.logger.error(n,{bucket:t.bucket,endpoint:t.endpoint}),new Error(n)}return{type:"s3",config:{settings:t,env:e.config.env,id:e.config.id,logger:e.config.logger},async get(t){const r=f(t);if(!r)return;let i;try{const e=await s.getObjectArrayBuffer(r);if(!e)return;i=Buffer.from(e)}catch{return}if(n)return i;try{return(0,c.deserializeStoreValue)(i)}catch(r){return void e.logger.debug("structured decode failed, degrading to miss",{key:t,error:r instanceof Error?r.message:String(r)})}},async set(e,t){const r=f(e);if(r)if(n){if(!(t instanceof Uint8Array)&&"string"!=typeof t)throw new Error("storeS3Init.set: in file mode value must be Uint8Array or string; got "+typeof t);await s.putObject(r,t,function(e){const t=e.lastIndexOf(".");if(t<0||t===e.length-1)return u;const r=e.slice(t+1).toLowerCase();return a[r]??u}(e))}else await s.putObject(r,(0,c.serializeStoreValue)(t),"application/json")},async delete(e){const t=f(e);if(t)try{await s.deleteObject(t)}catch{}}}},p=require("s3mini"),f=require("@walkeros/core"),d={region:"eu-central-1"},y=async e=>{const{config:t,logger:r}=e,n=(0,f.resolveSetup)(t.setup,d);if(!n)return void r.debug("setup: skipped (config.setup is false or unset)");!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("setup: settings.bucket is required (non-empty string)");if("string"!=typeof e.endpoint||0===e.endpoint.length)throw new Error("setup: settings.endpoint is required (non-empty string)");if("string"!=typeof e.accessKeyId||0===e.accessKeyId.length)throw new Error("setup: settings.accessKeyId is required (non-empty string)");if("string"!=typeof e.secretAccessKey||0===e.secretAccessKey.length)throw new Error("setup: settings.secretAccessKey is required (non-empty string)")}(t.settings);const i=t.settings,s=function(e,t){if("string"==typeof e&&e.length>0)return e;if("string"==typeof t&&t.length>0&&"auto"!==t)return t;return d.region}(function(e){if(!function(e){return"object"==typeof e&&null!==e}(e))return;const t=e.region;return"string"==typeof t?t:void 0}(t.setup),i.region),o=new p.S3mini({endpoint:l(i.endpoint,i.bucket),accessKeyId:i.accessKeyId,secretAccessKey:i.secretAccessKey,region:s});if(await o.bucketExists())return r.debug("setup: bucket exists",{bucket:i.bucket}),{bucketCreated:!1};try{return await o.createBucket(),r.info("setup: bucket created",{bucket:i.bucket,region:s}),{bucketCreated:!0}}catch(e){if(function(e){return b(e,"BucketAlreadyOwnedByYou")}(e))return r.debug("setup: bucket created concurrently (race)",{bucket:i.bucket}),{bucketCreated:!1};if(function(e){return b(e,"BucketAlreadyExists")}(e))throw new Error(`S3 bucket name "${i.bucket}" is already in use by another AWS account. Choose a different bucket name in settings.bucket.`);throw e}};function l(e,t){return`${e.replace(/\/+$/,"")}/${t}`}function b(e,t){if("object"!=typeof e||null===e)return!1;const r=e;return r.code===t||r.Code===t||r.name===t||r.serviceCode===t}var w={type:"s3",init:g,setup:y};//# sourceMappingURL=index.js.map
1
+ "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,i=Object.prototype.hasOwnProperty,s={};((e,r)=>{for(var n in r)t(e,n,{get:r[n],enumerable:!0})})(s,{DEFAULT_SETUP:()=>d,default:()=>w,setup:()=>l,storeS3Init:()=>g}),module.exports=(e=s,((e,s,o,c)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let u of n(s))i.call(e,u)||u===o||t(e,u,{get:()=>s[u],enumerable:!(c=r(s,u))||c.enumerable});return e})(t({},"__esModule",{value:!0}),e));var o=require("s3mini"),c=require("@walkeros/core");var u="application/octet-stream",a={js:"application/javascript",mjs:"application/javascript",json:"application/json",css:"text/css",html:"text/html",txt:"text/plain",svg:"image/svg+xml",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",ico:"image/x-icon",wasm:"application/wasm",map:"application/json"};var g=async e=>{!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("storeS3Init: settings.bucket is required (non-empty string)");if("string"!=typeof e.endpoint||0===e.endpoint.length)throw new Error("storeS3Init: settings.endpoint is required (non-empty string)");if("string"!=typeof e.accessKeyId||0===e.accessKeyId.length)throw new Error("storeS3Init: settings.accessKeyId is required (non-empty string)");if("string"!=typeof e.secretAccessKey||0===e.secretAccessKey.length)throw new Error("storeS3Init: settings.secretAccessKey is required (non-empty string)")}(e.config.settings);const t=e.config.settings,r=function(e){if(!e)return"";const t=e.replace(/^\/+|\/+$/g,"");return t?t+"/":""}(t.prefix),n=!0===e.config.file,i=e.env?.fetch??e.config.env?.fetch,s=new o.S3mini({endpoint:(g=t.endpoint,f=t.bucket,`${g.replace(/\/+$/,"")}/${f}`),accessKeyId:t.accessKeyId,secretAccessKey:t.secretAccessKey,region:t.region||"auto",...i?{fetch:i}:{}});var g,f;function p(t){if(function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(t))return r+t;e.logger.warn("Invalid key rejected",{key:t})}if(!await s.bucketExists()){const r=e.id??"s3",n=`S3 bucket not found: ${t.bucket} at ${t.endpoint}. Run "walkeros setup store.${r}" to create it.`;throw e.logger.error(n,{bucket:t.bucket,endpoint:t.endpoint}),new Error(n)}return{type:"s3",config:{settings:t,env:e.config.env,id:e.config.id,logger:e.config.logger,file:e.config.file},async get(t){const r=p(t);if(!r)return;let i;try{const e=await s.getObjectArrayBuffer(r);if(!e)return;i=Buffer.from(e)}catch{return}if(n)return i;try{return(0,c.deserializeStoreValue)(i)}catch(r){return void e.logger.debug("structured decode failed, degrading to miss",{key:t,error:r instanceof Error?r.message:String(r)})}},async set(e,t){const r=p(e);if(r)if(n){if(!(t instanceof Uint8Array)&&"string"!=typeof t)throw new Error("storeS3Init.set: in file mode value must be Uint8Array or string; got "+typeof t);await s.putObject(r,t,function(e){const t=e.lastIndexOf(".");if(t<0||t===e.length-1)return u;const r=e.slice(t+1).toLowerCase();return a[r]??u}(e))}else await s.putObject(r,(0,c.serializeStoreValue)(t),"application/json")},async delete(e){const t=p(e);if(t)try{await s.deleteObject(t)}catch{}}}},f=require("s3mini"),p=require("@walkeros/core"),d={region:"eu-central-1"},l=async e=>{const{config:t,logger:r}=e,n=(0,p.resolveSetup)(t.setup,d);if(!n)return void r.debug("setup: skipped (config.setup is false or unset)");!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("setup: settings.bucket is required (non-empty string)");if("string"!=typeof e.endpoint||0===e.endpoint.length)throw new Error("setup: settings.endpoint is required (non-empty string)");if("string"!=typeof e.accessKeyId||0===e.accessKeyId.length)throw new Error("setup: settings.accessKeyId is required (non-empty string)");if("string"!=typeof e.secretAccessKey||0===e.secretAccessKey.length)throw new Error("setup: settings.secretAccessKey is required (non-empty string)")}(t.settings);const i=t.settings,s=function(e,t){if("string"==typeof e&&e.length>0)return e;if("string"==typeof t&&t.length>0&&"auto"!==t)return t;return d.region}(function(e){if(!function(e){return"object"==typeof e&&null!==e}(e))return;const t=e.region;return"string"==typeof t?t:void 0}(t.setup),i.region),o=new f.S3mini({endpoint:y(i.endpoint,i.bucket),accessKeyId:i.accessKeyId,secretAccessKey:i.secretAccessKey,region:s});if(await o.bucketExists())return r.debug("setup: bucket exists",{bucket:i.bucket}),{bucketCreated:!1};try{return await o.createBucket(),r.info("setup: bucket created",{bucket:i.bucket,region:s}),{bucketCreated:!0}}catch(e){if(function(e){return b(e,"BucketAlreadyOwnedByYou")}(e))return r.debug("setup: bucket created concurrently (race)",{bucket:i.bucket}),{bucketCreated:!1};if(function(e){return b(e,"BucketAlreadyExists")}(e))throw new Error(`S3 bucket name "${i.bucket}" is already in use by another AWS account. Choose a different bucket name in settings.bucket.`);throw e}};function y(e,t){return`${e.replace(/\/+$/,"")}/${t}`}function b(e,t){if("object"!=typeof e||null===e)return!1;const r=e;return r.code===t||r.Code===t||r.name===t||r.serviceCode===t}var w={type:"s3",init:g,setup:l};//# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/store.ts","../src/setup.ts"],"sourcesContent":["import type { Store, SetupFn } from '@walkeros/core';\nimport { storeS3Init } from './store';\nimport { setup } from './setup';\nimport type { Types } from './types';\nimport type { S3StoreConfig } from './setup';\n\nexport { storeS3Init } from './store';\nexport { setup, DEFAULT_SETUP } from './setup';\nexport type { SetupResult, S3StoreConfig } from './setup';\nexport type {\n Env,\n S3StoreSettings,\n S3StoreInitSettings,\n S3StoreSetup,\n Types,\n} from './types';\n\n/**\n * Default export shape for store packages. The CLI (`walkeros setup store.<id>`)\n * reads `default.setup` to find the lifecycle function; the collector reads\n * `default.init` (or the named export) to construct the runtime instance.\n *\n * Soft breaking change: callers using `import storeS3Init from '@walkeros/server-store-s3'`\n * (default import) must now call `.init` on the returned object. Named imports\n * (`import { storeS3Init }`) are unaffected.\n */\nexport interface StoreS3Module {\n type: 's3';\n init: Store.Init<Types>;\n setup: SetupFn<S3StoreConfig, Store.BaseEnv>;\n}\n\nconst storeS3: StoreS3Module = {\n type: 's3',\n init: storeS3Init,\n setup,\n};\n\nexport default storeS3;\n","import { S3mini } from 's3mini';\nimport { serializeStoreValue, deserializeStoreValue } from '@walkeros/core';\nimport type { Store } from '@walkeros/core';\nimport type { S3StoreSettings, Types } from './types';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nconst JSON_CONTENT_TYPE = 'application/json';\nconst DEFAULT_CONTENT_TYPE = 'application/octet-stream';\n\n// Minimal extension to content-type map for the file-mode asset-serving use\n// case (serving walker.js and friends back with a correct mime). Anything not\n// listed falls back to application/octet-stream.\nconst MIME_BY_EXT: Readonly<Record<string, string>> = {\n js: 'application/javascript',\n mjs: 'application/javascript',\n json: 'application/json',\n css: 'text/css',\n html: 'text/html',\n txt: 'text/plain',\n svg: 'image/svg+xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n ico: 'image/x-icon',\n wasm: 'application/wasm',\n map: 'application/json',\n};\n\nfunction mimeFromKey(key: string): string {\n const dot = key.lastIndexOf('.');\n if (dot < 0 || dot === key.length - 1) return DEFAULT_CONTENT_TYPE;\n const ext = key.slice(dot + 1).toLowerCase();\n return MIME_BY_EXT[ext] ?? DEFAULT_CONTENT_TYPE;\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction buildEndpoint(endpoint: string, bucket: string): string {\n const base = endpoint.replace(/\\/+$/, '');\n return `${base}/${bucket}`;\n}\n\nfunction assertSettings(\n settings: Partial<S3StoreSettings> | undefined,\n): asserts settings is S3StoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.bucket is required (non-empty string)',\n );\n }\n if (typeof settings.endpoint !== 'string' || settings.endpoint.length === 0) {\n throw new Error(\n 'storeS3Init: settings.endpoint is required (non-empty string)',\n );\n }\n if (\n typeof settings.accessKeyId !== 'string' ||\n settings.accessKeyId.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.accessKeyId is required (non-empty string)',\n );\n }\n if (\n typeof settings.secretAccessKey !== 'string' ||\n settings.secretAccessKey.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.secretAccessKey is required (non-empty string)',\n );\n }\n}\n\nexport const storeS3Init: Store.Init<Types> = async (context) => {\n assertSettings(context.config.settings);\n const settings = context.config.settings;\n const prefix = normalizePrefix(settings.prefix);\n // One mode per instance, decided at init. file:true persists raw bytes\n // byte-exact with a real mime; structured mode round-trips StoreValue\n // through the shared core codec, stored as application/json.\n const fileMode = context.config.file === true;\n\n // An injected fetch (tests, simulate) carries every S3 request; s3mini\n // uses the global fetch when none is given.\n const injectedFetch = context.env?.fetch ?? context.config.env?.fetch;\n const client = new S3mini({\n endpoint: buildEndpoint(settings.endpoint, settings.bucket),\n accessKeyId: settings.accessKeyId,\n secretAccessKey: settings.secretAccessKey,\n region: settings.region || 'auto',\n ...(injectedFetch ? { fetch: injectedFetch } : {}),\n });\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n // Hard-fail with an actionable message if the bucket is missing.\n // Mirrors the BigQuery init-time hard-fail pattern.\n const exists = await client.bucketExists();\n if (!exists) {\n const setupId = context.id ?? 's3';\n const errorMsg =\n `S3 bucket not found: ${settings.bucket} at ${settings.endpoint}. ` +\n `Run \"walkeros setup store.${setupId}\" to create it.`;\n context.logger.error(errorMsg, {\n bucket: settings.bucket,\n endpoint: settings.endpoint,\n });\n throw new Error(errorMsg);\n }\n\n return {\n type: 's3',\n config: {\n settings,\n env: context.config.env,\n id: context.config.id,\n logger: context.config.logger,\n },\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const s3Key = resolveKey(key);\n if (!s3Key) return undefined;\n\n let bytes: Buffer;\n try {\n const arrayBuffer = await client.getObjectArrayBuffer(s3Key);\n if (!arrayBuffer) return undefined;\n bytes = Buffer.from(arrayBuffer);\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 // A corrupt or empty payload (e.g. a 0-byte object) degrades to a miss\n // 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 s3Key = resolveKey(key);\n if (!s3Key) return;\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 'storeS3Init.set: in file mode value must be Uint8Array or string; got ' +\n typeof value,\n );\n }\n await client.putObject(s3Key, value, mimeFromKey(key));\n return;\n }\n\n // Structured mode: serialize to utf8-JSON bytes, stored as JSON.\n await client.putObject(\n s3Key,\n serializeStoreValue(value),\n JSON_CONTENT_TYPE,\n );\n },\n\n async delete(key: string): Promise<void> {\n const s3Key = resolveKey(key);\n if (!s3Key) return;\n\n try {\n await client.deleteObject(s3Key);\n } catch {\n /* S3 delete is idempotent */\n }\n },\n };\n};\n","import { S3mini } from 's3mini';\nimport type { LifecycleContext, SetupFn, Store } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type { S3StoreSettings, S3StoreSetup, Types } from './types';\n\n/**\n * Default setup options. EU region by default per AGENT.md.\n */\nexport const DEFAULT_SETUP: Required<S3StoreSetup> = {\n region: 'eu-central-1',\n};\n\nexport interface SetupResult {\n bucketCreated: boolean;\n}\n\n/**\n * Public alias kept for callers that imported the prior shape.\n * Equivalent to the framework's `Store.Config<Types>`.\n */\nexport type S3StoreConfig = Store.Config<Types>;\n\n/**\n * Provision the S3 bucket described in the flow config. Idempotent. Triggered\n * only by the explicit `walkeros setup store.<id>` CLI command.\n *\n * Variant B: only the bucket is created. Encryption, public-access block,\n * versioning, lifecycle rules, and tags are not applied here because `s3mini`\n * does not expose the relevant API operations. Configure those once via the\n * AWS Console or `aws s3api`.\n */\nexport const setup: SetupFn<S3StoreConfig, Store.BaseEnv> = async (\n context: LifecycleContext<S3StoreConfig, Store.BaseEnv>,\n) => {\n const { config, logger } = context;\n\n const options = resolveSetup(config.setup, DEFAULT_SETUP);\n if (!options) {\n logger.debug('setup: skipped (config.setup is false or unset)');\n return;\n }\n\n assertS3Settings(config.settings);\n const settings = config.settings;\n\n // Region resolution per Open Question #4 (iii):\n // - explicit setup.region wins\n // - else fall back to settings.region when concrete (not 'auto')\n // - else default eu-central-1\n const explicitSetupRegion = readExplicitRegion(config.setup);\n const region = resolveRegion(explicitSetupRegion, settings.region);\n void options; // resolveSetup applies defaults; explicit branch handled separately\n\n const client = new S3mini({\n endpoint: buildEndpoint(settings.endpoint, settings.bucket),\n accessKeyId: settings.accessKeyId,\n secretAccessKey: settings.secretAccessKey,\n region,\n });\n\n const exists = await client.bucketExists();\n if (exists) {\n logger.debug('setup: bucket exists', { bucket: settings.bucket });\n return { bucketCreated: false };\n }\n\n try {\n await client.createBucket();\n logger.info('setup: bucket created', {\n bucket: settings.bucket,\n region,\n });\n return { bucketCreated: true };\n } catch (err: unknown) {\n if (isAlreadyOwnedByYou(err)) {\n logger.debug('setup: bucket created concurrently (race)', {\n bucket: settings.bucket,\n });\n return { bucketCreated: false };\n }\n if (isAlreadyExistsDifferentOwner(err)) {\n throw new Error(\n `S3 bucket name \"${settings.bucket}\" is already in use by another AWS account. ` +\n `Choose a different bucket name in settings.bucket.`,\n );\n }\n throw err;\n }\n};\n\nfunction resolveRegion(\n explicitSetupRegion: string | undefined,\n settingsRegion: string | undefined,\n): string {\n if (\n typeof explicitSetupRegion === 'string' &&\n explicitSetupRegion.length > 0\n ) {\n return explicitSetupRegion;\n }\n if (\n typeof settingsRegion === 'string' &&\n settingsRegion.length > 0 &&\n settingsRegion !== 'auto'\n ) {\n return settingsRegion;\n }\n return DEFAULT_SETUP.region;\n}\n\nfunction readExplicitRegion(raw: S3StoreConfig['setup']): string | undefined {\n if (!isRecord(raw)) return undefined;\n const value = raw.region;\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction assertS3Settings(\n settings: Partial<S3StoreSettings> | undefined,\n): asserts settings is S3StoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error('setup: settings.bucket is required (non-empty string)');\n }\n if (typeof settings.endpoint !== 'string' || settings.endpoint.length === 0) {\n throw new Error('setup: settings.endpoint is required (non-empty string)');\n }\n if (\n typeof settings.accessKeyId !== 'string' ||\n settings.accessKeyId.length === 0\n ) {\n throw new Error(\n 'setup: settings.accessKeyId is required (non-empty string)',\n );\n }\n if (\n typeof settings.secretAccessKey !== 'string' ||\n settings.secretAccessKey.length === 0\n ) {\n throw new Error(\n 'setup: settings.secretAccessKey is required (non-empty string)',\n );\n }\n}\n\nfunction buildEndpoint(endpoint: string, bucket: string): string {\n const base = endpoint.replace(/\\/+$/, '');\n return `${base}/${bucket}`;\n}\n\nfunction isAlreadyOwnedByYou(err: unknown): boolean {\n return hasErrorCode(err, 'BucketAlreadyOwnedByYou');\n}\n\nfunction isAlreadyExistsDifferentOwner(err: unknown): boolean {\n return hasErrorCode(err, 'BucketAlreadyExists');\n}\n\nfunction hasErrorCode(err: unknown, code: string): boolean {\n if (typeof err !== 'object' || err === null) return false;\n const obj: {\n code?: unknown;\n Code?: unknown;\n name?: unknown;\n serviceCode?: unknown;\n } = err;\n return (\n obj.code === code ||\n obj.Code === code ||\n obj.name === code ||\n obj.serviceCode === code\n );\n}\n"],"mappings":"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","names":["import_s3mini","import_core","buildEndpoint"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/store.ts","../src/setup.ts"],"sourcesContent":["import type { Store, SetupFn } from '@walkeros/core';\nimport { storeS3Init } from './store';\nimport { setup } from './setup';\nimport type { Types } from './types';\nimport type { S3StoreConfig } from './setup';\n\nexport { storeS3Init } from './store';\nexport { setup, DEFAULT_SETUP } from './setup';\nexport type { SetupResult, S3StoreConfig } from './setup';\nexport type {\n Env,\n S3StoreSettings,\n S3StoreInitSettings,\n S3StoreSetup,\n Types,\n} from './types';\n\n/**\n * Default export shape for store packages. The CLI (`walkeros setup store.<id>`)\n * reads `default.setup` to find the lifecycle function; the collector reads\n * `default.init` (or the named export) to construct the runtime instance.\n *\n * Soft breaking change: callers using `import storeS3Init from '@walkeros/server-store-s3'`\n * (default import) must now call `.init` on the returned object. Named imports\n * (`import { storeS3Init }`) are unaffected.\n */\nexport interface StoreS3Module {\n type: 's3';\n init: Store.Init<Types>;\n setup: SetupFn<S3StoreConfig, Store.BaseEnv>;\n}\n\nconst storeS3: StoreS3Module = {\n type: 's3',\n init: storeS3Init,\n setup,\n};\n\nexport default storeS3;\n","import { S3mini } from 's3mini';\nimport { serializeStoreValue, deserializeStoreValue } from '@walkeros/core';\nimport type { Store } from '@walkeros/core';\nimport type { S3StoreSettings, Types } from './types';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nconst JSON_CONTENT_TYPE = 'application/json';\nconst DEFAULT_CONTENT_TYPE = 'application/octet-stream';\n\n// Minimal extension to content-type map for the file-mode asset-serving use\n// case (serving walker.js and friends back with a correct mime). Anything not\n// listed falls back to application/octet-stream.\nconst MIME_BY_EXT: Readonly<Record<string, string>> = {\n js: 'application/javascript',\n mjs: 'application/javascript',\n json: 'application/json',\n css: 'text/css',\n html: 'text/html',\n txt: 'text/plain',\n svg: 'image/svg+xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n ico: 'image/x-icon',\n wasm: 'application/wasm',\n map: 'application/json',\n};\n\nfunction mimeFromKey(key: string): string {\n const dot = key.lastIndexOf('.');\n if (dot < 0 || dot === key.length - 1) return DEFAULT_CONTENT_TYPE;\n const ext = key.slice(dot + 1).toLowerCase();\n return MIME_BY_EXT[ext] ?? DEFAULT_CONTENT_TYPE;\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction buildEndpoint(endpoint: string, bucket: string): string {\n const base = endpoint.replace(/\\/+$/, '');\n return `${base}/${bucket}`;\n}\n\nfunction assertSettings(\n settings: Partial<S3StoreSettings> | undefined,\n): asserts settings is S3StoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.bucket is required (non-empty string)',\n );\n }\n if (typeof settings.endpoint !== 'string' || settings.endpoint.length === 0) {\n throw new Error(\n 'storeS3Init: settings.endpoint is required (non-empty string)',\n );\n }\n if (\n typeof settings.accessKeyId !== 'string' ||\n settings.accessKeyId.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.accessKeyId is required (non-empty string)',\n );\n }\n if (\n typeof settings.secretAccessKey !== 'string' ||\n settings.secretAccessKey.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.secretAccessKey is required (non-empty string)',\n );\n }\n}\n\nexport const storeS3Init: Store.Init<Types> = async (context) => {\n assertSettings(context.config.settings);\n const settings = context.config.settings;\n const prefix = normalizePrefix(settings.prefix);\n // One mode per instance, decided at init. file:true persists raw bytes\n // byte-exact with a real mime; structured mode round-trips StoreValue\n // through the shared core codec, stored as application/json.\n const fileMode = context.config.file === true;\n\n // An injected fetch (tests, simulate) carries every S3 request; s3mini\n // uses the global fetch when none is given.\n const injectedFetch = context.env?.fetch ?? context.config.env?.fetch;\n const client = new S3mini({\n endpoint: buildEndpoint(settings.endpoint, settings.bucket),\n accessKeyId: settings.accessKeyId,\n secretAccessKey: settings.secretAccessKey,\n region: settings.region || 'auto',\n ...(injectedFetch ? { fetch: injectedFetch } : {}),\n });\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n // Hard-fail with an actionable message if the bucket is missing.\n // Mirrors the BigQuery init-time hard-fail pattern.\n const exists = await client.bucketExists();\n if (!exists) {\n const setupId = context.id ?? 's3';\n const errorMsg =\n `S3 bucket not found: ${settings.bucket} at ${settings.endpoint}. ` +\n `Run \"walkeros setup store.${setupId}\" to create it.`;\n context.logger.error(errorMsg, {\n bucket: settings.bucket,\n endpoint: settings.endpoint,\n });\n throw new Error(errorMsg);\n }\n\n return {\n type: 's3',\n config: {\n settings,\n env: context.config.env,\n id: context.config.id,\n logger: context.config.logger,\n // The declared byte mode; state and validation read it from here.\n file: context.config.file,\n },\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const s3Key = resolveKey(key);\n if (!s3Key) return undefined;\n\n let bytes: Buffer;\n try {\n const arrayBuffer = await client.getObjectArrayBuffer(s3Key);\n if (!arrayBuffer) return undefined;\n bytes = Buffer.from(arrayBuffer);\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 // A corrupt or empty payload (e.g. a 0-byte object) degrades to a miss\n // 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 s3Key = resolveKey(key);\n if (!s3Key) return;\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 'storeS3Init.set: in file mode value must be Uint8Array or string; got ' +\n typeof value,\n );\n }\n await client.putObject(s3Key, value, mimeFromKey(key));\n return;\n }\n\n // Structured mode: serialize to utf8-JSON bytes, stored as JSON.\n await client.putObject(\n s3Key,\n serializeStoreValue(value),\n JSON_CONTENT_TYPE,\n );\n },\n\n async delete(key: string): Promise<void> {\n const s3Key = resolveKey(key);\n if (!s3Key) return;\n\n try {\n await client.deleteObject(s3Key);\n } catch {\n /* S3 delete is idempotent */\n }\n },\n };\n};\n","import { S3mini } from 's3mini';\nimport type { LifecycleContext, SetupFn, Store } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type { S3StoreSettings, S3StoreSetup, Types } from './types';\n\n/**\n * Default setup options. EU region by default per AGENT.md.\n */\nexport const DEFAULT_SETUP: Required<S3StoreSetup> = {\n region: 'eu-central-1',\n};\n\nexport interface SetupResult {\n bucketCreated: boolean;\n}\n\n/**\n * Public alias kept for callers that imported the prior shape.\n * Equivalent to the framework's `Store.Config<Types>`.\n */\nexport type S3StoreConfig = Store.Config<Types>;\n\n/**\n * Provision the S3 bucket described in the flow config. Idempotent. Triggered\n * only by the explicit `walkeros setup store.<id>` CLI command.\n *\n * Variant B: only the bucket is created. Encryption, public-access block,\n * versioning, lifecycle rules, and tags are not applied here because `s3mini`\n * does not expose the relevant API operations. Configure those once via the\n * AWS Console or `aws s3api`.\n */\nexport const setup: SetupFn<S3StoreConfig, Store.BaseEnv> = async (\n context: LifecycleContext<S3StoreConfig, Store.BaseEnv>,\n) => {\n const { config, logger } = context;\n\n const options = resolveSetup(config.setup, DEFAULT_SETUP);\n if (!options) {\n logger.debug('setup: skipped (config.setup is false or unset)');\n return;\n }\n\n assertS3Settings(config.settings);\n const settings = config.settings;\n\n // Region resolution per Open Question #4 (iii):\n // - explicit setup.region wins\n // - else fall back to settings.region when concrete (not 'auto')\n // - else default eu-central-1\n const explicitSetupRegion = readExplicitRegion(config.setup);\n const region = resolveRegion(explicitSetupRegion, settings.region);\n void options; // resolveSetup applies defaults; explicit branch handled separately\n\n const client = new S3mini({\n endpoint: buildEndpoint(settings.endpoint, settings.bucket),\n accessKeyId: settings.accessKeyId,\n secretAccessKey: settings.secretAccessKey,\n region,\n });\n\n const exists = await client.bucketExists();\n if (exists) {\n logger.debug('setup: bucket exists', { bucket: settings.bucket });\n return { bucketCreated: false };\n }\n\n try {\n await client.createBucket();\n logger.info('setup: bucket created', {\n bucket: settings.bucket,\n region,\n });\n return { bucketCreated: true };\n } catch (err: unknown) {\n if (isAlreadyOwnedByYou(err)) {\n logger.debug('setup: bucket created concurrently (race)', {\n bucket: settings.bucket,\n });\n return { bucketCreated: false };\n }\n if (isAlreadyExistsDifferentOwner(err)) {\n throw new Error(\n `S3 bucket name \"${settings.bucket}\" is already in use by another AWS account. ` +\n `Choose a different bucket name in settings.bucket.`,\n );\n }\n throw err;\n }\n};\n\nfunction resolveRegion(\n explicitSetupRegion: string | undefined,\n settingsRegion: string | undefined,\n): string {\n if (\n typeof explicitSetupRegion === 'string' &&\n explicitSetupRegion.length > 0\n ) {\n return explicitSetupRegion;\n }\n if (\n typeof settingsRegion === 'string' &&\n settingsRegion.length > 0 &&\n settingsRegion !== 'auto'\n ) {\n return settingsRegion;\n }\n return DEFAULT_SETUP.region;\n}\n\nfunction readExplicitRegion(raw: S3StoreConfig['setup']): string | undefined {\n if (!isRecord(raw)) return undefined;\n const value = raw.region;\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction assertS3Settings(\n settings: Partial<S3StoreSettings> | undefined,\n): asserts settings is S3StoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error('setup: settings.bucket is required (non-empty string)');\n }\n if (typeof settings.endpoint !== 'string' || settings.endpoint.length === 0) {\n throw new Error('setup: settings.endpoint is required (non-empty string)');\n }\n if (\n typeof settings.accessKeyId !== 'string' ||\n settings.accessKeyId.length === 0\n ) {\n throw new Error(\n 'setup: settings.accessKeyId is required (non-empty string)',\n );\n }\n if (\n typeof settings.secretAccessKey !== 'string' ||\n settings.secretAccessKey.length === 0\n ) {\n throw new Error(\n 'setup: settings.secretAccessKey is required (non-empty string)',\n );\n }\n}\n\nfunction buildEndpoint(endpoint: string, bucket: string): string {\n const base = endpoint.replace(/\\/+$/, '');\n return `${base}/${bucket}`;\n}\n\nfunction isAlreadyOwnedByYou(err: unknown): boolean {\n return hasErrorCode(err, 'BucketAlreadyOwnedByYou');\n}\n\nfunction isAlreadyExistsDifferentOwner(err: unknown): boolean {\n return hasErrorCode(err, 'BucketAlreadyExists');\n}\n\nfunction hasErrorCode(err: unknown, code: string): boolean {\n if (typeof err !== 'object' || err === null) return false;\n const obj: {\n code?: unknown;\n Code?: unknown;\n name?: unknown;\n serviceCode?: unknown;\n } = err;\n return (\n obj.code === code ||\n obj.Code === code ||\n obj.name === code ||\n obj.serviceCode === code\n );\n}\n"],"mappings":"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","names":["import_s3mini","import_core","buildEndpoint"]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{S3mini as e}from"s3mini";import{serializeStoreValue as t,deserializeStoreValue as n}from"@walkeros/core";var r="application/octet-stream",i={js:"application/javascript",mjs:"application/javascript",json:"application/json",css:"text/css",html:"text/html",txt:"text/plain",svg:"image/svg+xml",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",ico:"image/x-icon",wasm:"application/wasm",map:"application/json"};var s=async s=>{!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("storeS3Init: settings.bucket is required (non-empty string)");if("string"!=typeof e.endpoint||0===e.endpoint.length)throw new Error("storeS3Init: settings.endpoint is required (non-empty string)");if("string"!=typeof e.accessKeyId||0===e.accessKeyId.length)throw new Error("storeS3Init: settings.accessKeyId is required (non-empty string)");if("string"!=typeof e.secretAccessKey||0===e.secretAccessKey.length)throw new Error("storeS3Init: settings.secretAccessKey is required (non-empty string)")}(s.config.settings);const c=s.config.settings,o=function(e){if(!e)return"";const t=e.replace(/^\/+|\/+$/g,"");return t?t+"/":""}(c.prefix),u=!0===s.config.file,a=s.env?.fetch??s.config.env?.fetch,g=new e({endpoint:(f=c.endpoint,p=c.bucket,`${f.replace(/\/+$/,"")}/${p}`),accessKeyId:c.accessKeyId,secretAccessKey:c.secretAccessKey,region:c.region||"auto",...a?{fetch:a}:{}});var f,p;function d(e){if(function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(e))return o+e;s.logger.warn("Invalid key rejected",{key:e})}if(!await g.bucketExists()){const e=s.id??"s3",t=`S3 bucket not found: ${c.bucket} at ${c.endpoint}. Run "walkeros setup store.${e}" to create it.`;throw s.logger.error(t,{bucket:c.bucket,endpoint:c.endpoint}),new Error(t)}return{type:"s3",config:{settings:c,env:s.config.env,id:s.config.id,logger:s.config.logger},async get(e){const t=d(e);if(!t)return;let r;try{const e=await g.getObjectArrayBuffer(t);if(!e)return;r=Buffer.from(e)}catch{return}if(u)return r;try{return n(r)}catch(t){return void s.logger.debug("structured decode failed, degrading to miss",{key:e,error:t instanceof Error?t.message:String(t)})}},async set(e,n){const s=d(e);if(s)if(u){if(!(n instanceof Uint8Array)&&"string"!=typeof n)throw new Error("storeS3Init.set: in file mode value must be Uint8Array or string; got "+typeof n);await g.putObject(s,n,function(e){const t=e.lastIndexOf(".");if(t<0||t===e.length-1)return r;const n=e.slice(t+1).toLowerCase();return i[n]??r}(e))}else await g.putObject(s,t(n),"application/json")},async delete(e){const t=d(e);if(t)try{await g.deleteObject(t)}catch{}}}};import{S3mini as c}from"s3mini";import{resolveSetup as o}from"@walkeros/core";var u={region:"eu-central-1"},a=async e=>{const{config:t,logger:n}=e,r=o(t.setup,u);if(!r)return void n.debug("setup: skipped (config.setup is false or unset)");!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("setup: settings.bucket is required (non-empty string)");if("string"!=typeof e.endpoint||0===e.endpoint.length)throw new Error("setup: settings.endpoint is required (non-empty string)");if("string"!=typeof e.accessKeyId||0===e.accessKeyId.length)throw new Error("setup: settings.accessKeyId is required (non-empty string)");if("string"!=typeof e.secretAccessKey||0===e.secretAccessKey.length)throw new Error("setup: settings.secretAccessKey is required (non-empty string)")}(t.settings);const i=t.settings,s=function(e,t){if("string"==typeof e&&e.length>0)return e;if("string"==typeof t&&t.length>0&&"auto"!==t)return t;return u.region}(function(e){if(!function(e){return"object"==typeof e&&null!==e}(e))return;const t=e.region;return"string"==typeof t?t:void 0}(t.setup),i.region),a=new c({endpoint:g(i.endpoint,i.bucket),accessKeyId:i.accessKeyId,secretAccessKey:i.secretAccessKey,region:s});if(await a.bucketExists())return n.debug("setup: bucket exists",{bucket:i.bucket}),{bucketCreated:!1};try{return await a.createBucket(),n.info("setup: bucket created",{bucket:i.bucket,region:s}),{bucketCreated:!0}}catch(e){if(function(e){return f(e,"BucketAlreadyOwnedByYou")}(e))return n.debug("setup: bucket created concurrently (race)",{bucket:i.bucket}),{bucketCreated:!1};if(function(e){return f(e,"BucketAlreadyExists")}(e))throw new Error(`S3 bucket name "${i.bucket}" is already in use by another AWS account. Choose a different bucket name in settings.bucket.`);throw e}};function g(e,t){return`${e.replace(/\/+$/,"")}/${t}`}function f(e,t){if("object"!=typeof e||null===e)return!1;const n=e;return n.code===t||n.Code===t||n.name===t||n.serviceCode===t}var p={type:"s3",init:s,setup:a};export{u as DEFAULT_SETUP,p as default,a as setup,s as storeS3Init};//# sourceMappingURL=index.mjs.map
1
+ import{S3mini as e}from"s3mini";import{serializeStoreValue as t,deserializeStoreValue as n}from"@walkeros/core";var r="application/octet-stream",i={js:"application/javascript",mjs:"application/javascript",json:"application/json",css:"text/css",html:"text/html",txt:"text/plain",svg:"image/svg+xml",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",ico:"image/x-icon",wasm:"application/wasm",map:"application/json"};var s=async s=>{!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("storeS3Init: settings.bucket is required (non-empty string)");if("string"!=typeof e.endpoint||0===e.endpoint.length)throw new Error("storeS3Init: settings.endpoint is required (non-empty string)");if("string"!=typeof e.accessKeyId||0===e.accessKeyId.length)throw new Error("storeS3Init: settings.accessKeyId is required (non-empty string)");if("string"!=typeof e.secretAccessKey||0===e.secretAccessKey.length)throw new Error("storeS3Init: settings.secretAccessKey is required (non-empty string)")}(s.config.settings);const c=s.config.settings,o=function(e){if(!e)return"";const t=e.replace(/^\/+|\/+$/g,"");return t?t+"/":""}(c.prefix),u=!0===s.config.file,a=s.env?.fetch??s.config.env?.fetch,g=new e({endpoint:(f=c.endpoint,p=c.bucket,`${f.replace(/\/+$/,"")}/${p}`),accessKeyId:c.accessKeyId,secretAccessKey:c.secretAccessKey,region:c.region||"auto",...a?{fetch:a}:{}});var f,p;function d(e){if(function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(e))return o+e;s.logger.warn("Invalid key rejected",{key:e})}if(!await g.bucketExists()){const e=s.id??"s3",t=`S3 bucket not found: ${c.bucket} at ${c.endpoint}. Run "walkeros setup store.${e}" to create it.`;throw s.logger.error(t,{bucket:c.bucket,endpoint:c.endpoint}),new Error(t)}return{type:"s3",config:{settings:c,env:s.config.env,id:s.config.id,logger:s.config.logger,file:s.config.file},async get(e){const t=d(e);if(!t)return;let r;try{const e=await g.getObjectArrayBuffer(t);if(!e)return;r=Buffer.from(e)}catch{return}if(u)return r;try{return n(r)}catch(t){return void s.logger.debug("structured decode failed, degrading to miss",{key:e,error:t instanceof Error?t.message:String(t)})}},async set(e,n){const s=d(e);if(s)if(u){if(!(n instanceof Uint8Array)&&"string"!=typeof n)throw new Error("storeS3Init.set: in file mode value must be Uint8Array or string; got "+typeof n);await g.putObject(s,n,function(e){const t=e.lastIndexOf(".");if(t<0||t===e.length-1)return r;const n=e.slice(t+1).toLowerCase();return i[n]??r}(e))}else await g.putObject(s,t(n),"application/json")},async delete(e){const t=d(e);if(t)try{await g.deleteObject(t)}catch{}}}};import{S3mini as c}from"s3mini";import{resolveSetup as o}from"@walkeros/core";var u={region:"eu-central-1"},a=async e=>{const{config:t,logger:n}=e,r=o(t.setup,u);if(!r)return void n.debug("setup: skipped (config.setup is false or unset)");!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("setup: settings.bucket is required (non-empty string)");if("string"!=typeof e.endpoint||0===e.endpoint.length)throw new Error("setup: settings.endpoint is required (non-empty string)");if("string"!=typeof e.accessKeyId||0===e.accessKeyId.length)throw new Error("setup: settings.accessKeyId is required (non-empty string)");if("string"!=typeof e.secretAccessKey||0===e.secretAccessKey.length)throw new Error("setup: settings.secretAccessKey is required (non-empty string)")}(t.settings);const i=t.settings,s=function(e,t){if("string"==typeof e&&e.length>0)return e;if("string"==typeof t&&t.length>0&&"auto"!==t)return t;return u.region}(function(e){if(!function(e){return"object"==typeof e&&null!==e}(e))return;const t=e.region;return"string"==typeof t?t:void 0}(t.setup),i.region),a=new c({endpoint:g(i.endpoint,i.bucket),accessKeyId:i.accessKeyId,secretAccessKey:i.secretAccessKey,region:s});if(await a.bucketExists())return n.debug("setup: bucket exists",{bucket:i.bucket}),{bucketCreated:!1};try{return await a.createBucket(),n.info("setup: bucket created",{bucket:i.bucket,region:s}),{bucketCreated:!0}}catch(e){if(function(e){return f(e,"BucketAlreadyOwnedByYou")}(e))return n.debug("setup: bucket created concurrently (race)",{bucket:i.bucket}),{bucketCreated:!1};if(function(e){return f(e,"BucketAlreadyExists")}(e))throw new Error(`S3 bucket name "${i.bucket}" is already in use by another AWS account. Choose a different bucket name in settings.bucket.`);throw e}};function g(e,t){return`${e.replace(/\/+$/,"")}/${t}`}function f(e,t){if("object"!=typeof e||null===e)return!1;const n=e;return n.code===t||n.Code===t||n.name===t||n.serviceCode===t}var p={type:"s3",init:s,setup:a};export{u as DEFAULT_SETUP,p as default,a as setup,s as storeS3Init};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/store.ts","../src/setup.ts","../src/index.ts"],"sourcesContent":["import { S3mini } from 's3mini';\nimport { serializeStoreValue, deserializeStoreValue } from '@walkeros/core';\nimport type { Store } from '@walkeros/core';\nimport type { S3StoreSettings, Types } from './types';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nconst JSON_CONTENT_TYPE = 'application/json';\nconst DEFAULT_CONTENT_TYPE = 'application/octet-stream';\n\n// Minimal extension to content-type map for the file-mode asset-serving use\n// case (serving walker.js and friends back with a correct mime). Anything not\n// listed falls back to application/octet-stream.\nconst MIME_BY_EXT: Readonly<Record<string, string>> = {\n js: 'application/javascript',\n mjs: 'application/javascript',\n json: 'application/json',\n css: 'text/css',\n html: 'text/html',\n txt: 'text/plain',\n svg: 'image/svg+xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n ico: 'image/x-icon',\n wasm: 'application/wasm',\n map: 'application/json',\n};\n\nfunction mimeFromKey(key: string): string {\n const dot = key.lastIndexOf('.');\n if (dot < 0 || dot === key.length - 1) return DEFAULT_CONTENT_TYPE;\n const ext = key.slice(dot + 1).toLowerCase();\n return MIME_BY_EXT[ext] ?? DEFAULT_CONTENT_TYPE;\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction buildEndpoint(endpoint: string, bucket: string): string {\n const base = endpoint.replace(/\\/+$/, '');\n return `${base}/${bucket}`;\n}\n\nfunction assertSettings(\n settings: Partial<S3StoreSettings> | undefined,\n): asserts settings is S3StoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.bucket is required (non-empty string)',\n );\n }\n if (typeof settings.endpoint !== 'string' || settings.endpoint.length === 0) {\n throw new Error(\n 'storeS3Init: settings.endpoint is required (non-empty string)',\n );\n }\n if (\n typeof settings.accessKeyId !== 'string' ||\n settings.accessKeyId.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.accessKeyId is required (non-empty string)',\n );\n }\n if (\n typeof settings.secretAccessKey !== 'string' ||\n settings.secretAccessKey.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.secretAccessKey is required (non-empty string)',\n );\n }\n}\n\nexport const storeS3Init: Store.Init<Types> = async (context) => {\n assertSettings(context.config.settings);\n const settings = context.config.settings;\n const prefix = normalizePrefix(settings.prefix);\n // One mode per instance, decided at init. file:true persists raw bytes\n // byte-exact with a real mime; structured mode round-trips StoreValue\n // through the shared core codec, stored as application/json.\n const fileMode = context.config.file === true;\n\n // An injected fetch (tests, simulate) carries every S3 request; s3mini\n // uses the global fetch when none is given.\n const injectedFetch = context.env?.fetch ?? context.config.env?.fetch;\n const client = new S3mini({\n endpoint: buildEndpoint(settings.endpoint, settings.bucket),\n accessKeyId: settings.accessKeyId,\n secretAccessKey: settings.secretAccessKey,\n region: settings.region || 'auto',\n ...(injectedFetch ? { fetch: injectedFetch } : {}),\n });\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n // Hard-fail with an actionable message if the bucket is missing.\n // Mirrors the BigQuery init-time hard-fail pattern.\n const exists = await client.bucketExists();\n if (!exists) {\n const setupId = context.id ?? 's3';\n const errorMsg =\n `S3 bucket not found: ${settings.bucket} at ${settings.endpoint}. ` +\n `Run \"walkeros setup store.${setupId}\" to create it.`;\n context.logger.error(errorMsg, {\n bucket: settings.bucket,\n endpoint: settings.endpoint,\n });\n throw new Error(errorMsg);\n }\n\n return {\n type: 's3',\n config: {\n settings,\n env: context.config.env,\n id: context.config.id,\n logger: context.config.logger,\n },\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const s3Key = resolveKey(key);\n if (!s3Key) return undefined;\n\n let bytes: Buffer;\n try {\n const arrayBuffer = await client.getObjectArrayBuffer(s3Key);\n if (!arrayBuffer) return undefined;\n bytes = Buffer.from(arrayBuffer);\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 // A corrupt or empty payload (e.g. a 0-byte object) degrades to a miss\n // 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 s3Key = resolveKey(key);\n if (!s3Key) return;\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 'storeS3Init.set: in file mode value must be Uint8Array or string; got ' +\n typeof value,\n );\n }\n await client.putObject(s3Key, value, mimeFromKey(key));\n return;\n }\n\n // Structured mode: serialize to utf8-JSON bytes, stored as JSON.\n await client.putObject(\n s3Key,\n serializeStoreValue(value),\n JSON_CONTENT_TYPE,\n );\n },\n\n async delete(key: string): Promise<void> {\n const s3Key = resolveKey(key);\n if (!s3Key) return;\n\n try {\n await client.deleteObject(s3Key);\n } catch {\n /* S3 delete is idempotent */\n }\n },\n };\n};\n","import { S3mini } from 's3mini';\nimport type { LifecycleContext, SetupFn, Store } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type { S3StoreSettings, S3StoreSetup, Types } from './types';\n\n/**\n * Default setup options. EU region by default per AGENT.md.\n */\nexport const DEFAULT_SETUP: Required<S3StoreSetup> = {\n region: 'eu-central-1',\n};\n\nexport interface SetupResult {\n bucketCreated: boolean;\n}\n\n/**\n * Public alias kept for callers that imported the prior shape.\n * Equivalent to the framework's `Store.Config<Types>`.\n */\nexport type S3StoreConfig = Store.Config<Types>;\n\n/**\n * Provision the S3 bucket described in the flow config. Idempotent. Triggered\n * only by the explicit `walkeros setup store.<id>` CLI command.\n *\n * Variant B: only the bucket is created. Encryption, public-access block,\n * versioning, lifecycle rules, and tags are not applied here because `s3mini`\n * does not expose the relevant API operations. Configure those once via the\n * AWS Console or `aws s3api`.\n */\nexport const setup: SetupFn<S3StoreConfig, Store.BaseEnv> = async (\n context: LifecycleContext<S3StoreConfig, Store.BaseEnv>,\n) => {\n const { config, logger } = context;\n\n const options = resolveSetup(config.setup, DEFAULT_SETUP);\n if (!options) {\n logger.debug('setup: skipped (config.setup is false or unset)');\n return;\n }\n\n assertS3Settings(config.settings);\n const settings = config.settings;\n\n // Region resolution per Open Question #4 (iii):\n // - explicit setup.region wins\n // - else fall back to settings.region when concrete (not 'auto')\n // - else default eu-central-1\n const explicitSetupRegion = readExplicitRegion(config.setup);\n const region = resolveRegion(explicitSetupRegion, settings.region);\n void options; // resolveSetup applies defaults; explicit branch handled separately\n\n const client = new S3mini({\n endpoint: buildEndpoint(settings.endpoint, settings.bucket),\n accessKeyId: settings.accessKeyId,\n secretAccessKey: settings.secretAccessKey,\n region,\n });\n\n const exists = await client.bucketExists();\n if (exists) {\n logger.debug('setup: bucket exists', { bucket: settings.bucket });\n return { bucketCreated: false };\n }\n\n try {\n await client.createBucket();\n logger.info('setup: bucket created', {\n bucket: settings.bucket,\n region,\n });\n return { bucketCreated: true };\n } catch (err: unknown) {\n if (isAlreadyOwnedByYou(err)) {\n logger.debug('setup: bucket created concurrently (race)', {\n bucket: settings.bucket,\n });\n return { bucketCreated: false };\n }\n if (isAlreadyExistsDifferentOwner(err)) {\n throw new Error(\n `S3 bucket name \"${settings.bucket}\" is already in use by another AWS account. ` +\n `Choose a different bucket name in settings.bucket.`,\n );\n }\n throw err;\n }\n};\n\nfunction resolveRegion(\n explicitSetupRegion: string | undefined,\n settingsRegion: string | undefined,\n): string {\n if (\n typeof explicitSetupRegion === 'string' &&\n explicitSetupRegion.length > 0\n ) {\n return explicitSetupRegion;\n }\n if (\n typeof settingsRegion === 'string' &&\n settingsRegion.length > 0 &&\n settingsRegion !== 'auto'\n ) {\n return settingsRegion;\n }\n return DEFAULT_SETUP.region;\n}\n\nfunction readExplicitRegion(raw: S3StoreConfig['setup']): string | undefined {\n if (!isRecord(raw)) return undefined;\n const value = raw.region;\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction assertS3Settings(\n settings: Partial<S3StoreSettings> | undefined,\n): asserts settings is S3StoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error('setup: settings.bucket is required (non-empty string)');\n }\n if (typeof settings.endpoint !== 'string' || settings.endpoint.length === 0) {\n throw new Error('setup: settings.endpoint is required (non-empty string)');\n }\n if (\n typeof settings.accessKeyId !== 'string' ||\n settings.accessKeyId.length === 0\n ) {\n throw new Error(\n 'setup: settings.accessKeyId is required (non-empty string)',\n );\n }\n if (\n typeof settings.secretAccessKey !== 'string' ||\n settings.secretAccessKey.length === 0\n ) {\n throw new Error(\n 'setup: settings.secretAccessKey is required (non-empty string)',\n );\n }\n}\n\nfunction buildEndpoint(endpoint: string, bucket: string): string {\n const base = endpoint.replace(/\\/+$/, '');\n return `${base}/${bucket}`;\n}\n\nfunction isAlreadyOwnedByYou(err: unknown): boolean {\n return hasErrorCode(err, 'BucketAlreadyOwnedByYou');\n}\n\nfunction isAlreadyExistsDifferentOwner(err: unknown): boolean {\n return hasErrorCode(err, 'BucketAlreadyExists');\n}\n\nfunction hasErrorCode(err: unknown, code: string): boolean {\n if (typeof err !== 'object' || err === null) return false;\n const obj: {\n code?: unknown;\n Code?: unknown;\n name?: unknown;\n serviceCode?: unknown;\n } = err;\n return (\n obj.code === code ||\n obj.Code === code ||\n obj.name === code ||\n obj.serviceCode === code\n );\n}\n","import type { Store, SetupFn } from '@walkeros/core';\nimport { storeS3Init } from './store';\nimport { setup } from './setup';\nimport type { Types } from './types';\nimport type { S3StoreConfig } from './setup';\n\nexport { storeS3Init } from './store';\nexport { setup, DEFAULT_SETUP } from './setup';\nexport type { SetupResult, S3StoreConfig } from './setup';\nexport type {\n Env,\n S3StoreSettings,\n S3StoreInitSettings,\n S3StoreSetup,\n Types,\n} from './types';\n\n/**\n * Default export shape for store packages. The CLI (`walkeros setup store.<id>`)\n * reads `default.setup` to find the lifecycle function; the collector reads\n * `default.init` (or the named export) to construct the runtime instance.\n *\n * Soft breaking change: callers using `import storeS3Init from '@walkeros/server-store-s3'`\n * (default import) must now call `.init` on the returned object. Named imports\n * (`import { storeS3Init }`) are unaffected.\n */\nexport interface StoreS3Module {\n type: 's3';\n init: Store.Init<Types>;\n setup: SetupFn<S3StoreConfig, Store.BaseEnv>;\n}\n\nconst storeS3: StoreS3Module = {\n type: 's3',\n init: storeS3Init,\n setup,\n};\n\nexport default storeS3;\n"],"mappings":"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","names":["S3mini","buildEndpoint"]}
1
+ {"version":3,"sources":["../src/store.ts","../src/setup.ts","../src/index.ts"],"sourcesContent":["import { S3mini } from 's3mini';\nimport { serializeStoreValue, deserializeStoreValue } from '@walkeros/core';\nimport type { Store } from '@walkeros/core';\nimport type { S3StoreSettings, Types } from './types';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nconst JSON_CONTENT_TYPE = 'application/json';\nconst DEFAULT_CONTENT_TYPE = 'application/octet-stream';\n\n// Minimal extension to content-type map for the file-mode asset-serving use\n// case (serving walker.js and friends back with a correct mime). Anything not\n// listed falls back to application/octet-stream.\nconst MIME_BY_EXT: Readonly<Record<string, string>> = {\n js: 'application/javascript',\n mjs: 'application/javascript',\n json: 'application/json',\n css: 'text/css',\n html: 'text/html',\n txt: 'text/plain',\n svg: 'image/svg+xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n ico: 'image/x-icon',\n wasm: 'application/wasm',\n map: 'application/json',\n};\n\nfunction mimeFromKey(key: string): string {\n const dot = key.lastIndexOf('.');\n if (dot < 0 || dot === key.length - 1) return DEFAULT_CONTENT_TYPE;\n const ext = key.slice(dot + 1).toLowerCase();\n return MIME_BY_EXT[ext] ?? DEFAULT_CONTENT_TYPE;\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction buildEndpoint(endpoint: string, bucket: string): string {\n const base = endpoint.replace(/\\/+$/, '');\n return `${base}/${bucket}`;\n}\n\nfunction assertSettings(\n settings: Partial<S3StoreSettings> | undefined,\n): asserts settings is S3StoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.bucket is required (non-empty string)',\n );\n }\n if (typeof settings.endpoint !== 'string' || settings.endpoint.length === 0) {\n throw new Error(\n 'storeS3Init: settings.endpoint is required (non-empty string)',\n );\n }\n if (\n typeof settings.accessKeyId !== 'string' ||\n settings.accessKeyId.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.accessKeyId is required (non-empty string)',\n );\n }\n if (\n typeof settings.secretAccessKey !== 'string' ||\n settings.secretAccessKey.length === 0\n ) {\n throw new Error(\n 'storeS3Init: settings.secretAccessKey is required (non-empty string)',\n );\n }\n}\n\nexport const storeS3Init: Store.Init<Types> = async (context) => {\n assertSettings(context.config.settings);\n const settings = context.config.settings;\n const prefix = normalizePrefix(settings.prefix);\n // One mode per instance, decided at init. file:true persists raw bytes\n // byte-exact with a real mime; structured mode round-trips StoreValue\n // through the shared core codec, stored as application/json.\n const fileMode = context.config.file === true;\n\n // An injected fetch (tests, simulate) carries every S3 request; s3mini\n // uses the global fetch when none is given.\n const injectedFetch = context.env?.fetch ?? context.config.env?.fetch;\n const client = new S3mini({\n endpoint: buildEndpoint(settings.endpoint, settings.bucket),\n accessKeyId: settings.accessKeyId,\n secretAccessKey: settings.secretAccessKey,\n region: settings.region || 'auto',\n ...(injectedFetch ? { fetch: injectedFetch } : {}),\n });\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n // Hard-fail with an actionable message if the bucket is missing.\n // Mirrors the BigQuery init-time hard-fail pattern.\n const exists = await client.bucketExists();\n if (!exists) {\n const setupId = context.id ?? 's3';\n const errorMsg =\n `S3 bucket not found: ${settings.bucket} at ${settings.endpoint}. ` +\n `Run \"walkeros setup store.${setupId}\" to create it.`;\n context.logger.error(errorMsg, {\n bucket: settings.bucket,\n endpoint: settings.endpoint,\n });\n throw new Error(errorMsg);\n }\n\n return {\n type: 's3',\n config: {\n settings,\n env: context.config.env,\n id: context.config.id,\n logger: context.config.logger,\n // The declared byte mode; state and validation read it from here.\n file: context.config.file,\n },\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const s3Key = resolveKey(key);\n if (!s3Key) return undefined;\n\n let bytes: Buffer;\n try {\n const arrayBuffer = await client.getObjectArrayBuffer(s3Key);\n if (!arrayBuffer) return undefined;\n bytes = Buffer.from(arrayBuffer);\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 // A corrupt or empty payload (e.g. a 0-byte object) degrades to a miss\n // 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 s3Key = resolveKey(key);\n if (!s3Key) return;\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 'storeS3Init.set: in file mode value must be Uint8Array or string; got ' +\n typeof value,\n );\n }\n await client.putObject(s3Key, value, mimeFromKey(key));\n return;\n }\n\n // Structured mode: serialize to utf8-JSON bytes, stored as JSON.\n await client.putObject(\n s3Key,\n serializeStoreValue(value),\n JSON_CONTENT_TYPE,\n );\n },\n\n async delete(key: string): Promise<void> {\n const s3Key = resolveKey(key);\n if (!s3Key) return;\n\n try {\n await client.deleteObject(s3Key);\n } catch {\n /* S3 delete is idempotent */\n }\n },\n };\n};\n","import { S3mini } from 's3mini';\nimport type { LifecycleContext, SetupFn, Store } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type { S3StoreSettings, S3StoreSetup, Types } from './types';\n\n/**\n * Default setup options. EU region by default per AGENT.md.\n */\nexport const DEFAULT_SETUP: Required<S3StoreSetup> = {\n region: 'eu-central-1',\n};\n\nexport interface SetupResult {\n bucketCreated: boolean;\n}\n\n/**\n * Public alias kept for callers that imported the prior shape.\n * Equivalent to the framework's `Store.Config<Types>`.\n */\nexport type S3StoreConfig = Store.Config<Types>;\n\n/**\n * Provision the S3 bucket described in the flow config. Idempotent. Triggered\n * only by the explicit `walkeros setup store.<id>` CLI command.\n *\n * Variant B: only the bucket is created. Encryption, public-access block,\n * versioning, lifecycle rules, and tags are not applied here because `s3mini`\n * does not expose the relevant API operations. Configure those once via the\n * AWS Console or `aws s3api`.\n */\nexport const setup: SetupFn<S3StoreConfig, Store.BaseEnv> = async (\n context: LifecycleContext<S3StoreConfig, Store.BaseEnv>,\n) => {\n const { config, logger } = context;\n\n const options = resolveSetup(config.setup, DEFAULT_SETUP);\n if (!options) {\n logger.debug('setup: skipped (config.setup is false or unset)');\n return;\n }\n\n assertS3Settings(config.settings);\n const settings = config.settings;\n\n // Region resolution per Open Question #4 (iii):\n // - explicit setup.region wins\n // - else fall back to settings.region when concrete (not 'auto')\n // - else default eu-central-1\n const explicitSetupRegion = readExplicitRegion(config.setup);\n const region = resolveRegion(explicitSetupRegion, settings.region);\n void options; // resolveSetup applies defaults; explicit branch handled separately\n\n const client = new S3mini({\n endpoint: buildEndpoint(settings.endpoint, settings.bucket),\n accessKeyId: settings.accessKeyId,\n secretAccessKey: settings.secretAccessKey,\n region,\n });\n\n const exists = await client.bucketExists();\n if (exists) {\n logger.debug('setup: bucket exists', { bucket: settings.bucket });\n return { bucketCreated: false };\n }\n\n try {\n await client.createBucket();\n logger.info('setup: bucket created', {\n bucket: settings.bucket,\n region,\n });\n return { bucketCreated: true };\n } catch (err: unknown) {\n if (isAlreadyOwnedByYou(err)) {\n logger.debug('setup: bucket created concurrently (race)', {\n bucket: settings.bucket,\n });\n return { bucketCreated: false };\n }\n if (isAlreadyExistsDifferentOwner(err)) {\n throw new Error(\n `S3 bucket name \"${settings.bucket}\" is already in use by another AWS account. ` +\n `Choose a different bucket name in settings.bucket.`,\n );\n }\n throw err;\n }\n};\n\nfunction resolveRegion(\n explicitSetupRegion: string | undefined,\n settingsRegion: string | undefined,\n): string {\n if (\n typeof explicitSetupRegion === 'string' &&\n explicitSetupRegion.length > 0\n ) {\n return explicitSetupRegion;\n }\n if (\n typeof settingsRegion === 'string' &&\n settingsRegion.length > 0 &&\n settingsRegion !== 'auto'\n ) {\n return settingsRegion;\n }\n return DEFAULT_SETUP.region;\n}\n\nfunction readExplicitRegion(raw: S3StoreConfig['setup']): string | undefined {\n if (!isRecord(raw)) return undefined;\n const value = raw.region;\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction assertS3Settings(\n settings: Partial<S3StoreSettings> | undefined,\n): asserts settings is S3StoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error('setup: settings.bucket is required (non-empty string)');\n }\n if (typeof settings.endpoint !== 'string' || settings.endpoint.length === 0) {\n throw new Error('setup: settings.endpoint is required (non-empty string)');\n }\n if (\n typeof settings.accessKeyId !== 'string' ||\n settings.accessKeyId.length === 0\n ) {\n throw new Error(\n 'setup: settings.accessKeyId is required (non-empty string)',\n );\n }\n if (\n typeof settings.secretAccessKey !== 'string' ||\n settings.secretAccessKey.length === 0\n ) {\n throw new Error(\n 'setup: settings.secretAccessKey is required (non-empty string)',\n );\n }\n}\n\nfunction buildEndpoint(endpoint: string, bucket: string): string {\n const base = endpoint.replace(/\\/+$/, '');\n return `${base}/${bucket}`;\n}\n\nfunction isAlreadyOwnedByYou(err: unknown): boolean {\n return hasErrorCode(err, 'BucketAlreadyOwnedByYou');\n}\n\nfunction isAlreadyExistsDifferentOwner(err: unknown): boolean {\n return hasErrorCode(err, 'BucketAlreadyExists');\n}\n\nfunction hasErrorCode(err: unknown, code: string): boolean {\n if (typeof err !== 'object' || err === null) return false;\n const obj: {\n code?: unknown;\n Code?: unknown;\n name?: unknown;\n serviceCode?: unknown;\n } = err;\n return (\n obj.code === code ||\n obj.Code === code ||\n obj.name === code ||\n obj.serviceCode === code\n );\n}\n","import type { Store, SetupFn } from '@walkeros/core';\nimport { storeS3Init } from './store';\nimport { setup } from './setup';\nimport type { Types } from './types';\nimport type { S3StoreConfig } from './setup';\n\nexport { storeS3Init } from './store';\nexport { setup, DEFAULT_SETUP } from './setup';\nexport type { SetupResult, S3StoreConfig } from './setup';\nexport type {\n Env,\n S3StoreSettings,\n S3StoreInitSettings,\n S3StoreSetup,\n Types,\n} from './types';\n\n/**\n * Default export shape for store packages. The CLI (`walkeros setup store.<id>`)\n * reads `default.setup` to find the lifecycle function; the collector reads\n * `default.init` (or the named export) to construct the runtime instance.\n *\n * Soft breaking change: callers using `import storeS3Init from '@walkeros/server-store-s3'`\n * (default import) must now call `.init` on the returned object. Named imports\n * (`import { storeS3Init }`) are unaffected.\n */\nexport interface StoreS3Module {\n type: 's3';\n init: Store.Init<Types>;\n setup: SetupFn<S3StoreConfig, Store.BaseEnv>;\n}\n\nconst storeS3: StoreS3Module = {\n type: 's3',\n init: storeS3Init,\n setup,\n};\n\nexport default storeS3;\n"],"mappings":"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","names":["S3mini","buildEndpoint"]}
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "$meta": {
3
3
  "package": "@walkeros/server-store-s3",
4
- "version": "4.7.0-next-1790722899482",
4
+ "version": "4.7.0",
5
5
  "type": "store",
6
6
  "platform": [
7
7
  "server"
8
8
  ],
9
+ "exports": {},
9
10
  "docs": "https://www.walkeros.io/docs/stores/server/s3",
10
11
  "source": "https://github.com/elbwalker/walkerOS/tree/main/packages/server/stores/s3/src"
11
12
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walkeros/server-store-s3",
3
3
  "description": "S3-compatible object storage for walkerOS server flows",
4
- "version": "4.7.0-next-1790722899482",
4
+ "version": "4.7.0",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -33,7 +33,7 @@
33
33
  "update": "npx npm-check-updates -u && npm update"
34
34
  },
35
35
  "dependencies": {
36
- "@walkeros/core": "4.7.0-next-1790722899482",
36
+ "@walkeros/core": "4.7.0",
37
37
  "s3mini": "^0.9.1"
38
38
  },
39
39
  "devDependencies": {},