@walkeros/server-store-gcs 4.0.0 → 4.1.0-next-1778155282668

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,10 +65,69 @@ await store.delete('old-file.txt');
65
65
 
66
66
  | Setting | Type | Required | Default | Description |
67
67
  | ------------- | ------------------ | -------- | ------- | ------------------------------ |
68
- | `bucket` | `string` | Yes | | GCS bucket name |
69
- | `prefix` | `string` | No | | Key prefix for scoping |
68
+ | `bucket` | `string` | Yes | (none) | GCS bucket name |
69
+ | `prefix` | `string` | No | (none) | Key prefix for scoping |
70
70
  | `credentials` | `string \| object` | No | ADC | Service account JSON or string |
71
71
 
72
+ ## Provisioning
73
+
74
+ The package ships an idempotent `setup()` lifecycle that creates the GCS bucket
75
+ described in flow config. It is invoked only by the explicit operator command:
76
+
77
+ ```bash
78
+ walkeros setup store.<id>
79
+ ```
80
+
81
+ It never runs automatically and never alters an existing bucket.
82
+
83
+ ### `Setup` options
84
+
85
+ | Option | Type | Default | Notes |
86
+ | -------------- | ----------------------------------------------------- | ------------ | -------------------------------------------------------------- |
87
+ | `projectId` | `string` | (resolved) | GCP project that owns the bucket. Resolution order below. |
88
+ | `location` | `string` | `'EU'` | Multi-region or regional location. |
89
+ | `storageClass` | `'STANDARD' \| 'NEARLINE' \| 'COLDLINE' \| 'ARCHIVE'` | `'STANDARD'` | Default object storage class. |
90
+ | `versioning` | `boolean` | `false` | Object versioning. Off by default; opt in. |
91
+ | `lifecycle` | `{ rule: unknown[] }` | (none) | Applied at create. Drift detection NOT included for lifecycle. |
92
+ | `kmsKeyName` | `string` | (none) | Customer-managed encryption key (CMEK) at create time. |
93
+ | `labels` | `Record<string, string>` | (none) | Cost-allocation labels. |
94
+
95
+ `bucket` is taken from `settings.bucket` and is NOT duplicated under `setup`.
96
+
97
+ ### `projectId` resolution
98
+
99
+ The GCS create call requires a project. Resolution order:
100
+
101
+ 1. Explicit `setup.projectId`.
102
+ 2. `project_id` field inside the `settings.credentials` service-account JSON.
103
+ 3. `process.env.GOOGLE_CLOUD_PROJECT` (Cloud Run / GKE convention).
104
+ 4. Throws with an actionable error if none of the above is available.
105
+
106
+ ### Behavior
107
+
108
+ - **Idempotent**: HTTP 409 (bucket exists) is treated as success. The setup
109
+ never patches or mutates an existing bucket.
110
+ - **Drift detection**: when the bucket already exists, setup performs a
111
+ `GET /b/<bucket>` and logs `WARN setup.drift { field, declared, actual }` for
112
+ any of `location`, `storageClass`, `versioning`, `iamConfiguration` (uniform
113
+ bucket-level access, public access prevention), and `labels` that do not
114
+ match. Drift is logged, never auto-fixed.
115
+ - **Defaults enforced at create**: uniform bucket-level access on, public access
116
+ prevention enforced. These are baked in by the package.
117
+
118
+ ### Runtime hard-fail
119
+
120
+ At runtime, the first `get` / `set` / `delete` call issues a single
121
+ `HEAD /b/<bucket>` per process per bucket. On 404, it throws with an actionable
122
+ message:
123
+
124
+ ```
125
+ GCS bucket not found: <bucket> in project <projectId>. Run "walkeros setup store.<id>" to create it.
126
+ ```
127
+
128
+ Operators see the error pointing at the exact command to fix it. Subsequent
129
+ operations in the same process skip the check via an in-memory cache.
130
+
72
131
  ## Authentication
73
132
 
74
133
  ### Cloud Run / GKE (ADC)
package/dist/dev.d.mts CHANGED
@@ -12,13 +12,30 @@ declare const SettingsSchema: z.ZodObject<{
12
12
  }, z.core.$strip>;
13
13
  type Settings = z.infer<typeof SettingsSchema>;
14
14
 
15
+ declare const SetupSchema: z.ZodObject<{
16
+ projectId: z.ZodOptional<z.ZodString>;
17
+ location: z.ZodDefault<z.ZodString>;
18
+ storageClass: z.ZodDefault<z.ZodEnum<{
19
+ STANDARD: "STANDARD";
20
+ NEARLINE: "NEARLINE";
21
+ COLDLINE: "COLDLINE";
22
+ ARCHIVE: "ARCHIVE";
23
+ }>>;
24
+ versioning: z.ZodDefault<z.ZodBoolean>;
25
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
26
+ kmsKeyName: z.ZodOptional<z.ZodString>;
27
+ }, z.core.$strip>;
28
+
15
29
  declare const settings: _walkeros_core_dev.JSONSchema;
30
+ declare const setup: _walkeros_core_dev.JSONSchema;
16
31
 
17
32
  type index$1_Settings = Settings;
18
33
  declare const index$1_SettingsSchema: typeof SettingsSchema;
34
+ declare const index$1_SetupSchema: typeof SetupSchema;
19
35
  declare const index$1_settings: typeof settings;
36
+ declare const index$1_setup: typeof setup;
20
37
  declare namespace index$1 {
21
- export { type index$1_Settings as Settings, index$1_SettingsSchema as SettingsSchema, index$1_settings as settings };
38
+ export { type index$1_Settings as Settings, index$1_SettingsSchema as SettingsSchema, index$1_SetupSchema as SetupSchema, index$1_settings as settings, index$1_setup as setup };
22
39
  }
23
40
 
24
41
  /** Read a file using Application Default Credentials (Cloud Run / GKE). */
package/dist/dev.d.ts CHANGED
@@ -12,13 +12,30 @@ declare const SettingsSchema: z.ZodObject<{
12
12
  }, z.core.$strip>;
13
13
  type Settings = z.infer<typeof SettingsSchema>;
14
14
 
15
+ declare const SetupSchema: z.ZodObject<{
16
+ projectId: z.ZodOptional<z.ZodString>;
17
+ location: z.ZodDefault<z.ZodString>;
18
+ storageClass: z.ZodDefault<z.ZodEnum<{
19
+ STANDARD: "STANDARD";
20
+ NEARLINE: "NEARLINE";
21
+ COLDLINE: "COLDLINE";
22
+ ARCHIVE: "ARCHIVE";
23
+ }>>;
24
+ versioning: z.ZodDefault<z.ZodBoolean>;
25
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
26
+ kmsKeyName: z.ZodOptional<z.ZodString>;
27
+ }, z.core.$strip>;
28
+
15
29
  declare const settings: _walkeros_core_dev.JSONSchema;
30
+ declare const setup: _walkeros_core_dev.JSONSchema;
16
31
 
17
32
  type index$1_Settings = Settings;
18
33
  declare const index$1_SettingsSchema: typeof SettingsSchema;
34
+ declare const index$1_SetupSchema: typeof SetupSchema;
19
35
  declare const index$1_settings: typeof settings;
36
+ declare const index$1_setup: typeof setup;
20
37
  declare namespace index$1 {
21
- export { type index$1_Settings as Settings, index$1_SettingsSchema as SettingsSchema, index$1_settings as settings };
38
+ export { type index$1_Settings as Settings, index$1_SettingsSchema as SettingsSchema, index$1_SetupSchema as SetupSchema, index$1_settings as settings, index$1_setup as setup };
22
39
  }
23
40
 
24
41
  /** Read a file using Application Default Credentials (Cloud Run / GKE). */
package/dist/dev.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,s=Object.prototype.hasOwnProperty,o=(e,r)=>{for(var i in r)t(e,i,{get:r[i],enumerable:!0})},c={};o(c,{examples:()=>b,schemas:()=>n}),module.exports=(e=c,((e,o,c,n)=>{if(o&&"object"==typeof o||"function"==typeof o)for(let a of i(o))s.call(e,a)||a===c||t(e,a,{get:()=>o[a],enumerable:!(n=r(o,a))||n.enumerable});return e})(t({},"__esModule",{value:!0}),e));var n={};o(n,{SettingsSchema:()=>p,settings:()=>u});var a=require("@walkeros/core/dev"),l=require("@walkeros/core/dev"),p=l.z.object({bucket:l.z.string().min(1).describe("GCS bucket name"),prefix:l.z.string().optional().describe("Key prefix prepended to all store keys for scoping"),credentials:l.z.union([l.z.string(),l.z.object({client_email:l.z.string().describe("Service account email"),private_key:l.z.string().describe("RSA private key in PEM format")})]).optional().describe("Service account JSON (string or object). Omit for ADC on Cloud Run/GKE")}),u=(0,a.zodToSchema)(p),b={};o(b,{cloudRunAdc:()=>k,serviceAccount:()=>m,step:()=>d});var d={};o(d,{prefixScoping:()=>g,readWithAdc:()=>f});var f={title:"Read with ADC",description:"Read object from GCS bucket using ADC — no credentials needed",in:{operation:"get",key:"walker.js"},out:[["get","walker.js","Buffer<(function(){...})()>"]]},g={title:"Prefix scoping",description:'Key "walker.js" with prefix "public/" resolves to GCS path "public/walker.js"',in:{operation:"get",key:"walker.js",settings:{bucket:"my-assets",prefix:"public"}},out:[["get","public/walker.js","Buffer<...>"]]},k={settings:{bucket:"my-assets",prefix:"public"}},m={settings:{bucket:"my-assets",prefix:"public",credentials:"$env.GCS_SA_KEY"}};//# sourceMappingURL=dev.js.map
1
+ "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,s=(e,r)=>{for(var i in r)t(e,i,{get:r[i],enumerable:!0})},n={};s(n,{examples:()=>f,schemas:()=>c}),module.exports=(e=n,((e,s,n,c)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let a of i(s))o.call(e,a)||a===n||t(e,a,{get:()=>s[a],enumerable:!(c=r(s,a))||c.enumerable});return e})(t({},"__esModule",{value:!0}),e));var c={};s(c,{SettingsSchema:()=>p,SetupSchema:()=>d,settings:()=>b,setup:()=>g});var a=require("@walkeros/core/dev"),l=require("@walkeros/core/dev"),p=l.z.object({bucket:l.z.string().min(1).describe("GCS bucket name"),prefix:l.z.string().optional().describe("Key prefix prepended to all store keys for scoping"),credentials:l.z.union([l.z.string(),l.z.object({client_email:l.z.string().describe("Service account email"),private_key:l.z.string().describe("RSA private key in PEM format")})]).optional().describe("Service account JSON (string or object). Omit for ADC on Cloud Run/GKE")}),u=require("@walkeros/core/dev"),d=u.z.object({projectId:u.z.string().optional().describe("GCP project that owns the bucket. Falls back to credentials.project_id then GOOGLE_CLOUD_PROJECT env"),location:u.z.string().default("EU").describe("Geographic location (multi-region or region code)"),storageClass:u.z.enum(["STANDARD","NEARLINE","COLDLINE","ARCHIVE"]).default("STANDARD").describe("Default storage class for objects"),versioning:u.z.boolean().default(!1).describe("Enable object versioning at create time"),labels:u.z.record(u.z.string(),u.z.string()).optional().describe("Labels for cost allocation"),kmsKeyName:u.z.string().optional().describe("Customer-managed encryption key for at-rest encryption")}).describe('Provisioning options for "walkeros setup store.<id>". Idempotent: never mutates an existing bucket.'),b=(0,a.zodToSchema)(p),g=(0,a.zodToSchema)(d),f={};s(f,{cloudRunAdc:()=>j,serviceAccount:()=>y,step:()=>m});var m={};s(m,{prefixScoping:()=>v,readWithAdc:()=>k});var k={title:"Read with ADC",description:"Read object from GCS bucket using ADC — no credentials needed",in:{operation:"get",key:"walker.js"},out:[["get","walker.js","Buffer<(function(){...})()>"]]},v={title:"Prefix scoping",description:'Key "walker.js" with prefix "public/" resolves to GCS path "public/walker.js"',in:{operation:"get",key:"walker.js",settings:{bucket:"my-assets",prefix:"public"}},out:[["get","public/walker.js","Buffer<...>"]]},j={settings:{bucket:"my-assets",prefix:"public"}},y={settings:{bucket:"my-assets",prefix:"public",credentials:"$env.GCS_SA_KEY"}};//# 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"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\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 bucket: z.string().min(1).describe('GCS bucket name'),\n prefix: z\n .string()\n .optional()\n .describe('Key prefix prepended to all store keys for scoping'),\n credentials: z\n .union([\n z.string(),\n z.object({\n client_email: z.string().describe('Service account email'),\n private_key: z.string().describe('RSA private key in PEM format'),\n }),\n ])\n .optional()\n .describe(\n 'Service account JSON (string or object). Omit for ADC on Cloud Run/GKE',\n ),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Cloud Run with ADC — no credentials needed */\nexport const cloudRunAdc: Store.Config = {\n settings: {\n bucket: 'my-assets',\n prefix: 'public',\n },\n};\n\n/** Explicit service account for non-GCP environments */\nexport const serviceAccount: Store.Config = {\n settings: {\n bucket: 'my-assets',\n prefix: 'public',\n credentials: '$env.GCS_SA_KEY',\n },\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a file using Application Default Credentials (Cloud Run / GKE). */\nexport const readWithAdc: Flow.StepExample = {\n title: 'Read with ADC',\n description: 'Read object from GCS bucket using ADC — no credentials needed',\n in: { operation: 'get', key: 'walker.js' },\n out: [['get', 'walker.js', 'Buffer<(function(){...})()>']],\n};\n\n/** Key is scoped under the configured prefix subdirectory. */\nexport const prefixScoping: Flow.StepExample = {\n title: 'Prefix scoping',\n description:\n 'Key \"walker.js\" with prefix \"public/\" resolves to GCS path \"public/walker.js\"',\n in: {\n operation: 'get',\n key: 'walker.js',\n settings: { bucket: 'my-assets', prefix: 'public' },\n },\n out: [['get', 'public/walker.js', 'Buffer<...>']],\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,cAA4B;;;ACA5B,iBAAkB;AAEX,IAAM,iBAAiB,aAAE,OAAO;AAAA,EACrC,QAAQ,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,iBAAiB;AAAA,EACpD,QAAQ,aACL,OAAO,EACP,SAAS,EACT,SAAS,oDAAoD;AAAA,EAChE,aAAa,aACV,MAAM;AAAA,IACL,aAAE,OAAO;AAAA,IACT,aAAE,OAAO;AAAA,MACP,cAAc,aAAE,OAAO,EAAE,SAAS,uBAAuB;AAAA,MACzD,aAAa,aAAE,OAAO,EAAE,SAAS,+BAA+B;AAAA,IAClE,CAAC;AAAA,EACH,CAAC,EACA,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC;;;ADfM,IAAM,eAAW,yBAAY,cAAc;;;AELlD;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,cAAgC;AAAA,EAC3C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,CAAC,CAAC,OAAO,aAAa,6BAA6B,CAAC;AAC3D;AAGO,IAAM,gBAAkC;AAAA,EAC7C,OAAO;AAAA,EACP,aACE;AAAA,EACF,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,UAAU,EAAE,QAAQ,aAAa,QAAQ,SAAS;AAAA,EACpD;AAAA,EACA,KAAK,CAAC,CAAC,OAAO,oBAAoB,aAAa,CAAC;AAClD;;;ADlBO,IAAM,cAA4B;AAAA,EACvC,UAAU;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAGO,IAAM,iBAA+B;AAAA,EAC1C,UAAU;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AACF;","names":["import_dev"]}
1
+ {"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/setup.ts","../src/examples/index.ts","../src/examples/step.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\nimport { SetupSchema } from './setup';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport { SetupSchema } from './setup';\n\nexport const settings = zodToSchema(SettingsSchema);\nexport const setup = zodToSchema(SetupSchema);\n","import { z } from '@walkeros/core/dev';\n\nexport const SettingsSchema = z.object({\n bucket: z.string().min(1).describe('GCS bucket name'),\n prefix: z\n .string()\n .optional()\n .describe('Key prefix prepended to all store keys for scoping'),\n credentials: z\n .union([\n z.string(),\n z.object({\n client_email: z.string().describe('Service account email'),\n private_key: z.string().describe('RSA private key in PEM format'),\n }),\n ])\n .optional()\n .describe(\n 'Service account JSON (string or object). Omit for ADC on Cloud Run/GKE',\n ),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\nexport const SetupSchema = z\n .object({\n projectId: z\n .string()\n .optional()\n .describe(\n 'GCP project that owns the bucket. Falls back to credentials.project_id then GOOGLE_CLOUD_PROJECT env',\n ),\n location: z\n .string()\n .default('EU')\n .describe('Geographic location (multi-region or region code)'),\n storageClass: z\n .enum(['STANDARD', 'NEARLINE', 'COLDLINE', 'ARCHIVE'])\n .default('STANDARD')\n .describe('Default storage class for objects'),\n versioning: z\n .boolean()\n .default(false)\n .describe('Enable object versioning at create time'),\n labels: z\n .record(z.string(), z.string())\n .optional()\n .describe('Labels for cost allocation'),\n kmsKeyName: z\n .string()\n .optional()\n .describe('Customer-managed encryption key for at-rest encryption'),\n // lifecycle rules intentionally omitted from this schema: the type is\n // deeply nested and not useful to autocomplete in MCP discovery for v1.\n })\n .describe(\n 'Provisioning options for \"walkeros setup store.<id>\". Idempotent: never mutates an existing bucket.',\n );\n\nexport type Setup = z.infer<typeof SetupSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Cloud Run with ADC — no credentials needed */\nexport const cloudRunAdc: Store.Config = {\n settings: {\n bucket: 'my-assets',\n prefix: 'public',\n },\n};\n\n/** Explicit service account for non-GCP environments */\nexport const serviceAccount: Store.Config = {\n settings: {\n bucket: 'my-assets',\n prefix: 'public',\n credentials: '$env.GCS_SA_KEY',\n },\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a file using Application Default Credentials (Cloud Run / GKE). */\nexport const readWithAdc: Flow.StepExample = {\n title: 'Read with ADC',\n description: 'Read object from GCS bucket using ADC — no credentials needed',\n in: { operation: 'get', key: 'walker.js' },\n out: [['get', 'walker.js', 'Buffer<(function(){...})()>']],\n};\n\n/** Key is scoped under the configured prefix subdirectory. */\nexport const prefixScoping: Flow.StepExample = {\n title: 'Prefix scoping',\n description:\n 'Key \"walker.js\" with prefix \"public/\" resolves to GCS path \"public/walker.js\"',\n in: {\n operation: 'get',\n key: 'walker.js',\n settings: { bucket: 'my-assets', prefix: 'public' },\n },\n out: [['get', 'public/walker.js', 'Buffer<...>']],\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,cAA4B;;;ACA5B,iBAAkB;AAEX,IAAM,iBAAiB,aAAE,OAAO;AAAA,EACrC,QAAQ,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,iBAAiB;AAAA,EACpD,QAAQ,aACL,OAAO,EACP,SAAS,EACT,SAAS,oDAAoD;AAAA,EAChE,aAAa,aACV,MAAM;AAAA,IACL,aAAE,OAAO;AAAA,IACT,aAAE,OAAO;AAAA,MACP,cAAc,aAAE,OAAO,EAAE,SAAS,uBAAuB;AAAA,MACzD,aAAa,aAAE,OAAO,EAAE,SAAS,+BAA+B;AAAA,IAClE,CAAC;AAAA,EACH,CAAC,EACA,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC;;;ACpBD,IAAAC,cAAkB;AAEX,IAAM,cAAc,cACxB,OAAO;AAAA,EACN,WAAW,cACR,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,UAAU,cACP,OAAO,EACP,QAAQ,IAAI,EACZ,SAAS,mDAAmD;AAAA,EAC/D,cAAc,cACX,KAAK,CAAC,YAAY,YAAY,YAAY,SAAS,CAAC,EACpD,QAAQ,UAAU,EAClB,SAAS,mCAAmC;AAAA,EAC/C,YAAY,cACT,QAAQ,EACR,QAAQ,KAAK,EACb,SAAS,yCAAyC;AAAA,EACrD,QAAQ,cACL,OAAO,cAAE,OAAO,GAAG,cAAE,OAAO,CAAC,EAC7B,SAAS,EACT,SAAS,4BAA4B;AAAA,EACxC,YAAY,cACT,OAAO,EACP,SAAS,EACT,SAAS,wDAAwD;AAAA;AAAA;AAGtE,CAAC,EACA;AAAA,EACC;AACF;;;AF5BK,IAAM,eAAW,yBAAY,cAAc;AAC3C,IAAM,YAAQ,yBAAY,WAAW;;;AGR5C;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,cAAgC;AAAA,EAC3C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,CAAC,CAAC,OAAO,aAAa,6BAA6B,CAAC;AAC3D;AAGO,IAAM,gBAAkC;AAAA,EAC7C,OAAO;AAAA,EACP,aACE;AAAA,EACF,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,UAAU,EAAE,QAAQ,aAAa,QAAQ,SAAS;AAAA,EACpD;AAAA,EACA,KAAK,CAAC,CAAC,OAAO,oBAAoB,aAAa,CAAC;AAClD;;;ADlBO,IAAM,cAA4B;AAAA,EACvC,UAAU;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAGO,IAAM,iBAA+B;AAAA,EAC1C,UAAU;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AACF;","names":["import_dev","import_dev"]}
package/dist/dev.mjs CHANGED
@@ -1 +1 @@
1
- var e=Object.defineProperty,t=(t,i)=>{for(var r in i)e(t,r,{get:i[r],enumerable:!0})},i={};t(i,{SettingsSchema:()=>c,settings:()=>o});import{zodToSchema as r}from"@walkeros/core/dev";import{z as s}from"@walkeros/core/dev";var c=s.object({bucket:s.string().min(1).describe("GCS bucket name"),prefix:s.string().optional().describe("Key prefix prepended to all store keys for scoping"),credentials:s.union([s.string(),s.object({client_email:s.string().describe("Service account email"),private_key:s.string().describe("RSA private key in PEM format")})]).optional().describe("Service account JSON (string or object). Omit for ADC on Cloud Run/GKE")}),o=r(c),n={};t(n,{cloudRunAdc:()=>u,serviceAccount:()=>d,step:()=>a});var a={};t(a,{prefixScoping:()=>l,readWithAdc:()=>p});var p={title:"Read with ADC",description:"Read object from GCS bucket using ADC — no credentials needed",in:{operation:"get",key:"walker.js"},out:[["get","walker.js","Buffer<(function(){...})()>"]]},l={title:"Prefix scoping",description:'Key "walker.js" with prefix "public/" resolves to GCS path "public/walker.js"',in:{operation:"get",key:"walker.js",settings:{bucket:"my-assets",prefix:"public"}},out:[["get","public/walker.js","Buffer<...>"]]},u={settings:{bucket:"my-assets",prefix:"public"}},d={settings:{bucket:"my-assets",prefix:"public",credentials:"$env.GCS_SA_KEY"}};export{n as examples,i as schemas};//# sourceMappingURL=dev.mjs.map
1
+ var e=Object.defineProperty,t=(t,r)=>{for(var i in r)e(t,i,{get:r[i],enumerable:!0})},r={};t(r,{SettingsSchema:()=>s,SetupSchema:()=>a,settings:()=>c,setup:()=>l});import{zodToSchema as i}from"@walkeros/core/dev";import{z as o}from"@walkeros/core/dev";var s=o.object({bucket:o.string().min(1).describe("GCS bucket name"),prefix:o.string().optional().describe("Key prefix prepended to all store keys for scoping"),credentials:o.union([o.string(),o.object({client_email:o.string().describe("Service account email"),private_key:o.string().describe("RSA private key in PEM format")})]).optional().describe("Service account JSON (string or object). Omit for ADC on Cloud Run/GKE")});import{z as n}from"@walkeros/core/dev";var a=n.object({projectId:n.string().optional().describe("GCP project that owns the bucket. Falls back to credentials.project_id then GOOGLE_CLOUD_PROJECT env"),location:n.string().default("EU").describe("Geographic location (multi-region or region code)"),storageClass:n.enum(["STANDARD","NEARLINE","COLDLINE","ARCHIVE"]).default("STANDARD").describe("Default storage class for objects"),versioning:n.boolean().default(!1).describe("Enable object versioning at create time"),labels:n.record(n.string(),n.string()).optional().describe("Labels for cost allocation"),kmsKeyName:n.string().optional().describe("Customer-managed encryption key for at-rest encryption")}).describe('Provisioning options for "walkeros setup store.<id>". Idempotent: never mutates an existing bucket.'),c=i(s),l=i(a),p={};t(p,{cloudRunAdc:()=>g,serviceAccount:()=>f,step:()=>d});var d={};t(d,{prefixScoping:()=>u,readWithAdc:()=>b});var b={title:"Read with ADC",description:"Read object from GCS bucket using ADC — no credentials needed",in:{operation:"get",key:"walker.js"},out:[["get","walker.js","Buffer<(function(){...})()>"]]},u={title:"Prefix scoping",description:'Key "walker.js" with prefix "public/" resolves to GCS path "public/walker.js"',in:{operation:"get",key:"walker.js",settings:{bucket:"my-assets",prefix:"public"}},out:[["get","public/walker.js","Buffer<...>"]]},g={settings:{bucket:"my-assets",prefix:"public"}},f={settings:{bucket:"my-assets",prefix:"public",credentials:"$env.GCS_SA_KEY"}};export{p as examples,r 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"],"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 bucket: z.string().min(1).describe('GCS bucket name'),\n prefix: z\n .string()\n .optional()\n .describe('Key prefix prepended to all store keys for scoping'),\n credentials: z\n .union([\n z.string(),\n z.object({\n client_email: z.string().describe('Service account email'),\n private_key: z.string().describe('RSA private key in PEM format'),\n }),\n ])\n .optional()\n .describe(\n 'Service account JSON (string or object). Omit for ADC on Cloud Run/GKE',\n ),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Cloud Run with ADC — no credentials needed */\nexport const cloudRunAdc: Store.Config = {\n settings: {\n bucket: 'my-assets',\n prefix: 'public',\n },\n};\n\n/** Explicit service account for non-GCP environments */\nexport const serviceAccount: Store.Config = {\n settings: {\n bucket: 'my-assets',\n prefix: 'public',\n credentials: '$env.GCS_SA_KEY',\n },\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a file using Application Default Credentials (Cloud Run / GKE). */\nexport const readWithAdc: Flow.StepExample = {\n title: 'Read with ADC',\n description: 'Read object from GCS bucket using ADC — no credentials needed',\n in: { operation: 'get', key: 'walker.js' },\n out: [['get', 'walker.js', 'Buffer<(function(){...})()>']],\n};\n\n/** Key is scoped under the configured prefix subdirectory. */\nexport const prefixScoping: Flow.StepExample = {\n title: 'Prefix scoping',\n description:\n 'Key \"walker.js\" with prefix \"public/\" resolves to GCS path \"public/walker.js\"',\n in: {\n operation: 'get',\n key: 'walker.js',\n settings: { bucket: 'my-assets', prefix: 'public' },\n },\n out: [['get', 'public/walker.js', 'Buffer<...>']],\n};\n"],"mappings":";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;;;ACA5B,SAAS,SAAS;AAEX,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,iBAAiB;AAAA,EACpD,QAAQ,EACL,OAAO,EACP,SAAS,EACT,SAAS,oDAAoD;AAAA,EAChE,aAAa,EACV,MAAM;AAAA,IACL,EAAE,OAAO;AAAA,IACT,EAAE,OAAO;AAAA,MACP,cAAc,EAAE,OAAO,EAAE,SAAS,uBAAuB;AAAA,MACzD,aAAa,EAAE,OAAO,EAAE,SAAS,+BAA+B;AAAA,IAClE,CAAC;AAAA,EACH,CAAC,EACA,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC;;;ADfM,IAAM,WAAW,YAAY,cAAc;;;AELlD;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,cAAgC;AAAA,EAC3C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,CAAC,CAAC,OAAO,aAAa,6BAA6B,CAAC;AAC3D;AAGO,IAAM,gBAAkC;AAAA,EAC7C,OAAO;AAAA,EACP,aACE;AAAA,EACF,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,UAAU,EAAE,QAAQ,aAAa,QAAQ,SAAS;AAAA,EACpD;AAAA,EACA,KAAK,CAAC,CAAC,OAAO,oBAAoB,aAAa,CAAC;AAClD;;;ADlBO,IAAM,cAA4B;AAAA,EACvC,UAAU;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAGO,IAAM,iBAA+B;AAAA,EAC1C,UAAU;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/setup.ts","../src/examples/index.ts","../src/examples/step.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\nimport { SetupSchema } from './setup';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport { SetupSchema } from './setup';\n\nexport const settings = zodToSchema(SettingsSchema);\nexport const setup = zodToSchema(SetupSchema);\n","import { z } from '@walkeros/core/dev';\n\nexport const SettingsSchema = z.object({\n bucket: z.string().min(1).describe('GCS bucket name'),\n prefix: z\n .string()\n .optional()\n .describe('Key prefix prepended to all store keys for scoping'),\n credentials: z\n .union([\n z.string(),\n z.object({\n client_email: z.string().describe('Service account email'),\n private_key: z.string().describe('RSA private key in PEM format'),\n }),\n ])\n .optional()\n .describe(\n 'Service account JSON (string or object). Omit for ADC on Cloud Run/GKE',\n ),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\nexport const SetupSchema = z\n .object({\n projectId: z\n .string()\n .optional()\n .describe(\n 'GCP project that owns the bucket. Falls back to credentials.project_id then GOOGLE_CLOUD_PROJECT env',\n ),\n location: z\n .string()\n .default('EU')\n .describe('Geographic location (multi-region or region code)'),\n storageClass: z\n .enum(['STANDARD', 'NEARLINE', 'COLDLINE', 'ARCHIVE'])\n .default('STANDARD')\n .describe('Default storage class for objects'),\n versioning: z\n .boolean()\n .default(false)\n .describe('Enable object versioning at create time'),\n labels: z\n .record(z.string(), z.string())\n .optional()\n .describe('Labels for cost allocation'),\n kmsKeyName: z\n .string()\n .optional()\n .describe('Customer-managed encryption key for at-rest encryption'),\n // lifecycle rules intentionally omitted from this schema: the type is\n // deeply nested and not useful to autocomplete in MCP discovery for v1.\n })\n .describe(\n 'Provisioning options for \"walkeros setup store.<id>\". Idempotent: never mutates an existing bucket.',\n );\n\nexport type Setup = z.infer<typeof SetupSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Cloud Run with ADC — no credentials needed */\nexport const cloudRunAdc: Store.Config = {\n settings: {\n bucket: 'my-assets',\n prefix: 'public',\n },\n};\n\n/** Explicit service account for non-GCP environments */\nexport const serviceAccount: Store.Config = {\n settings: {\n bucket: 'my-assets',\n prefix: 'public',\n credentials: '$env.GCS_SA_KEY',\n },\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a file using Application Default Credentials (Cloud Run / GKE). */\nexport const readWithAdc: Flow.StepExample = {\n title: 'Read with ADC',\n description: 'Read object from GCS bucket using ADC — no credentials needed',\n in: { operation: 'get', key: 'walker.js' },\n out: [['get', 'walker.js', 'Buffer<(function(){...})()>']],\n};\n\n/** Key is scoped under the configured prefix subdirectory. */\nexport const prefixScoping: Flow.StepExample = {\n title: 'Prefix scoping',\n description:\n 'Key \"walker.js\" with prefix \"public/\" resolves to GCS path \"public/walker.js\"',\n in: {\n operation: 'get',\n key: 'walker.js',\n settings: { bucket: 'my-assets', prefix: 'public' },\n },\n out: [['get', 'public/walker.js', 'Buffer<...>']],\n};\n"],"mappings":";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;;;ACA5B,SAAS,SAAS;AAEX,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,iBAAiB;AAAA,EACpD,QAAQ,EACL,OAAO,EACP,SAAS,EACT,SAAS,oDAAoD;AAAA,EAChE,aAAa,EACV,MAAM;AAAA,IACL,EAAE,OAAO;AAAA,IACT,EAAE,OAAO;AAAA,MACP,cAAc,EAAE,OAAO,EAAE,SAAS,uBAAuB;AAAA,MACzD,aAAa,EAAE,OAAO,EAAE,SAAS,+BAA+B;AAAA,IAClE,CAAC;AAAA,EACH,CAAC,EACA,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC;;;ACpBD,SAAS,KAAAA,UAAS;AAEX,IAAM,cAAcA,GACxB,OAAO;AAAA,EACN,WAAWA,GACR,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,UAAUA,GACP,OAAO,EACP,QAAQ,IAAI,EACZ,SAAS,mDAAmD;AAAA,EAC/D,cAAcA,GACX,KAAK,CAAC,YAAY,YAAY,YAAY,SAAS,CAAC,EACpD,QAAQ,UAAU,EAClB,SAAS,mCAAmC;AAAA,EAC/C,YAAYA,GACT,QAAQ,EACR,QAAQ,KAAK,EACb,SAAS,yCAAyC;AAAA,EACrD,QAAQA,GACL,OAAOA,GAAE,OAAO,GAAGA,GAAE,OAAO,CAAC,EAC7B,SAAS,EACT,SAAS,4BAA4B;AAAA,EACxC,YAAYA,GACT,OAAO,EACP,SAAS,EACT,SAAS,wDAAwD;AAAA;AAAA;AAGtE,CAAC,EACA;AAAA,EACC;AACF;;;AF5BK,IAAM,WAAW,YAAY,cAAc;AAC3C,IAAM,QAAQ,YAAY,WAAW;;;AGR5C;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,cAAgC;AAAA,EAC3C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,CAAC,CAAC,OAAO,aAAa,6BAA6B,CAAC;AAC3D;AAGO,IAAM,gBAAkC;AAAA,EAC7C,OAAO;AAAA,EACP,aACE;AAAA,EACF,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,UAAU,EAAE,QAAQ,aAAa,QAAQ,SAAS;AAAA,EACpD;AAAA,EACA,KAAK,CAAC,CAAC,OAAO,oBAAoB,aAAa,CAAC;AAClD;;;ADlBO,IAAM,cAA4B;AAAA,EACvC,UAAU;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAGO,IAAM,iBAA+B;AAAA,EAC1C,UAAU;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AACF;","names":["z"]}
package/dist/index.d.mts CHANGED
@@ -1,13 +1,13 @@
1
- import { Store } from '@walkeros/core';
1
+ import { Store, SetupFn } from '@walkeros/core';
2
2
 
3
3
  interface GcsStoreSettings {
4
4
  /** GCS bucket name */
5
5
  bucket: string;
6
6
  /**
7
7
  * Key prefix prepended to all store keys.
8
- * No leading/trailing slash needed normalized automatically.
8
+ * No leading/trailing slash needed, normalized automatically.
9
9
  *
10
- * @example "public/assets" get("walker.js") looks up "public/assets/walker.js"
10
+ * @example "public/assets" -> get("walker.js") looks up "public/assets/walker.js"
11
11
  */
12
12
  prefix?: string;
13
13
  /**
@@ -23,7 +23,52 @@ interface ServiceAccountCredentials {
23
23
  /** RSA private key in PEM format (from SA JSON `private_key` field) */
24
24
  private_key: string;
25
25
  }
26
+ /**
27
+ * Provisioning options for `walkeros setup store.<id>`.
28
+ * Triggered only by the explicit CLI command. Idempotent. Never alters existing resources.
29
+ *
30
+ * `bucket` lives in `Settings`, NOT here (no duplication).
31
+ * `projectId` lives here because the package's Settings does not currently carry one.
32
+ */
33
+ interface Setup {
34
+ /** GCS project to create the bucket in. Resolution order: this, settings.credentials.project_id, GOOGLE_CLOUD_PROJECT env, then throw. */
35
+ projectId?: string;
36
+ /** Geographic location. Default: 'EU' (multi-region). */
37
+ location?: string;
38
+ /** Storage class. Default: 'STANDARD'. */
39
+ storageClass?: 'STANDARD' | 'NEARLINE' | 'COLDLINE' | 'ARCHIVE';
40
+ /** Enable bucket versioning at create time. Default: false. */
41
+ versioning?: boolean;
42
+ /** Lifecycle rules to apply at create. Optional. Drift detection NOT included for lifecycle (complex object). */
43
+ lifecycle?: {
44
+ rule: unknown[];
45
+ };
46
+ /** CMEK key for at-rest encryption. Optional. */
47
+ kmsKeyName?: string;
48
+ /** Labels for cost allocation. Optional. */
49
+ labels?: Record<string, string>;
50
+ }
51
+ type Types = Store.Types<GcsStoreSettings, Store.BaseEnv, GcsStoreSettings, Setup>;
52
+
53
+ declare const storeGcsInit: Store.Init<Types>;
26
54
 
27
- declare const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>>;
55
+ /**
56
+ * Default setup options. Optional fields (lifecycle, kmsKeyName, labels,
57
+ * projectId) are intentionally omitted so the resolved options carry
58
+ * `undefined` for them and the body builder can skip writing those
59
+ * properties to the create request payload.
60
+ */
61
+ declare const DEFAULT_SETUP: Setup;
62
+ /**
63
+ * Public alias kept for callers that imported the prior shape.
64
+ * Equivalent to the framework's `Store.Config<Types>`.
65
+ */
66
+ type GcsStoreConfig = Store.Config<Types>;
67
+ /**
68
+ * Provision a GCS bucket described in the flow config.
69
+ * Idempotent: 409 means the bucket exists, drift is logged but never patched.
70
+ * Never auto-mutates an existing bucket.
71
+ */
72
+ declare const setup: SetupFn<GcsStoreConfig, Store.BaseEnv>;
28
73
 
29
- export { type GcsStoreSettings, type ServiceAccountCredentials, storeGcsInit as default, storeGcsInit };
74
+ export { DEFAULT_SETUP, type GcsStoreSettings, type ServiceAccountCredentials, type Setup, type Types, storeGcsInit as default, setup, storeGcsInit };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- import { Store } from '@walkeros/core';
1
+ import { Store, SetupFn } from '@walkeros/core';
2
2
 
3
3
  interface GcsStoreSettings {
4
4
  /** GCS bucket name */
5
5
  bucket: string;
6
6
  /**
7
7
  * Key prefix prepended to all store keys.
8
- * No leading/trailing slash needed normalized automatically.
8
+ * No leading/trailing slash needed, normalized automatically.
9
9
  *
10
- * @example "public/assets" get("walker.js") looks up "public/assets/walker.js"
10
+ * @example "public/assets" -> get("walker.js") looks up "public/assets/walker.js"
11
11
  */
12
12
  prefix?: string;
13
13
  /**
@@ -23,7 +23,52 @@ interface ServiceAccountCredentials {
23
23
  /** RSA private key in PEM format (from SA JSON `private_key` field) */
24
24
  private_key: string;
25
25
  }
26
+ /**
27
+ * Provisioning options for `walkeros setup store.<id>`.
28
+ * Triggered only by the explicit CLI command. Idempotent. Never alters existing resources.
29
+ *
30
+ * `bucket` lives in `Settings`, NOT here (no duplication).
31
+ * `projectId` lives here because the package's Settings does not currently carry one.
32
+ */
33
+ interface Setup {
34
+ /** GCS project to create the bucket in. Resolution order: this, settings.credentials.project_id, GOOGLE_CLOUD_PROJECT env, then throw. */
35
+ projectId?: string;
36
+ /** Geographic location. Default: 'EU' (multi-region). */
37
+ location?: string;
38
+ /** Storage class. Default: 'STANDARD'. */
39
+ storageClass?: 'STANDARD' | 'NEARLINE' | 'COLDLINE' | 'ARCHIVE';
40
+ /** Enable bucket versioning at create time. Default: false. */
41
+ versioning?: boolean;
42
+ /** Lifecycle rules to apply at create. Optional. Drift detection NOT included for lifecycle (complex object). */
43
+ lifecycle?: {
44
+ rule: unknown[];
45
+ };
46
+ /** CMEK key for at-rest encryption. Optional. */
47
+ kmsKeyName?: string;
48
+ /** Labels for cost allocation. Optional. */
49
+ labels?: Record<string, string>;
50
+ }
51
+ type Types = Store.Types<GcsStoreSettings, Store.BaseEnv, GcsStoreSettings, Setup>;
52
+
53
+ declare const storeGcsInit: Store.Init<Types>;
26
54
 
27
- declare const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>>;
55
+ /**
56
+ * Default setup options. Optional fields (lifecycle, kmsKeyName, labels,
57
+ * projectId) are intentionally omitted so the resolved options carry
58
+ * `undefined` for them and the body builder can skip writing those
59
+ * properties to the create request payload.
60
+ */
61
+ declare const DEFAULT_SETUP: Setup;
62
+ /**
63
+ * Public alias kept for callers that imported the prior shape.
64
+ * Equivalent to the framework's `Store.Config<Types>`.
65
+ */
66
+ type GcsStoreConfig = Store.Config<Types>;
67
+ /**
68
+ * Provision a GCS bucket described in the flow config.
69
+ * Idempotent: 409 means the bucket exists, drift is logged but never patched.
70
+ * Never auto-mutates an existing bucket.
71
+ */
72
+ declare const setup: SetupFn<GcsStoreConfig, Store.BaseEnv>;
28
73
 
29
- export { type GcsStoreSettings, type ServiceAccountCredentials, storeGcsInit as default, storeGcsInit };
74
+ export { DEFAULT_SETUP, type GcsStoreSettings, type ServiceAccountCredentials, type Setup, type Types, storeGcsInit as default, setup, storeGcsInit };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,a={};((e,r)=>{for(var o in r)t(e,o,{get:r[o],enumerable:!0})})(a,{default:()=>g,storeGcsInit:()=>g}),module.exports=(e=a,((e,a,i,s)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let c of o(a))n.call(e,c)||c===i||t(e,c,{get:()=>a[c],enumerable:!(s=r(a,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var i=require("crypto"),s="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",c="https://oauth2.googleapis.com/token",u="https://www.googleapis.com/auth/devstorage.read_write",f=6e4;function p(e){let t;return async function(){return t&&Date.now()<t.expiresAt||(t=e?await async function(e){const t=Math.floor(Date.now()/1e3),r=function(e,t){const r=l(JSON.stringify({alg:"RS256",typ:"JWT"})),o=l(JSON.stringify(e)),n=(0,i.createSign)("RSA-SHA256");return n.update(`${r}.${o}`),`${r}.${o}.${n.sign(t,"base64url")}`}({iss:e.client_email,scope:u,aud:c,iat:t,exp:t+3600},e.private_key),o=await fetch(c,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${r}`});if(!o.ok)throw new Error(`Token exchange error: ${o.status}`);const n=await o.json();return{token:n.access_token,expiresAt:Date.now()+1e3*n.expires_in-f}}(e):await async function(){const e=await fetch(s,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Metadata server error: ${e.status}`);const t=await e.json();return{token:t.access_token,expiresAt:Date.now()+1e3*t.expires_in-f}}()),t.token}}function l(e){return("string"==typeof e?Buffer.from(e):e).toString("base64url")}var d="https://storage.googleapis.com";var g=e=>{const t=e.config.settings,r=function(e){if(!e)return"";const t=e.replace(/^\/+|\/+$/g,"");return t?t+"/":""}(t.prefix),o=p(function(e){if(e)return"string"==typeof e?JSON.parse(e):e}(t.credentials)),n=encodeURIComponent(t.bucket);function a(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})}return{type:"gcs",config:e.config,async get(e){const t=a(e);if(t)try{const e=await o(),r=`${d}/download/storage/v1/b/${n}/o/${encodeURIComponent(t)}?alt=media`,a=await fetch(r,{headers:{Authorization:`Bearer ${e}`}});if(!a.ok)return;return Buffer.from(await a.arrayBuffer())}catch{return}},async set(e,t){const r=a(e);if(!r)return;const i=await o(),s=`${d}/upload/storage/v1/b/${n}/o?uploadType=media&name=${encodeURIComponent(r)}`;await fetch(s,{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/octet-stream"},body:new Uint8Array(t)})},async delete(e){const t=a(e);if(t)try{const e=await o(),r=`${d}/storage/v1/b/${n}/o/${encodeURIComponent(t)}`;await fetch(r,{method:"DELETE",headers:{Authorization:`Bearer ${e}`}})}catch{}}}};//# sourceMappingURL=index.js.map
1
+ "use strict";var e,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,a={};((e,n)=>{for(var r in n)t(e,r,{get:n[r],enumerable:!0})})(a,{DEFAULT_SETUP:()=>w,default:()=>A,setup:()=>h,storeGcsInit:()=>A}),module.exports=(e=a,((e,a,i,s)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let c of r(a))o.call(e,c)||c===i||t(e,c,{get:()=>a[c],enumerable:!(s=n(a,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var i=require("crypto"),s="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",c="https://oauth2.googleapis.com/token",u="https://www.googleapis.com/auth/devstorage.read_write",f=6e4;function l(e){let t;return async function(){return t&&Date.now()<t.expiresAt||(t=e?await async function(e){const t=Math.floor(Date.now()/1e3),n=function(e,t){const n=d(JSON.stringify({alg:"RS256",typ:"JWT"})),r=d(JSON.stringify(e)),o=(0,i.createSign)("RSA-SHA256");return o.update(`${n}.${r}`),`${n}.${r}.${o.sign(t,"base64url")}`}({iss:e.client_email,scope:u,aud:c,iat:t,exp:t+3600},e.private_key),r=await fetch(c,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${n}`});if(!r.ok)throw new Error(`Token exchange error: ${r.status}`);const o=await r.json();return{token:o.access_token,expiresAt:Date.now()+1e3*o.expires_in-f}}(e):await async function(){const e=await fetch(s,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Metadata server error: ${e.status}`);const t=await e.json();return{token:t.access_token,expiresAt:Date.now()+1e3*t.expires_in-f}}()),t.token}}function d(e){return("string"==typeof e?Buffer.from(e):e).toString("base64url")}var p=require("@walkeros/core");function g(e,t){if(t.projectId)return t.projectId;const n=function(e){if(!e)return;if("string"==typeof e){let t;try{t=JSON.parse(e)}catch{return}return y(t,"project_id")}return y(e,"project_id")}(e.credentials);if(n)return n;const r=process.env.GOOGLE_CLOUD_PROJECT;if(r)return r;throw new Error("setup: projectId is required. Set setup.projectId, provide a service account with project_id, or export GOOGLE_CLOUD_PROJECT.")}function y(e,t){if(!function(e){return"object"==typeof e&&null!==e}(e))return;const n=e[t];return"string"==typeof n?n:void 0}var b="https://storage.googleapis.com",w={location:"EU",storageClass:"STANDARD",versioning:!1},h=async e=>{const{config:t,logger:n}=e,r=(0,p.resolveSetup)(t.setup,w);if(!r)return void n.debug("setup: skipped (config.setup is false or unset)");const o=t.settings;if(!o||!o.bucket)throw new Error("setup: settings.bucket is required");const a=g(o,r),i=l(function(e){if(!e)return;if("string"==typeof e){const n=JSON.parse(e);return v(t=n)&&"string"==typeof t.client_email&&"string"==typeof t.private_key?n:void 0}var t;return e}(o.credentials)),s=await i(),c=`${b}/storage/v1/b?project=${encodeURIComponent(a)}`,u=function(e,t){const n={name:e,location:t.location,storageClass:t.storageClass,versioning:{enabled:t.versioning??!1},iamConfiguration:{uniformBucketLevelAccess:{enabled:!0},publicAccessPrevention:"enforced"}};t.labels&&(n.labels=t.labels);t.kmsKeyName&&(n.encryption={defaultKmsKeyName:t.kmsKeyName});t.lifecycle&&(n.lifecycle=t.lifecycle);return n}(o.bucket,r),f=await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify(u)});if(200===f.status||201===f.status)return n.info("setup: bucket created",{bucket:o.bucket,projectId:a,location:r.location}),{bucketCreated:!0};if(409!==f.status){const e=await async function(e){try{return await e.text()}catch{return""}}(f);throw new Error(`setup: bucket create failed (${f.status}): ${e}`)}return n.debug("setup: bucket already exists",{bucket:o.bucket}),await async function(e,t,n,r){const o=`${b}/storage/v1/b/${encodeURIComponent(e)}`;let a;try{const e=await fetch(o,{headers:{Authorization:`Bearer ${t}`}});if(!e.ok)return void r.debug("setup: drift check failed (non-fatal)",{status:e.status});a=function(e){if(!v(e))return{};const t={};"string"==typeof e.location&&(t.location=e.location);"string"==typeof e.storageClass&&(t.storageClass=e.storageClass);if(v(e.versioning)){const n=e.versioning.enabled;"boolean"==typeof n&&(t.versioning={enabled:n})}if(v(e.iamConfiguration)){const n={},r=e.iamConfiguration.uniformBucketLevelAccess;v(r)&&"boolean"==typeof r.enabled&&(n.uniformBucketLevelAccess={enabled:r.enabled});const o=e.iamConfiguration.publicAccessPrevention;"string"==typeof o&&(n.publicAccessPrevention=o),t.iamConfiguration=n}if(v(e.labels)){const n={};for(const[t,r]of Object.entries(e.labels))"string"==typeof r&&(n[t]=r);t.labels=n}return t}(await e.json())}catch(e){return void r.debug("setup: drift check failed (non-fatal)",{error:e instanceof Error?e.message:String(e)})}"string"==typeof a.location&&void 0!==n.location&&a.location!==n.location&&r.warn("setup.drift",{field:"location",declared:n.location,actual:a.location});"string"==typeof a.storageClass&&void 0!==n.storageClass&&a.storageClass!==n.storageClass&&r.warn("setup.drift",{field:"storageClass",declared:n.storageClass,actual:a.storageClass});const i=n.versioning??!1,s=a.versioning?.enabled;"boolean"==typeof s&&s!==i&&r.warn("setup.drift",{field:"versioning",declared:i,actual:s});const c=a.iamConfiguration;if(c){const e=c.uniformBucketLevelAccess?.enabled;"boolean"==typeof e&&!0!==e&&r.warn("setup.drift",{field:"uniformBucketLevelAccess",declared:!0,actual:e});const t=c.publicAccessPrevention;"string"==typeof t&&"enforced"!==t&&r.warn("setup.drift",{field:"publicAccessPrevention",declared:"enforced",actual:t})}if(n.labels){const e=a.labels??{};(function(e,t){const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(const r of n)if(e[r]!==t[r])return!1;return!0})(n.labels,e)||r.warn("setup.drift",{field:"labels",declared:n.labels,actual:e})}}(o.bucket,s,r,n),{bucketCreated:!1}};function v(e){return"object"==typeof e&&null!==e}var k="https://storage.googleapis.com",m=new Map;function C(e){const t=new ArrayBuffer(e.byteLength),n=new Uint8Array(t);return n.set(e),n}async function $(e,t,n,r,o){const a=m.get(e);if(void 0!==a)return void await a;const i=(async()=>{let a;try{const t=await r(),n=`${k}/storage/v1/b/${encodeURIComponent(e)}`;a=await fetch(n,{method:"HEAD",headers:{Authorization:`Bearer ${t}`}})}catch(t){return o.debug("ensureBucketExists check failed (non-fatal)",{bucket:e,error:t instanceof Error?t.message:String(t)}),!0}if(404===a.status){const r=function(e){try{return g(e,{})}catch{return"unknown"}}(n);throw new Error(`GCS bucket not found: ${e} in project ${r}. Run "walkeros setup store.${t}" to create it.`)}return a.ok||o.debug("ensureBucketExists check failed (non-fatal)",{bucket:e,status:a.status}),!0})();m.set(e,i);try{await i}catch(t){throw m.delete(e),t}}var A=e=>{!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("storeGcsInit: settings.bucket is required (non-empty string)")}(e.config.settings);const t=e.config.settings,n=function(e){if(!e)return"";const t=e.replace(/^\/+|\/+$/g,"");return t?t+"/":""}(t.prefix),r=l(function(e){if(e){if("string"==typeof e){const n=JSON.parse(e);return function(e){return"object"==typeof e&&null!==e}(t=n)&&"string"==typeof t.client_email&&"string"==typeof t.private_key?n:void 0}var t;return e}}(t.credentials)),o=t.bucket,a=encodeURIComponent(o),i=e.id,{logger:s}=e;function c(e){if(function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(e))return n+e;s.warn("Invalid key rejected",{key:e})}return{type:"gcs",config:{settings:t,env:e.config.env,id:e.config.id,logger:e.config.logger},setup:h,async get(e){const n=c(e);if(n){await $(o,i,t,r,s);try{const e=await r(),t=`${k}/download/storage/v1/b/${a}/o/${encodeURIComponent(n)}?alt=media`,o=await fetch(t,{headers:{Authorization:`Bearer ${e}`}});if(!o.ok)return;return Buffer.from(await o.arrayBuffer())}catch{return}}},async set(e,n){const u=c(e);if(!u)return;if(!function(e){return Buffer.isBuffer(e)}(n))throw new Error("storeGcsInit.set: value must be a Buffer; got "+typeof n);await $(o,i,t,r,s);const f=await r(),l=`${k}/upload/storage/v1/b/${a}/o?uploadType=media&name=${encodeURIComponent(u)}`;await fetch(l,{method:"POST",headers:{Authorization:`Bearer ${f}`,"Content-Type":"application/octet-stream"},body:C(n)})},async delete(e){const n=c(e);if(n){await $(o,i,t,r,s);try{const e=await r(),t=`${k}/storage/v1/b/${a}/o/${encodeURIComponent(n)}`;await fetch(t,{method:"DELETE",headers:{Authorization:`Bearer ${e}`}})}catch{}}}}};//# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/auth.ts","../src/store.ts"],"sourcesContent":["export { storeGcsInit } from './store';\nexport type { GcsStoreSettings, ServiceAccountCredentials } from './types';\nexport { storeGcsInit as default } from './store';\n","import { createSign } from 'node:crypto';\nimport type { ServiceAccountCredentials } from './types';\n\nexport type TokenProvider = () => Promise<string>;\n\nconst METADATA_URL =\n 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token';\nconst OAUTH_URL = 'https://oauth2.googleapis.com/token';\nconst SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n): TokenProvider {\n let cached: CachedToken | undefined;\n\n return async function getToken(): Promise<string> {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n\n cached = credentials\n ? await fetchServiceAccountToken(credentials)\n : await fetchMetadataToken();\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(): Promise<CachedToken> {\n const res = await fetch(METADATA_URL, {\n headers: { 'Metadata-Flavor': 'Google' },\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`Metadata server error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nasync function fetchServiceAccountToken(\n creds: ServiceAccountCredentials,\n): Promise<CachedToken> {\n const now = Math.floor(Date.now() / 1000);\n const jwt = signJwt(\n {\n iss: creds.client_email,\n scope: SCOPE,\n aud: OAUTH_URL,\n iat: now,\n exp: now + 3600,\n },\n creds.private_key,\n );\n\n const res = await fetch(OAUTH_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`,\n });\n if (!res.ok) throw new Error(`Token exchange error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nfunction base64url(input: string | Buffer): string {\n const buf = typeof input === 'string' ? Buffer.from(input) : input;\n return buf.toString('base64url');\n}\n\nfunction signJwt(payload: object, privateKey: string): string {\n const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));\n const body = base64url(JSON.stringify(payload));\n const sign = createSign('RSA-SHA256');\n sign.update(`${header}.${body}`);\n return `${header}.${body}.${sign.sign(privateKey, 'base64url')}`;\n}\n","import type { Store } from '@walkeros/core';\nimport type { GcsStoreSettings, ServiceAccountCredentials } from './types';\nimport { createTokenProvider } from './auth';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') return JSON.parse(credentials);\n return credentials;\n}\n\nexport const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>> = (\n context,\n) => {\n const settings = context.config.settings as GcsStoreSettings;\n const prefix = normalizePrefix(settings.prefix);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const bucket = encodeURIComponent(settings.bucket);\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 return {\n type: 'gcs',\n config: context.config as Store.Config<Store.Types<GcsStoreSettings>>,\n\n async get(key: string): Promise<Buffer | undefined> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return undefined;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/download/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}?alt=media`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n return Buffer.from(await res.arrayBuffer());\n } catch {\n return undefined;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n const token = await getToken();\n const url = `${GCS_BASE}/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: new Uint8Array(value as Buffer),\n });\n },\n\n async delete(key: string): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch {\n /* GCS delete is idempotent */\n }\n },\n };\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/auth.ts","../src/setup.ts","../src/setup-helpers.ts","../src/store.ts"],"sourcesContent":["export { storeGcsInit } from './store';\nexport { setup, DEFAULT_SETUP } from './setup';\nexport type {\n GcsStoreSettings,\n ServiceAccountCredentials,\n Setup,\n Types,\n} from './types';\nexport { storeGcsInit as default } from './store';\n","import { createSign } from 'node:crypto';\nimport type { ServiceAccountCredentials } from './types';\n\nexport type TokenProvider = () => Promise<string>;\n\nconst METADATA_URL =\n 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token';\nconst OAUTH_URL = 'https://oauth2.googleapis.com/token';\nconst SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n): TokenProvider {\n let cached: CachedToken | undefined;\n\n return async function getToken(): Promise<string> {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n\n cached = credentials\n ? await fetchServiceAccountToken(credentials)\n : await fetchMetadataToken();\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(): Promise<CachedToken> {\n const res = await fetch(METADATA_URL, {\n headers: { 'Metadata-Flavor': 'Google' },\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`Metadata server error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nasync function fetchServiceAccountToken(\n creds: ServiceAccountCredentials,\n): Promise<CachedToken> {\n const now = Math.floor(Date.now() / 1000);\n const jwt = signJwt(\n {\n iss: creds.client_email,\n scope: SCOPE,\n aud: OAUTH_URL,\n iat: now,\n exp: now + 3600,\n },\n creds.private_key,\n );\n\n const res = await fetch(OAUTH_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`,\n });\n if (!res.ok) throw new Error(`Token exchange error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nfunction base64url(input: string | Buffer): string {\n const buf = typeof input === 'string' ? Buffer.from(input) : input;\n return buf.toString('base64url');\n}\n\nfunction signJwt(payload: object, privateKey: string): string {\n const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));\n const body = base64url(JSON.stringify(payload));\n const sign = createSign('RSA-SHA256');\n sign.update(`${header}.${body}`);\n return `${header}.${body}.${sign.sign(privateKey, 'base64url')}`;\n}\n","import type { LifecycleContext, Logger, Store, SetupFn } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type {\n GcsStoreSettings,\n ServiceAccountCredentials,\n Setup,\n Types,\n} from './types';\nimport { createTokenProvider } from './auth';\nimport { resolveProjectId } from './setup-helpers';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\n/**\n * Default setup options. Optional fields (lifecycle, kmsKeyName, labels,\n * projectId) are intentionally omitted so the resolved options carry\n * `undefined` for them and the body builder can skip writing those\n * properties to the create request payload.\n */\nexport const DEFAULT_SETUP: Setup = {\n location: 'EU',\n storageClass: 'STANDARD',\n versioning: false,\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 GcsStoreConfig = Store.Config<Types>;\n\n/**\n * Provision a GCS bucket described in the flow config.\n * Idempotent: 409 means the bucket exists, drift is logged but never patched.\n * Never auto-mutates an existing bucket.\n */\nexport const setup: SetupFn<GcsStoreConfig, Store.BaseEnv> = async (\n context: LifecycleContext<GcsStoreConfig, Store.BaseEnv>,\n) => {\n const { config, logger } = context;\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 const settings = config.settings;\n if (!settings || !settings.bucket) {\n throw new Error('setup: settings.bucket is required');\n }\n\n const projectId = resolveProjectId(settings, options);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const token = await getToken();\n\n const createUrl = `${GCS_BASE}/storage/v1/b?project=${encodeURIComponent(projectId)}`;\n const body = buildCreateBody(settings.bucket, options);\n\n const createRes = await fetch(createUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (createRes.status === 200 || createRes.status === 201) {\n logger.info('setup: bucket created', {\n bucket: settings.bucket,\n projectId,\n location: options.location,\n });\n return { bucketCreated: true };\n }\n\n if (createRes.status !== 409) {\n const text = await safeText(createRes);\n throw new Error(\n `setup: bucket create failed (${createRes.status}): ${text}`,\n );\n }\n\n logger.debug('setup: bucket already exists', { bucket: settings.bucket });\n await detectDrift(settings.bucket, token, options, logger);\n return { bucketCreated: false };\n};\n\ninterface CreateBody {\n name: string;\n location?: string;\n storageClass?: string;\n versioning: { enabled: boolean };\n iamConfiguration: {\n uniformBucketLevelAccess: { enabled: true };\n publicAccessPrevention: 'enforced';\n };\n labels?: Record<string, string>;\n encryption?: { defaultKmsKeyName: string };\n lifecycle?: { rule: unknown[] };\n}\n\nfunction buildCreateBody(bucket: string, options: Setup): CreateBody {\n const body: CreateBody = {\n name: bucket,\n location: options.location,\n storageClass: options.storageClass,\n versioning: { enabled: options.versioning ?? false },\n iamConfiguration: {\n uniformBucketLevelAccess: { enabled: true },\n publicAccessPrevention: 'enforced',\n },\n };\n if (options.labels) body.labels = options.labels;\n if (options.kmsKeyName) {\n body.encryption = { defaultKmsKeyName: options.kmsKeyName };\n }\n if (options.lifecycle) body.lifecycle = options.lifecycle;\n return body;\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n const parsed: unknown = JSON.parse(credentials);\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n return credentials;\n}\n\nfunction isServiceAccountShape(\n value: unknown,\n): value is ServiceAccountCredentials {\n if (!isRecord(value)) return false;\n return (\n typeof value.client_email === 'string' &&\n typeof value.private_key === 'string'\n );\n}\n\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text();\n } catch {\n return '';\n }\n}\n\ninterface BucketMetadata {\n location?: string;\n storageClass?: string;\n versioning?: { enabled?: boolean };\n iamConfiguration?: {\n uniformBucketLevelAccess?: { enabled?: boolean };\n publicAccessPrevention?: string;\n };\n labels?: Record<string, string>;\n}\n\nasync function detectDrift(\n bucket: string,\n token: string,\n options: Setup,\n logger: Logger.Instance,\n): Promise<void> {\n const url = `${GCS_BASE}/storage/v1/b/${encodeURIComponent(bucket)}`;\n let metadata: BucketMetadata;\n try {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) {\n logger.debug('setup: drift check failed (non-fatal)', {\n status: res.status,\n });\n return;\n }\n const parsed: unknown = await res.json();\n metadata = readBucketMetadata(parsed);\n } catch (err) {\n logger.debug('setup: drift check failed (non-fatal)', {\n error: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n\n if (\n typeof metadata.location === 'string' &&\n options.location !== undefined &&\n metadata.location !== options.location\n ) {\n logger.warn('setup.drift', {\n field: 'location',\n declared: options.location,\n actual: metadata.location,\n });\n }\n\n if (\n typeof metadata.storageClass === 'string' &&\n options.storageClass !== undefined &&\n metadata.storageClass !== options.storageClass\n ) {\n logger.warn('setup.drift', {\n field: 'storageClass',\n declared: options.storageClass,\n actual: metadata.storageClass,\n });\n }\n\n const declaredVersioning = options.versioning ?? false;\n const actualVersioning = metadata.versioning?.enabled;\n if (\n typeof actualVersioning === 'boolean' &&\n actualVersioning !== declaredVersioning\n ) {\n logger.warn('setup.drift', {\n field: 'versioning',\n declared: declaredVersioning,\n actual: actualVersioning,\n });\n }\n\n const iam = metadata.iamConfiguration;\n if (iam) {\n const actualUniform = iam.uniformBucketLevelAccess?.enabled;\n if (typeof actualUniform === 'boolean' && actualUniform !== true) {\n logger.warn('setup.drift', {\n field: 'uniformBucketLevelAccess',\n declared: true,\n actual: actualUniform,\n });\n }\n const actualPap = iam.publicAccessPrevention;\n if (typeof actualPap === 'string' && actualPap !== 'enforced') {\n logger.warn('setup.drift', {\n field: 'publicAccessPrevention',\n declared: 'enforced',\n actual: actualPap,\n });\n }\n }\n\n if (options.labels) {\n const actualLabels = metadata.labels ?? {};\n if (!labelsEqual(options.labels, actualLabels)) {\n logger.warn('setup.drift', {\n field: 'labels',\n declared: options.labels,\n actual: actualLabels,\n });\n }\n }\n}\n\nfunction labelsEqual(\n a: Record<string, string>,\n b: Record<string, string>,\n): boolean {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (a[key] !== b[key]) return false;\n }\n return true;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readBucketMetadata(value: unknown): BucketMetadata {\n if (!isRecord(value)) return {};\n const out: BucketMetadata = {};\n if (typeof value.location === 'string') out.location = value.location;\n if (typeof value.storageClass === 'string') {\n out.storageClass = value.storageClass;\n }\n if (isRecord(value.versioning)) {\n const enabled = value.versioning.enabled;\n if (typeof enabled === 'boolean') {\n out.versioning = { enabled };\n }\n }\n if (isRecord(value.iamConfiguration)) {\n const iam: BucketMetadata['iamConfiguration'] = {};\n const uniform = value.iamConfiguration.uniformBucketLevelAccess;\n if (isRecord(uniform) && typeof uniform.enabled === 'boolean') {\n iam.uniformBucketLevelAccess = { enabled: uniform.enabled };\n }\n const pap = value.iamConfiguration.publicAccessPrevention;\n if (typeof pap === 'string') iam.publicAccessPrevention = pap;\n out.iamConfiguration = iam;\n }\n if (isRecord(value.labels)) {\n const labels: Record<string, string> = {};\n for (const [k, v] of Object.entries(value.labels)) {\n if (typeof v === 'string') labels[k] = v;\n }\n out.labels = labels;\n }\n return out;\n}\n","import type { GcsStoreSettings, ServiceAccountCredentials } from './types';\n\n/**\n * Resolves the GCP project ID used to create a bucket in the GCS JSON API.\n *\n * Resolution order:\n * 1. `setup.projectId` (explicit override).\n * 2. `settings.credentials.project_id` (when SA JSON is parsed and contains it).\n * 3. `process.env.GOOGLE_CLOUD_PROJECT` (Cloud Run / GKE convention).\n * 4. Throw with an actionable error message.\n *\n * `process.env.GOOGLE_CLOUD_PROJECT` is read at call time, not at module load,\n * so changes after import are honored.\n */\nexport function resolveProjectId(\n settings: GcsStoreSettings,\n setup: { projectId?: string },\n): string {\n if (setup.projectId) return setup.projectId;\n\n const fromCreds = extractProjectIdFromCredentials(settings.credentials);\n if (fromCreds) return fromCreds;\n\n const fromEnv = process.env.GOOGLE_CLOUD_PROJECT;\n if (fromEnv) return fromEnv;\n\n throw new Error(\n 'setup: projectId is required. Set setup.projectId, provide a service account with project_id, or export GOOGLE_CLOUD_PROJECT.',\n );\n}\n\n/**\n * Typed predicate: returns the `project_id` field from credentials when\n * present and a string. Avoids type casts by validating the runtime shape.\n *\n * - `string` credentials are parsed as JSON; non-JSON or non-object payloads\n * return undefined.\n * - object credentials are inspected directly. Real SA JSON contains\n * `project_id` even though `ServiceAccountCredentials` declares only the\n * fields the runtime needs (client_email, private_key).\n */\nfunction extractProjectIdFromCredentials(\n credentials: GcsStoreSettings['credentials'],\n): string | undefined {\n if (!credentials) return undefined;\n\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n return readStringField(parsed, 'project_id');\n }\n\n return readStringField(credentials, 'project_id');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readStringField(\n value: ServiceAccountCredentials | unknown,\n field: string,\n): string | undefined {\n if (!isRecord(value)) return undefined;\n const fieldValue = value[field];\n return typeof fieldValue === 'string' ? fieldValue : undefined;\n}\n","import type { Logger, Store } from '@walkeros/core';\nimport type {\n GcsStoreSettings,\n ServiceAccountCredentials,\n Types,\n} from './types';\nimport { createTokenProvider } from './auth';\nimport { setup as gcsSetup } from './setup';\nimport { resolveProjectId } from './setup-helpers';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\n/**\n * Module-level cache for the bucket existence pre-check. Keyed by bucket\n * name (bucket names are globally unique in GCS). One HEAD per process per\n * bucket. The cache stores a Promise so concurrent first-operations share a\n * single in-flight request.\n *\n * Exported helpers below allow tests to reset/seed the cache.\n */\nconst bucketExistsCache: Map<string, Promise<boolean>> = new Map();\n\n/** @internal Test-only: clear the existence cache. */\nexport function __resetBucketExistenceCache(): void {\n bucketExistsCache.clear();\n}\n\n/** @internal Test-only: seed the existence cache for a bucket. */\nexport function __seedBucketExists(bucket: string): void {\n bucketExistsCache.set(bucket, Promise.resolve(true));\n}\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isServiceAccountShape(\n value: unknown,\n): value is ServiceAccountCredentials {\n if (!isRecord(value)) return false;\n return (\n typeof value.client_email === 'string' &&\n typeof value.private_key === 'string'\n );\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n const parsed: unknown = JSON.parse(credentials);\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n return credentials;\n}\n\nfunction isBufferLike(value: unknown): value is Buffer {\n return Buffer.isBuffer(value);\n}\n\n/**\n * Wrap a Buffer in a fresh Uint8Array backed by a plain ArrayBuffer so it\n * satisfies fetch's BodyInit contract. Avoids type casts: Node's Buffer\n * type is `Buffer<ArrayBufferLike>`, while the DOM's BodyInit (used by\n * fetch's RequestInit) expects `ArrayBufferView<ArrayBuffer>`.\n */\nfunction bufferToBody(buf: Buffer): Uint8Array<ArrayBuffer> {\n const out = new ArrayBuffer(buf.byteLength);\n const view = new Uint8Array(out);\n view.set(buf);\n return view;\n}\n\nfunction assertGcsSettings(\n settings: Partial<GcsStoreSettings> | undefined,\n): asserts settings is GcsStoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error(\n 'storeGcsInit: settings.bucket is required (non-empty string)',\n );\n }\n}\n\n/**\n * Resolve a project ID for the runtime hard-fail message. Uses the same\n * resolution order as setup but never throws: returns \"unknown\" when no\n * source is available so the error message is still actionable.\n */\nfunction resolveProjectIdForMessage(settings: GcsStoreSettings): string {\n try {\n return resolveProjectId(settings, {});\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Verify the bucket exists. Issues `HEAD /storage/v1/b/<bucket>` once per\n * process per bucket. On 404, throws an actionable error pointing at the\n * setup command. On any other failure (non-2xx response or transport\n * error), treats the result as \"exists\" to avoid blocking legitimate\n * operations on a transient blip.\n */\nasync function ensureBucketExists(\n bucket: string,\n id: string,\n settings: GcsStoreSettings,\n getToken: () => Promise<string>,\n logger: Logger.Instance,\n): Promise<void> {\n const existing = bucketExistsCache.get(bucket);\n if (existing !== undefined) {\n await existing;\n return;\n }\n\n const promise = (async (): Promise<boolean> => {\n let res: Response;\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${encodeURIComponent(bucket)}`;\n res = await fetch(url, {\n method: 'HEAD',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch (err) {\n logger.debug('ensureBucketExists check failed (non-fatal)', {\n bucket,\n error: err instanceof Error ? err.message : String(err),\n });\n return true;\n }\n\n if (res.status === 404) {\n const projectId = resolveProjectIdForMessage(settings);\n throw new Error(\n `GCS bucket not found: ${bucket} in project ${projectId}. Run \"walkeros setup store.${id}\" to create it.`,\n );\n }\n if (!res.ok) {\n logger.debug('ensureBucketExists check failed (non-fatal)', {\n bucket,\n status: res.status,\n });\n }\n return true;\n })();\n\n bucketExistsCache.set(bucket, promise);\n try {\n await promise;\n } catch (err) {\n // Drop the failing entry so the next call retries the check.\n bucketExistsCache.delete(bucket);\n throw err;\n }\n}\n\nexport const storeGcsInit: Store.Init<Types> = (context) => {\n assertGcsSettings(context.config.settings);\n const settings: GcsStoreSettings = context.config.settings;\n const prefix = normalizePrefix(settings.prefix);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const bucketRaw = settings.bucket;\n const bucket = encodeURIComponent(bucketRaw);\n const id = context.id;\n const { logger } = context;\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n const config: Store.Config<Types> = {\n settings,\n env: context.config.env,\n id: context.config.id,\n logger: context.config.logger,\n // `setup` from the incoming context.config is typed as `unknown` because\n // the Init context narrows only the settings/env/initSettings slots and\n // not the setup-options slot. The flow runtime never uses this field at\n // runtime (setup is invoked separately by the CLI), so omit it from the\n // returned config without a cast.\n };\n\n return {\n type: 'gcs',\n config,\n setup: gcsSetup,\n\n async get(key: string): Promise<Buffer | undefined> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return undefined;\n\n await ensureBucketExists(bucketRaw, id, settings, getToken, logger);\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/download/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}?alt=media`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n return Buffer.from(await res.arrayBuffer());\n } catch {\n return undefined;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n if (!isBufferLike(value)) {\n throw new Error(\n 'storeGcsInit.set: value must be a Buffer; got ' + typeof value,\n );\n }\n\n await ensureBucketExists(bucketRaw, id, settings, getToken, logger);\n\n const token = await getToken();\n const url = `${GCS_BASE}/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: bufferToBody(value),\n });\n },\n\n async delete(key: string): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n await ensureBucketExists(bucketRaw, id, settings, getToken, logger);\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch {\n /* GCS delete is idempotent */\n }\n },\n };\n};\n"],"mappings":"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","names":["setup","isRecord","GCS_BASE","isRecord","isServiceAccountShape","parseCredentials","GCS_BASE"]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createSign as t}from"crypto";var e="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",a="https://oauth2.googleapis.com/token",n="https://www.googleapis.com/auth/devstorage.read_write",o=6e4;function r(r){let s;return async function(){return s&&Date.now()<s.expiresAt||(s=r?await async function(e){const r=Math.floor(Date.now()/1e3),s=function(e,a){const n=i(JSON.stringify({alg:"RS256",typ:"JWT"})),o=i(JSON.stringify(e)),r=t("RSA-SHA256");return r.update(`${n}.${o}`),`${n}.${o}.${r.sign(a,"base64url")}`}({iss:e.client_email,scope:n,aud:a,iat:r,exp:r+3600},e.private_key),c=await fetch(a,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${s}`});if(!c.ok)throw new Error(`Token exchange error: ${c.status}`);const u=await c.json();return{token:u.access_token,expiresAt:Date.now()+1e3*u.expires_in-o}}(r):await async function(){const t=await fetch(e,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!t.ok)throw new Error(`Metadata server error: ${t.status}`);const a=await t.json();return{token:a.access_token,expiresAt:Date.now()+1e3*a.expires_in-o}}()),s.token}}function i(t){return("string"==typeof t?Buffer.from(t):t).toString("base64url")}var s="https://storage.googleapis.com";var c=t=>{const e=t.config.settings,a=function(t){if(!t)return"";const e=t.replace(/^\/+|\/+$/g,"");return e?e+"/":""}(e.prefix),n=r(function(t){if(t)return"string"==typeof t?JSON.parse(t):t}(e.credentials)),o=encodeURIComponent(e.bucket);function i(e){if(function(t){return!(!t||t.startsWith("/")||t.startsWith("\\")||t.split(/[/\\]/).includes(".."))}(e))return a+e;t.logger.warn("Invalid key rejected",{key:e})}return{type:"gcs",config:t.config,async get(t){const e=i(t);if(e)try{const t=await n(),a=`${s}/download/storage/v1/b/${o}/o/${encodeURIComponent(e)}?alt=media`,r=await fetch(a,{headers:{Authorization:`Bearer ${t}`}});if(!r.ok)return;return Buffer.from(await r.arrayBuffer())}catch{return}},async set(t,e){const a=i(t);if(!a)return;const r=await n(),c=`${s}/upload/storage/v1/b/${o}/o?uploadType=media&name=${encodeURIComponent(a)}`;await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/octet-stream"},body:new Uint8Array(e)})},async delete(t){const e=i(t);if(e)try{const t=await n(),a=`${s}/storage/v1/b/${o}/o/${encodeURIComponent(e)}`;await fetch(a,{method:"DELETE",headers:{Authorization:`Bearer ${t}`}})}catch{}}}};export{c as default,c as storeGcsInit};//# sourceMappingURL=index.mjs.map
1
+ import{createSign as e}from"crypto";var t="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",n="https://oauth2.googleapis.com/token",r="https://www.googleapis.com/auth/devstorage.read_write",o=6e4;function a(a){let s;return async function(){return s&&Date.now()<s.expiresAt||(s=a?await async function(t){const a=Math.floor(Date.now()/1e3),s=function(t,n){const r=i(JSON.stringify({alg:"RS256",typ:"JWT"})),o=i(JSON.stringify(t)),a=e("RSA-SHA256");return a.update(`${r}.${o}`),`${r}.${o}.${a.sign(n,"base64url")}`}({iss:t.client_email,scope:r,aud:n,iat:a,exp:a+3600},t.private_key),c=await fetch(n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${s}`});if(!c.ok)throw new Error(`Token exchange error: ${c.status}`);const u=await c.json();return{token:u.access_token,expiresAt:Date.now()+1e3*u.expires_in-o}}(a):await async function(){const e=await fetch(t,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Metadata server error: ${e.status}`);const n=await e.json();return{token:n.access_token,expiresAt:Date.now()+1e3*n.expires_in-o}}()),s.token}}function i(e){return("string"==typeof e?Buffer.from(e):e).toString("base64url")}import{resolveSetup as s}from"@walkeros/core";function c(e,t){if(t.projectId)return t.projectId;const n=function(e){if(!e)return;if("string"==typeof e){let t;try{t=JSON.parse(e)}catch{return}return u(t,"project_id")}return u(e,"project_id")}(e.credentials);if(n)return n;const r=process.env.GOOGLE_CLOUD_PROJECT;if(r)return r;throw new Error("setup: projectId is required. Set setup.projectId, provide a service account with project_id, or export GOOGLE_CLOUD_PROJECT.")}function u(e,t){if(!function(e){return"object"==typeof e&&null!==e}(e))return;const n=e[t];return"string"==typeof n?n:void 0}var f="https://storage.googleapis.com",l={location:"EU",storageClass:"STANDARD",versioning:!1},d=async e=>{const{config:t,logger:n}=e,r=s(t.setup,l);if(!r)return void n.debug("setup: skipped (config.setup is false or unset)");const o=t.settings;if(!o||!o.bucket)throw new Error("setup: settings.bucket is required");const i=c(o,r),u=a(function(e){if(!e)return;if("string"==typeof e){const n=JSON.parse(e);return p(t=n)&&"string"==typeof t.client_email&&"string"==typeof t.private_key?n:void 0}var t;return e}(o.credentials)),d=await u(),g=`${f}/storage/v1/b?project=${encodeURIComponent(i)}`,y=function(e,t){const n={name:e,location:t.location,storageClass:t.storageClass,versioning:{enabled:t.versioning??!1},iamConfiguration:{uniformBucketLevelAccess:{enabled:!0},publicAccessPrevention:"enforced"}};t.labels&&(n.labels=t.labels);t.kmsKeyName&&(n.encryption={defaultKmsKeyName:t.kmsKeyName});t.lifecycle&&(n.lifecycle=t.lifecycle);return n}(o.bucket,r),b=await fetch(g,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify(y)});if(200===b.status||201===b.status)return n.info("setup: bucket created",{bucket:o.bucket,projectId:i,location:r.location}),{bucketCreated:!0};if(409!==b.status){const e=await async function(e){try{return await e.text()}catch{return""}}(b);throw new Error(`setup: bucket create failed (${b.status}): ${e}`)}return n.debug("setup: bucket already exists",{bucket:o.bucket}),await async function(e,t,n,r){const o=`${f}/storage/v1/b/${encodeURIComponent(e)}`;let a;try{const e=await fetch(o,{headers:{Authorization:`Bearer ${t}`}});if(!e.ok)return void r.debug("setup: drift check failed (non-fatal)",{status:e.status});a=function(e){if(!p(e))return{};const t={};"string"==typeof e.location&&(t.location=e.location);"string"==typeof e.storageClass&&(t.storageClass=e.storageClass);if(p(e.versioning)){const n=e.versioning.enabled;"boolean"==typeof n&&(t.versioning={enabled:n})}if(p(e.iamConfiguration)){const n={},r=e.iamConfiguration.uniformBucketLevelAccess;p(r)&&"boolean"==typeof r.enabled&&(n.uniformBucketLevelAccess={enabled:r.enabled});const o=e.iamConfiguration.publicAccessPrevention;"string"==typeof o&&(n.publicAccessPrevention=o),t.iamConfiguration=n}if(p(e.labels)){const n={};for(const[t,r]of Object.entries(e.labels))"string"==typeof r&&(n[t]=r);t.labels=n}return t}(await e.json())}catch(e){return void r.debug("setup: drift check failed (non-fatal)",{error:e instanceof Error?e.message:String(e)})}"string"==typeof a.location&&void 0!==n.location&&a.location!==n.location&&r.warn("setup.drift",{field:"location",declared:n.location,actual:a.location});"string"==typeof a.storageClass&&void 0!==n.storageClass&&a.storageClass!==n.storageClass&&r.warn("setup.drift",{field:"storageClass",declared:n.storageClass,actual:a.storageClass});const i=n.versioning??!1,s=a.versioning?.enabled;"boolean"==typeof s&&s!==i&&r.warn("setup.drift",{field:"versioning",declared:i,actual:s});const c=a.iamConfiguration;if(c){const e=c.uniformBucketLevelAccess?.enabled;"boolean"==typeof e&&!0!==e&&r.warn("setup.drift",{field:"uniformBucketLevelAccess",declared:!0,actual:e});const t=c.publicAccessPrevention;"string"==typeof t&&"enforced"!==t&&r.warn("setup.drift",{field:"publicAccessPrevention",declared:"enforced",actual:t})}if(n.labels){const e=a.labels??{};(function(e,t){const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(const r of n)if(e[r]!==t[r])return!1;return!0})(n.labels,e)||r.warn("setup.drift",{field:"labels",declared:n.labels,actual:e})}}(o.bucket,d,r,n),{bucketCreated:!1}};function p(e){return"object"==typeof e&&null!==e}var g="https://storage.googleapis.com",y=new Map;function b(e){const t=new ArrayBuffer(e.byteLength),n=new Uint8Array(t);return n.set(e),n}async function w(e,t,n,r,o){const a=y.get(e);if(void 0!==a)return void await a;const i=(async()=>{let a;try{const t=await r(),n=`${g}/storage/v1/b/${encodeURIComponent(e)}`;a=await fetch(n,{method:"HEAD",headers:{Authorization:`Bearer ${t}`}})}catch(t){return o.debug("ensureBucketExists check failed (non-fatal)",{bucket:e,error:t instanceof Error?t.message:String(t)}),!0}if(404===a.status){const r=function(e){try{return c(e,{})}catch{return"unknown"}}(n);throw new Error(`GCS bucket not found: ${e} in project ${r}. Run "walkeros setup store.${t}" to create it.`)}return a.ok||o.debug("ensureBucketExists check failed (non-fatal)",{bucket:e,status:a.status}),!0})();y.set(e,i);try{await i}catch(t){throw y.delete(e),t}}var h=e=>{!function(e){if(!e||"string"!=typeof e.bucket||0===e.bucket.length)throw new Error("storeGcsInit: settings.bucket is required (non-empty string)")}(e.config.settings);const t=e.config.settings,n=function(e){if(!e)return"";const t=e.replace(/^\/+|\/+$/g,"");return t?t+"/":""}(t.prefix),r=a(function(e){if(e){if("string"==typeof e){const n=JSON.parse(e);return function(e){return"object"==typeof e&&null!==e}(t=n)&&"string"==typeof t.client_email&&"string"==typeof t.private_key?n:void 0}var t;return e}}(t.credentials)),o=t.bucket,i=encodeURIComponent(o),s=e.id,{logger:c}=e;function u(e){if(function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(e))return n+e;c.warn("Invalid key rejected",{key:e})}return{type:"gcs",config:{settings:t,env:e.config.env,id:e.config.id,logger:e.config.logger},setup:d,async get(e){const n=u(e);if(n){await w(o,s,t,r,c);try{const e=await r(),t=`${g}/download/storage/v1/b/${i}/o/${encodeURIComponent(n)}?alt=media`,o=await fetch(t,{headers:{Authorization:`Bearer ${e}`}});if(!o.ok)return;return Buffer.from(await o.arrayBuffer())}catch{return}}},async set(e,n){const a=u(e);if(!a)return;if(!function(e){return Buffer.isBuffer(e)}(n))throw new Error("storeGcsInit.set: value must be a Buffer; got "+typeof n);await w(o,s,t,r,c);const f=await r(),l=`${g}/upload/storage/v1/b/${i}/o?uploadType=media&name=${encodeURIComponent(a)}`;await fetch(l,{method:"POST",headers:{Authorization:`Bearer ${f}`,"Content-Type":"application/octet-stream"},body:b(n)})},async delete(e){const n=u(e);if(n){await w(o,s,t,r,c);try{const e=await r(),t=`${g}/storage/v1/b/${i}/o/${encodeURIComponent(n)}`;await fetch(t,{method:"DELETE",headers:{Authorization:`Bearer ${e}`}})}catch{}}}}};export{l as DEFAULT_SETUP,h as default,d as setup,h as storeGcsInit};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth.ts","../src/store.ts"],"sourcesContent":["import { createSign } from 'node:crypto';\nimport type { ServiceAccountCredentials } from './types';\n\nexport type TokenProvider = () => Promise<string>;\n\nconst METADATA_URL =\n 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token';\nconst OAUTH_URL = 'https://oauth2.googleapis.com/token';\nconst SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n): TokenProvider {\n let cached: CachedToken | undefined;\n\n return async function getToken(): Promise<string> {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n\n cached = credentials\n ? await fetchServiceAccountToken(credentials)\n : await fetchMetadataToken();\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(): Promise<CachedToken> {\n const res = await fetch(METADATA_URL, {\n headers: { 'Metadata-Flavor': 'Google' },\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`Metadata server error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nasync function fetchServiceAccountToken(\n creds: ServiceAccountCredentials,\n): Promise<CachedToken> {\n const now = Math.floor(Date.now() / 1000);\n const jwt = signJwt(\n {\n iss: creds.client_email,\n scope: SCOPE,\n aud: OAUTH_URL,\n iat: now,\n exp: now + 3600,\n },\n creds.private_key,\n );\n\n const res = await fetch(OAUTH_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`,\n });\n if (!res.ok) throw new Error(`Token exchange error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nfunction base64url(input: string | Buffer): string {\n const buf = typeof input === 'string' ? Buffer.from(input) : input;\n return buf.toString('base64url');\n}\n\nfunction signJwt(payload: object, privateKey: string): string {\n const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));\n const body = base64url(JSON.stringify(payload));\n const sign = createSign('RSA-SHA256');\n sign.update(`${header}.${body}`);\n return `${header}.${body}.${sign.sign(privateKey, 'base64url')}`;\n}\n","import type { Store } from '@walkeros/core';\nimport type { GcsStoreSettings, ServiceAccountCredentials } from './types';\nimport { createTokenProvider } from './auth';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') return JSON.parse(credentials);\n return credentials;\n}\n\nexport const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>> = (\n context,\n) => {\n const settings = context.config.settings as GcsStoreSettings;\n const prefix = normalizePrefix(settings.prefix);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const bucket = encodeURIComponent(settings.bucket);\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 return {\n type: 'gcs',\n config: context.config as Store.Config<Store.Types<GcsStoreSettings>>,\n\n async get(key: string): Promise<Buffer | undefined> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return undefined;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/download/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}?alt=media`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n return Buffer.from(await res.arrayBuffer());\n } catch {\n return undefined;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n const token = await getToken();\n const url = `${GCS_BASE}/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: new Uint8Array(value as Buffer),\n });\n },\n\n async delete(key: string): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch {\n /* GCS delete is idempotent */\n }\n },\n };\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/auth.ts","../src/setup.ts","../src/setup-helpers.ts","../src/store.ts"],"sourcesContent":["import { createSign } from 'node:crypto';\nimport type { ServiceAccountCredentials } from './types';\n\nexport type TokenProvider = () => Promise<string>;\n\nconst METADATA_URL =\n 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token';\nconst OAUTH_URL = 'https://oauth2.googleapis.com/token';\nconst SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n): TokenProvider {\n let cached: CachedToken | undefined;\n\n return async function getToken(): Promise<string> {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n\n cached = credentials\n ? await fetchServiceAccountToken(credentials)\n : await fetchMetadataToken();\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(): Promise<CachedToken> {\n const res = await fetch(METADATA_URL, {\n headers: { 'Metadata-Flavor': 'Google' },\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`Metadata server error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nasync function fetchServiceAccountToken(\n creds: ServiceAccountCredentials,\n): Promise<CachedToken> {\n const now = Math.floor(Date.now() / 1000);\n const jwt = signJwt(\n {\n iss: creds.client_email,\n scope: SCOPE,\n aud: OAUTH_URL,\n iat: now,\n exp: now + 3600,\n },\n creds.private_key,\n );\n\n const res = await fetch(OAUTH_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`,\n });\n if (!res.ok) throw new Error(`Token exchange error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nfunction base64url(input: string | Buffer): string {\n const buf = typeof input === 'string' ? Buffer.from(input) : input;\n return buf.toString('base64url');\n}\n\nfunction signJwt(payload: object, privateKey: string): string {\n const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));\n const body = base64url(JSON.stringify(payload));\n const sign = createSign('RSA-SHA256');\n sign.update(`${header}.${body}`);\n return `${header}.${body}.${sign.sign(privateKey, 'base64url')}`;\n}\n","import type { LifecycleContext, Logger, Store, SetupFn } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type {\n GcsStoreSettings,\n ServiceAccountCredentials,\n Setup,\n Types,\n} from './types';\nimport { createTokenProvider } from './auth';\nimport { resolveProjectId } from './setup-helpers';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\n/**\n * Default setup options. Optional fields (lifecycle, kmsKeyName, labels,\n * projectId) are intentionally omitted so the resolved options carry\n * `undefined` for them and the body builder can skip writing those\n * properties to the create request payload.\n */\nexport const DEFAULT_SETUP: Setup = {\n location: 'EU',\n storageClass: 'STANDARD',\n versioning: false,\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 GcsStoreConfig = Store.Config<Types>;\n\n/**\n * Provision a GCS bucket described in the flow config.\n * Idempotent: 409 means the bucket exists, drift is logged but never patched.\n * Never auto-mutates an existing bucket.\n */\nexport const setup: SetupFn<GcsStoreConfig, Store.BaseEnv> = async (\n context: LifecycleContext<GcsStoreConfig, Store.BaseEnv>,\n) => {\n const { config, logger } = context;\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 const settings = config.settings;\n if (!settings || !settings.bucket) {\n throw new Error('setup: settings.bucket is required');\n }\n\n const projectId = resolveProjectId(settings, options);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const token = await getToken();\n\n const createUrl = `${GCS_BASE}/storage/v1/b?project=${encodeURIComponent(projectId)}`;\n const body = buildCreateBody(settings.bucket, options);\n\n const createRes = await fetch(createUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (createRes.status === 200 || createRes.status === 201) {\n logger.info('setup: bucket created', {\n bucket: settings.bucket,\n projectId,\n location: options.location,\n });\n return { bucketCreated: true };\n }\n\n if (createRes.status !== 409) {\n const text = await safeText(createRes);\n throw new Error(\n `setup: bucket create failed (${createRes.status}): ${text}`,\n );\n }\n\n logger.debug('setup: bucket already exists', { bucket: settings.bucket });\n await detectDrift(settings.bucket, token, options, logger);\n return { bucketCreated: false };\n};\n\ninterface CreateBody {\n name: string;\n location?: string;\n storageClass?: string;\n versioning: { enabled: boolean };\n iamConfiguration: {\n uniformBucketLevelAccess: { enabled: true };\n publicAccessPrevention: 'enforced';\n };\n labels?: Record<string, string>;\n encryption?: { defaultKmsKeyName: string };\n lifecycle?: { rule: unknown[] };\n}\n\nfunction buildCreateBody(bucket: string, options: Setup): CreateBody {\n const body: CreateBody = {\n name: bucket,\n location: options.location,\n storageClass: options.storageClass,\n versioning: { enabled: options.versioning ?? false },\n iamConfiguration: {\n uniformBucketLevelAccess: { enabled: true },\n publicAccessPrevention: 'enforced',\n },\n };\n if (options.labels) body.labels = options.labels;\n if (options.kmsKeyName) {\n body.encryption = { defaultKmsKeyName: options.kmsKeyName };\n }\n if (options.lifecycle) body.lifecycle = options.lifecycle;\n return body;\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n const parsed: unknown = JSON.parse(credentials);\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n return credentials;\n}\n\nfunction isServiceAccountShape(\n value: unknown,\n): value is ServiceAccountCredentials {\n if (!isRecord(value)) return false;\n return (\n typeof value.client_email === 'string' &&\n typeof value.private_key === 'string'\n );\n}\n\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text();\n } catch {\n return '';\n }\n}\n\ninterface BucketMetadata {\n location?: string;\n storageClass?: string;\n versioning?: { enabled?: boolean };\n iamConfiguration?: {\n uniformBucketLevelAccess?: { enabled?: boolean };\n publicAccessPrevention?: string;\n };\n labels?: Record<string, string>;\n}\n\nasync function detectDrift(\n bucket: string,\n token: string,\n options: Setup,\n logger: Logger.Instance,\n): Promise<void> {\n const url = `${GCS_BASE}/storage/v1/b/${encodeURIComponent(bucket)}`;\n let metadata: BucketMetadata;\n try {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) {\n logger.debug('setup: drift check failed (non-fatal)', {\n status: res.status,\n });\n return;\n }\n const parsed: unknown = await res.json();\n metadata = readBucketMetadata(parsed);\n } catch (err) {\n logger.debug('setup: drift check failed (non-fatal)', {\n error: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n\n if (\n typeof metadata.location === 'string' &&\n options.location !== undefined &&\n metadata.location !== options.location\n ) {\n logger.warn('setup.drift', {\n field: 'location',\n declared: options.location,\n actual: metadata.location,\n });\n }\n\n if (\n typeof metadata.storageClass === 'string' &&\n options.storageClass !== undefined &&\n metadata.storageClass !== options.storageClass\n ) {\n logger.warn('setup.drift', {\n field: 'storageClass',\n declared: options.storageClass,\n actual: metadata.storageClass,\n });\n }\n\n const declaredVersioning = options.versioning ?? false;\n const actualVersioning = metadata.versioning?.enabled;\n if (\n typeof actualVersioning === 'boolean' &&\n actualVersioning !== declaredVersioning\n ) {\n logger.warn('setup.drift', {\n field: 'versioning',\n declared: declaredVersioning,\n actual: actualVersioning,\n });\n }\n\n const iam = metadata.iamConfiguration;\n if (iam) {\n const actualUniform = iam.uniformBucketLevelAccess?.enabled;\n if (typeof actualUniform === 'boolean' && actualUniform !== true) {\n logger.warn('setup.drift', {\n field: 'uniformBucketLevelAccess',\n declared: true,\n actual: actualUniform,\n });\n }\n const actualPap = iam.publicAccessPrevention;\n if (typeof actualPap === 'string' && actualPap !== 'enforced') {\n logger.warn('setup.drift', {\n field: 'publicAccessPrevention',\n declared: 'enforced',\n actual: actualPap,\n });\n }\n }\n\n if (options.labels) {\n const actualLabels = metadata.labels ?? {};\n if (!labelsEqual(options.labels, actualLabels)) {\n logger.warn('setup.drift', {\n field: 'labels',\n declared: options.labels,\n actual: actualLabels,\n });\n }\n }\n}\n\nfunction labelsEqual(\n a: Record<string, string>,\n b: Record<string, string>,\n): boolean {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (a[key] !== b[key]) return false;\n }\n return true;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readBucketMetadata(value: unknown): BucketMetadata {\n if (!isRecord(value)) return {};\n const out: BucketMetadata = {};\n if (typeof value.location === 'string') out.location = value.location;\n if (typeof value.storageClass === 'string') {\n out.storageClass = value.storageClass;\n }\n if (isRecord(value.versioning)) {\n const enabled = value.versioning.enabled;\n if (typeof enabled === 'boolean') {\n out.versioning = { enabled };\n }\n }\n if (isRecord(value.iamConfiguration)) {\n const iam: BucketMetadata['iamConfiguration'] = {};\n const uniform = value.iamConfiguration.uniformBucketLevelAccess;\n if (isRecord(uniform) && typeof uniform.enabled === 'boolean') {\n iam.uniformBucketLevelAccess = { enabled: uniform.enabled };\n }\n const pap = value.iamConfiguration.publicAccessPrevention;\n if (typeof pap === 'string') iam.publicAccessPrevention = pap;\n out.iamConfiguration = iam;\n }\n if (isRecord(value.labels)) {\n const labels: Record<string, string> = {};\n for (const [k, v] of Object.entries(value.labels)) {\n if (typeof v === 'string') labels[k] = v;\n }\n out.labels = labels;\n }\n return out;\n}\n","import type { GcsStoreSettings, ServiceAccountCredentials } from './types';\n\n/**\n * Resolves the GCP project ID used to create a bucket in the GCS JSON API.\n *\n * Resolution order:\n * 1. `setup.projectId` (explicit override).\n * 2. `settings.credentials.project_id` (when SA JSON is parsed and contains it).\n * 3. `process.env.GOOGLE_CLOUD_PROJECT` (Cloud Run / GKE convention).\n * 4. Throw with an actionable error message.\n *\n * `process.env.GOOGLE_CLOUD_PROJECT` is read at call time, not at module load,\n * so changes after import are honored.\n */\nexport function resolveProjectId(\n settings: GcsStoreSettings,\n setup: { projectId?: string },\n): string {\n if (setup.projectId) return setup.projectId;\n\n const fromCreds = extractProjectIdFromCredentials(settings.credentials);\n if (fromCreds) return fromCreds;\n\n const fromEnv = process.env.GOOGLE_CLOUD_PROJECT;\n if (fromEnv) return fromEnv;\n\n throw new Error(\n 'setup: projectId is required. Set setup.projectId, provide a service account with project_id, or export GOOGLE_CLOUD_PROJECT.',\n );\n}\n\n/**\n * Typed predicate: returns the `project_id` field from credentials when\n * present and a string. Avoids type casts by validating the runtime shape.\n *\n * - `string` credentials are parsed as JSON; non-JSON or non-object payloads\n * return undefined.\n * - object credentials are inspected directly. Real SA JSON contains\n * `project_id` even though `ServiceAccountCredentials` declares only the\n * fields the runtime needs (client_email, private_key).\n */\nfunction extractProjectIdFromCredentials(\n credentials: GcsStoreSettings['credentials'],\n): string | undefined {\n if (!credentials) return undefined;\n\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n return readStringField(parsed, 'project_id');\n }\n\n return readStringField(credentials, 'project_id');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readStringField(\n value: ServiceAccountCredentials | unknown,\n field: string,\n): string | undefined {\n if (!isRecord(value)) return undefined;\n const fieldValue = value[field];\n return typeof fieldValue === 'string' ? fieldValue : undefined;\n}\n","import type { Logger, Store } from '@walkeros/core';\nimport type {\n GcsStoreSettings,\n ServiceAccountCredentials,\n Types,\n} from './types';\nimport { createTokenProvider } from './auth';\nimport { setup as gcsSetup } from './setup';\nimport { resolveProjectId } from './setup-helpers';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\n/**\n * Module-level cache for the bucket existence pre-check. Keyed by bucket\n * name (bucket names are globally unique in GCS). One HEAD per process per\n * bucket. The cache stores a Promise so concurrent first-operations share a\n * single in-flight request.\n *\n * Exported helpers below allow tests to reset/seed the cache.\n */\nconst bucketExistsCache: Map<string, Promise<boolean>> = new Map();\n\n/** @internal Test-only: clear the existence cache. */\nexport function __resetBucketExistenceCache(): void {\n bucketExistsCache.clear();\n}\n\n/** @internal Test-only: seed the existence cache for a bucket. */\nexport function __seedBucketExists(bucket: string): void {\n bucketExistsCache.set(bucket, Promise.resolve(true));\n}\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isServiceAccountShape(\n value: unknown,\n): value is ServiceAccountCredentials {\n if (!isRecord(value)) return false;\n return (\n typeof value.client_email === 'string' &&\n typeof value.private_key === 'string'\n );\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n const parsed: unknown = JSON.parse(credentials);\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n return credentials;\n}\n\nfunction isBufferLike(value: unknown): value is Buffer {\n return Buffer.isBuffer(value);\n}\n\n/**\n * Wrap a Buffer in a fresh Uint8Array backed by a plain ArrayBuffer so it\n * satisfies fetch's BodyInit contract. Avoids type casts: Node's Buffer\n * type is `Buffer<ArrayBufferLike>`, while the DOM's BodyInit (used by\n * fetch's RequestInit) expects `ArrayBufferView<ArrayBuffer>`.\n */\nfunction bufferToBody(buf: Buffer): Uint8Array<ArrayBuffer> {\n const out = new ArrayBuffer(buf.byteLength);\n const view = new Uint8Array(out);\n view.set(buf);\n return view;\n}\n\nfunction assertGcsSettings(\n settings: Partial<GcsStoreSettings> | undefined,\n): asserts settings is GcsStoreSettings {\n if (\n !settings ||\n typeof settings.bucket !== 'string' ||\n settings.bucket.length === 0\n ) {\n throw new Error(\n 'storeGcsInit: settings.bucket is required (non-empty string)',\n );\n }\n}\n\n/**\n * Resolve a project ID for the runtime hard-fail message. Uses the same\n * resolution order as setup but never throws: returns \"unknown\" when no\n * source is available so the error message is still actionable.\n */\nfunction resolveProjectIdForMessage(settings: GcsStoreSettings): string {\n try {\n return resolveProjectId(settings, {});\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Verify the bucket exists. Issues `HEAD /storage/v1/b/<bucket>` once per\n * process per bucket. On 404, throws an actionable error pointing at the\n * setup command. On any other failure (non-2xx response or transport\n * error), treats the result as \"exists\" to avoid blocking legitimate\n * operations on a transient blip.\n */\nasync function ensureBucketExists(\n bucket: string,\n id: string,\n settings: GcsStoreSettings,\n getToken: () => Promise<string>,\n logger: Logger.Instance,\n): Promise<void> {\n const existing = bucketExistsCache.get(bucket);\n if (existing !== undefined) {\n await existing;\n return;\n }\n\n const promise = (async (): Promise<boolean> => {\n let res: Response;\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${encodeURIComponent(bucket)}`;\n res = await fetch(url, {\n method: 'HEAD',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch (err) {\n logger.debug('ensureBucketExists check failed (non-fatal)', {\n bucket,\n error: err instanceof Error ? err.message : String(err),\n });\n return true;\n }\n\n if (res.status === 404) {\n const projectId = resolveProjectIdForMessage(settings);\n throw new Error(\n `GCS bucket not found: ${bucket} in project ${projectId}. Run \"walkeros setup store.${id}\" to create it.`,\n );\n }\n if (!res.ok) {\n logger.debug('ensureBucketExists check failed (non-fatal)', {\n bucket,\n status: res.status,\n });\n }\n return true;\n })();\n\n bucketExistsCache.set(bucket, promise);\n try {\n await promise;\n } catch (err) {\n // Drop the failing entry so the next call retries the check.\n bucketExistsCache.delete(bucket);\n throw err;\n }\n}\n\nexport const storeGcsInit: Store.Init<Types> = (context) => {\n assertGcsSettings(context.config.settings);\n const settings: GcsStoreSettings = context.config.settings;\n const prefix = normalizePrefix(settings.prefix);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const bucketRaw = settings.bucket;\n const bucket = encodeURIComponent(bucketRaw);\n const id = context.id;\n const { logger } = context;\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n const config: Store.Config<Types> = {\n settings,\n env: context.config.env,\n id: context.config.id,\n logger: context.config.logger,\n // `setup` from the incoming context.config is typed as `unknown` because\n // the Init context narrows only the settings/env/initSettings slots and\n // not the setup-options slot. The flow runtime never uses this field at\n // runtime (setup is invoked separately by the CLI), so omit it from the\n // returned config without a cast.\n };\n\n return {\n type: 'gcs',\n config,\n setup: gcsSetup,\n\n async get(key: string): Promise<Buffer | undefined> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return undefined;\n\n await ensureBucketExists(bucketRaw, id, settings, getToken, logger);\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/download/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}?alt=media`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n return Buffer.from(await res.arrayBuffer());\n } catch {\n return undefined;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n if (!isBufferLike(value)) {\n throw new Error(\n 'storeGcsInit.set: value must be a Buffer; got ' + typeof value,\n );\n }\n\n await ensureBucketExists(bucketRaw, id, settings, getToken, logger);\n\n const token = await getToken();\n const url = `${GCS_BASE}/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: bufferToBody(value),\n });\n },\n\n async delete(key: string): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n await ensureBucketExists(bucketRaw, id, settings, getToken, logger);\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch {\n /* GCS delete is idempotent */\n }\n },\n };\n};\n"],"mappings":"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","names":["setup","isRecord","GCS_BASE","isRecord","isServiceAccountShape","parseCredentials","GCS_BASE"]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$meta": {
3
3
  "package": "@walkeros/server-store-gcs",
4
- "version": "4.0.0",
4
+ "version": "4.1.0-next-1778155282668",
5
5
  "type": "store",
6
6
  "platform": [
7
7
  "server"
@@ -54,6 +54,58 @@
54
54
  "bucket"
55
55
  ],
56
56
  "additionalProperties": false
57
+ },
58
+ "setup": {
59
+ "$schema": "http://json-schema.org/draft-07/schema#",
60
+ "type": "object",
61
+ "properties": {
62
+ "projectId": {
63
+ "description": "GCP project that owns the bucket. Falls back to credentials.project_id then GOOGLE_CLOUD_PROJECT env",
64
+ "type": "string"
65
+ },
66
+ "location": {
67
+ "default": "EU",
68
+ "description": "Geographic location (multi-region or region code)",
69
+ "type": "string"
70
+ },
71
+ "storageClass": {
72
+ "default": "STANDARD",
73
+ "description": "Default storage class for objects",
74
+ "type": "string",
75
+ "enum": [
76
+ "STANDARD",
77
+ "NEARLINE",
78
+ "COLDLINE",
79
+ "ARCHIVE"
80
+ ]
81
+ },
82
+ "versioning": {
83
+ "default": false,
84
+ "description": "Enable object versioning at create time",
85
+ "type": "boolean"
86
+ },
87
+ "labels": {
88
+ "description": "Labels for cost allocation",
89
+ "type": "object",
90
+ "propertyNames": {
91
+ "type": "string"
92
+ },
93
+ "additionalProperties": {
94
+ "type": "string"
95
+ }
96
+ },
97
+ "kmsKeyName": {
98
+ "description": "Customer-managed encryption key for at-rest encryption",
99
+ "type": "string"
100
+ }
101
+ },
102
+ "required": [
103
+ "location",
104
+ "storageClass",
105
+ "versioning"
106
+ ],
107
+ "additionalProperties": false,
108
+ "description": "Provisioning options for \"walkeros setup store.<id>\". Idempotent: never mutates an existing bucket."
57
109
  }
58
110
  },
59
111
  "examples": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walkeros/server-store-gcs",
3
3
  "description": "Google Cloud Storage for walkerOS server flows",
4
- "version": "4.0.0",
4
+ "version": "4.1.0-next-1778155282668",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -32,7 +32,7 @@
32
32
  "update": "npx npm-check-updates -u && npm update"
33
33
  },
34
34
  "dependencies": {
35
- "@walkeros/core": "4.0.0"
35
+ "@walkeros/core": "4.1.0-next-1778155282668"
36
36
  },
37
37
  "devDependencies": {},
38
38
  "repository": {