@walkeros/server-store-gcs 3.0.0-next-1773236214827
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 +107 -0
- package/dist/dev.d.mts +47 -0
- package/dist/dev.d.ts +47 -0
- package/dist/dev.js +1 -0
- package/dist/dev.js.map +1 -0
- package/dist/dev.mjs +1 -0
- package/dist/dev.mjs.map +1 -0
- package/dist/index.d.mts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -0
- package/dist/walkerOS.json +99 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @walkeros/server-store-gcs
|
|
2
|
+
|
|
3
|
+
Google Cloud Storage for walkerOS server flows.
|
|
4
|
+
|
|
5
|
+
Zero runtime dependencies — uses raw `fetch` against the GCS JSON API with
|
|
6
|
+
built-in auth (ADC on Cloud Run / service account JWT elsewhere).
|
|
7
|
+
|
|
8
|
+
## Quick Start (Bundled Mode)
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"version": 1,
|
|
13
|
+
"flows": {
|
|
14
|
+
"default": {
|
|
15
|
+
"server": {},
|
|
16
|
+
"stores": {
|
|
17
|
+
"assets": {
|
|
18
|
+
"package": "@walkeros/server-store-gcs",
|
|
19
|
+
"config": {
|
|
20
|
+
"settings": {
|
|
21
|
+
"bucket": "my-assets",
|
|
22
|
+
"prefix": "public"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"transformers": {
|
|
28
|
+
"file": {
|
|
29
|
+
"package": "@walkeros/server-transformer-file",
|
|
30
|
+
"config": { "settings": { "prefix": "/static" } },
|
|
31
|
+
"env": { "store": "$store:assets" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Integrated Mode
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { storeGcsInit } from '@walkeros/server-store-gcs';
|
|
43
|
+
|
|
44
|
+
const store = await storeGcsInit({
|
|
45
|
+
collector,
|
|
46
|
+
logger,
|
|
47
|
+
id: 'assets',
|
|
48
|
+
config: {
|
|
49
|
+
settings: {
|
|
50
|
+
bucket: 'my-assets',
|
|
51
|
+
prefix: 'public',
|
|
52
|
+
// Omit credentials for ADC on Cloud Run/GKE
|
|
53
|
+
// Or pass SA JSON: credentials: process.env.GCS_SA_KEY
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
env: {},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const file = await store.get('walker.js'); // Buffer | undefined
|
|
60
|
+
await store.set('cache/data.json', Buffer.from('{}'));
|
|
61
|
+
await store.delete('old-file.txt');
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
|
|
66
|
+
| Setting | Type | Required | Default | Description |
|
|
67
|
+
| ------------- | ------------------ | -------- | ------- | ------------------------------ |
|
|
68
|
+
| `bucket` | `string` | Yes | — | GCS bucket name |
|
|
69
|
+
| `prefix` | `string` | No | — | Key prefix for scoping |
|
|
70
|
+
| `credentials` | `string \| object` | No | ADC | Service account JSON or string |
|
|
71
|
+
|
|
72
|
+
## Authentication
|
|
73
|
+
|
|
74
|
+
### Cloud Run / GKE (ADC)
|
|
75
|
+
|
|
76
|
+
When running on GCP infrastructure, omit `credentials`. The store fetches tokens
|
|
77
|
+
from the metadata server automatically.
|
|
78
|
+
|
|
79
|
+
### Non-GCP (Service Account)
|
|
80
|
+
|
|
81
|
+
Pass a service account JSON as a string (from `$env.GCS_SA_KEY`) or as an object
|
|
82
|
+
with `client_email` and `private_key` fields. The store signs JWTs locally and
|
|
83
|
+
exchanges them for access tokens.
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"credentials": "$env.GCS_SA_KEY"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Security
|
|
92
|
+
|
|
93
|
+
- **Key validation**: Path traversal attempts (`..`, absolute paths) are
|
|
94
|
+
rejected
|
|
95
|
+
- **Prefix scoping**: The `prefix` setting restricts all operations to a
|
|
96
|
+
subdirectory
|
|
97
|
+
- **Token caching**: Access tokens are cached and refreshed automatically
|
|
98
|
+
|
|
99
|
+
## API
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const file = await store.get('walker.js'); // Buffer | undefined
|
|
103
|
+
await store.set('data.json', Buffer.from('{}')); // void
|
|
104
|
+
await store.delete('old-file.txt'); // void
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`get()` returns `Buffer` for compatibility with the file transformer.
|
package/dist/dev.d.mts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as _walkeros_core_dev from '@walkeros/core/dev';
|
|
2
|
+
import { z } from '@walkeros/core/dev';
|
|
3
|
+
import { Flow, Store } from '@walkeros/core';
|
|
4
|
+
|
|
5
|
+
declare const SettingsSchema: z.ZodObject<{
|
|
6
|
+
bucket: z.ZodString;
|
|
7
|
+
prefix: z.ZodOptional<z.ZodString>;
|
|
8
|
+
credentials: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
9
|
+
client_email: z.ZodString;
|
|
10
|
+
private_key: z.ZodString;
|
|
11
|
+
}, z.core.$strip>]>>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
type Settings = z.infer<typeof SettingsSchema>;
|
|
14
|
+
|
|
15
|
+
declare const settings: _walkeros_core_dev.JSONSchema;
|
|
16
|
+
|
|
17
|
+
type index$1_Settings = Settings;
|
|
18
|
+
declare const index$1_SettingsSchema: typeof SettingsSchema;
|
|
19
|
+
declare const index$1_settings: typeof settings;
|
|
20
|
+
declare namespace index$1 {
|
|
21
|
+
export { type index$1_Settings as Settings, index$1_SettingsSchema as SettingsSchema, index$1_settings as settings };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Read a file using Application Default Credentials (Cloud Run / GKE). */
|
|
25
|
+
declare const readWithAdc: Flow.StepExample;
|
|
26
|
+
/** Key is scoped under the configured prefix subdirectory. */
|
|
27
|
+
declare const prefixScoping: Flow.StepExample;
|
|
28
|
+
|
|
29
|
+
declare const step_prefixScoping: typeof prefixScoping;
|
|
30
|
+
declare const step_readWithAdc: typeof readWithAdc;
|
|
31
|
+
declare namespace step {
|
|
32
|
+
export { step_prefixScoping as prefixScoping, step_readWithAdc as readWithAdc };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Cloud Run with ADC — no credentials needed */
|
|
36
|
+
declare const cloudRunAdc: Store.Config;
|
|
37
|
+
/** Explicit service account for non-GCP environments */
|
|
38
|
+
declare const serviceAccount: Store.Config;
|
|
39
|
+
|
|
40
|
+
declare const index_cloudRunAdc: typeof cloudRunAdc;
|
|
41
|
+
declare const index_serviceAccount: typeof serviceAccount;
|
|
42
|
+
declare const index_step: typeof step;
|
|
43
|
+
declare namespace index {
|
|
44
|
+
export { index_cloudRunAdc as cloudRunAdc, index_serviceAccount as serviceAccount, index_step as step };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { index as examples, index$1 as schemas };
|
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as _walkeros_core_dev from '@walkeros/core/dev';
|
|
2
|
+
import { z } from '@walkeros/core/dev';
|
|
3
|
+
import { Flow, Store } from '@walkeros/core';
|
|
4
|
+
|
|
5
|
+
declare const SettingsSchema: z.ZodObject<{
|
|
6
|
+
bucket: z.ZodString;
|
|
7
|
+
prefix: z.ZodOptional<z.ZodString>;
|
|
8
|
+
credentials: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
9
|
+
client_email: z.ZodString;
|
|
10
|
+
private_key: z.ZodString;
|
|
11
|
+
}, z.core.$strip>]>>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
type Settings = z.infer<typeof SettingsSchema>;
|
|
14
|
+
|
|
15
|
+
declare const settings: _walkeros_core_dev.JSONSchema;
|
|
16
|
+
|
|
17
|
+
type index$1_Settings = Settings;
|
|
18
|
+
declare const index$1_SettingsSchema: typeof SettingsSchema;
|
|
19
|
+
declare const index$1_settings: typeof settings;
|
|
20
|
+
declare namespace index$1 {
|
|
21
|
+
export { type index$1_Settings as Settings, index$1_SettingsSchema as SettingsSchema, index$1_settings as settings };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Read a file using Application Default Credentials (Cloud Run / GKE). */
|
|
25
|
+
declare const readWithAdc: Flow.StepExample;
|
|
26
|
+
/** Key is scoped under the configured prefix subdirectory. */
|
|
27
|
+
declare const prefixScoping: Flow.StepExample;
|
|
28
|
+
|
|
29
|
+
declare const step_prefixScoping: typeof prefixScoping;
|
|
30
|
+
declare const step_readWithAdc: typeof readWithAdc;
|
|
31
|
+
declare namespace step {
|
|
32
|
+
export { step_prefixScoping as prefixScoping, step_readWithAdc as readWithAdc };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Cloud Run with ADC — no credentials needed */
|
|
36
|
+
declare const cloudRunAdc: Store.Config;
|
|
37
|
+
/** Explicit service account for non-GCP environments */
|
|
38
|
+
declare const serviceAccount: Store.Config;
|
|
39
|
+
|
|
40
|
+
declare const index_cloudRunAdc: typeof cloudRunAdc;
|
|
41
|
+
declare const index_serviceAccount: typeof serviceAccount;
|
|
42
|
+
declare const index_step: typeof step;
|
|
43
|
+
declare namespace index {
|
|
44
|
+
export { index_cloudRunAdc as cloudRunAdc, index_serviceAccount as serviceAccount, index_step as step };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { index as examples, index$1 as schemas };
|
package/dist/dev.js
ADDED
|
@@ -0,0 +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:()=>l,settings:()=>p});var a=require("@walkeros/core/dev"),u=require("@walkeros/core/dev"),l=u.z.object({bucket:u.z.string().min(1).describe("GCS bucket name"),prefix:u.z.string().optional().describe("Key prefix prepended to all store keys for scoping"),credentials:u.z.union([u.z.string(),u.z.object({client_email:u.z.string().describe("Service account email"),private_key:u.z.string().describe("RSA private key in PEM format")})]).optional().describe("Service account JSON (string or object). Omit for ADC on Cloud Run/GKE")}),p=(0,a.zodToSchema)(l),b={};o(b,{cloudRunAdc:()=>m,serviceAccount:()=>k,step:()=>d});var d={};o(d,{prefixScoping:()=>g,readWithAdc:()=>f});var f={description:"Read object from GCS bucket using ADC — no credentials needed",in:{operation:"get",key:"walker.js"},out:{value:"Buffer<(function(){...})()>"}},g={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:{gcsPath:"public/walker.js",value:"Buffer<...>"}},m={settings:{bucket:"my-assets",prefix:"public"}},k={settings:{bucket:"my-assets",prefix:"public",credentials:"$env.GCS_SA_KEY"}};//# sourceMappingURL=dev.js.map
|
package/dist/dev.js.map
ADDED
|
@@ -0,0 +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 description: 'Read object from GCS bucket using ADC — no credentials needed',\n in: { operation: 'get', key: 'walker.js' },\n out: { value: 'Buffer<(function(){...})()>' },\n};\n\n/** Key is scoped under the configured prefix subdirectory. */\nexport const prefixScoping: Flow.StepExample = {\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: { gcsPath: 'public/walker.js', value: '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,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,EAAE,OAAO,8BAA8B;AAC9C;AAGO,IAAM,gBAAkC;AAAA,EAC7C,aACE;AAAA,EACF,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,UAAU,EAAE,QAAQ,aAAa,QAAQ,SAAS;AAAA,EACpD;AAAA,EACA,KAAK,EAAE,SAAS,oBAAoB,OAAO,cAAc;AAC3D;;;ADhBO,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"]}
|
package/dist/dev.mjs
ADDED
|
@@ -0,0 +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:()=>b,step:()=>a});var a={};t(a,{prefixScoping:()=>l,readWithAdc:()=>p});var p={description:"Read object from GCS bucket using ADC — no credentials needed",in:{operation:"get",key:"walker.js"},out:{value:"Buffer<(function(){...})()>"}},l={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:{gcsPath:"public/walker.js",value:"Buffer<...>"}},u={settings:{bucket:"my-assets",prefix:"public"}},b={settings:{bucket:"my-assets",prefix:"public",credentials:"$env.GCS_SA_KEY"}};export{n as examples,i as schemas};//# sourceMappingURL=dev.mjs.map
|
package/dist/dev.mjs.map
ADDED
|
@@ -0,0 +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 description: 'Read object from GCS bucket using ADC — no credentials needed',\n in: { operation: 'get', key: 'walker.js' },\n out: { value: 'Buffer<(function(){...})()>' },\n};\n\n/** Key is scoped under the configured prefix subdirectory. */\nexport const prefixScoping: Flow.StepExample = {\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: { gcsPath: 'public/walker.js', value: '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,aAAa;AAAA,EACb,IAAI,EAAE,WAAW,OAAO,KAAK,YAAY;AAAA,EACzC,KAAK,EAAE,OAAO,8BAA8B;AAC9C;AAGO,IAAM,gBAAkC;AAAA,EAC7C,aACE;AAAA,EACF,IAAI;AAAA,IACF,WAAW;AAAA,IACX,KAAK;AAAA,IACL,UAAU,EAAE,QAAQ,aAAa,QAAQ,SAAS;AAAA,EACpD;AAAA,EACA,KAAK,EAAE,SAAS,oBAAoB,OAAO,cAAc;AAC3D;;;ADhBO,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":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Store } from '@walkeros/core';
|
|
2
|
+
|
|
3
|
+
interface GcsStoreSettings {
|
|
4
|
+
/** GCS bucket name */
|
|
5
|
+
bucket: string;
|
|
6
|
+
/**
|
|
7
|
+
* Key prefix prepended to all store keys.
|
|
8
|
+
* No leading/trailing slash needed — normalized automatically.
|
|
9
|
+
*
|
|
10
|
+
* @example "public/assets" → get("walker.js") looks up "public/assets/walker.js"
|
|
11
|
+
*/
|
|
12
|
+
prefix?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Service account credentials for non-GCP environments.
|
|
15
|
+
* Pass a JSON string (e.g., from $env.GCS_SA_KEY) or the parsed object.
|
|
16
|
+
* When omitted, uses Application Default Credentials (ADC) via the metadata server.
|
|
17
|
+
*/
|
|
18
|
+
credentials?: string | ServiceAccountCredentials;
|
|
19
|
+
}
|
|
20
|
+
interface ServiceAccountCredentials {
|
|
21
|
+
/** Service account email (from SA JSON `client_email` field) */
|
|
22
|
+
client_email: string;
|
|
23
|
+
/** RSA private key in PEM format (from SA JSON `private_key` field) */
|
|
24
|
+
private_key: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>>;
|
|
28
|
+
|
|
29
|
+
export { type GcsStoreSettings, type ServiceAccountCredentials, storeGcsInit as default, storeGcsInit };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Store } from '@walkeros/core';
|
|
2
|
+
|
|
3
|
+
interface GcsStoreSettings {
|
|
4
|
+
/** GCS bucket name */
|
|
5
|
+
bucket: string;
|
|
6
|
+
/**
|
|
7
|
+
* Key prefix prepended to all store keys.
|
|
8
|
+
* No leading/trailing slash needed — normalized automatically.
|
|
9
|
+
*
|
|
10
|
+
* @example "public/assets" → get("walker.js") looks up "public/assets/walker.js"
|
|
11
|
+
*/
|
|
12
|
+
prefix?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Service account credentials for non-GCP environments.
|
|
15
|
+
* Pass a JSON string (e.g., from $env.GCS_SA_KEY) or the parsed object.
|
|
16
|
+
* When omitted, uses Application Default Credentials (ADC) via the metadata server.
|
|
17
|
+
*/
|
|
18
|
+
credentials?: string | ServiceAccountCredentials;
|
|
19
|
+
}
|
|
20
|
+
interface ServiceAccountCredentials {
|
|
21
|
+
/** Service account email (from SA JSON `client_email` field) */
|
|
22
|
+
client_email: string;
|
|
23
|
+
/** RSA private key in PEM format (from SA JSON `private_key` field) */
|
|
24
|
+
private_key: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>>;
|
|
28
|
+
|
|
29
|
+
export { type GcsStoreSettings, type ServiceAccountCredentials, storeGcsInit as default, storeGcsInit };
|
package/dist/index.js
ADDED
|
@@ -0,0 +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(e){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(e){}}}};//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,yBAA2B;AAK3B,IAAM,eACJ;AACF,IAAM,YAAY;AAClB,IAAM,QAAQ;AACd,IAAM,oBAAoB;AAOnB,SAAS,oBACd,aACe;AACf,MAAI;AAEJ,SAAO,eAAe,WAA4B;AAChD,QAAI,UAAU,KAAK,IAAI,IAAI,OAAO,UAAW,QAAO,OAAO;AAE3D,aAAS,cACL,MAAM,yBAAyB,WAAW,IAC1C,MAAM,mBAAmB;AAE7B,WAAO,OAAO;AAAA,EAChB;AACF;AAEA,eAAe,qBAA2C;AACxD,QAAM,MAAM,MAAM,MAAM,cAAc;AAAA,IACpC,SAAS,EAAE,mBAAmB,SAAS;AAAA,IACvC,QAAQ,YAAY,QAAQ,GAAI;AAAA,EAClC,CAAC;AACD,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,0BAA0B,IAAI,MAAM,EAAE;AAEnE,QAAM,OAAQ,MAAM,IAAI,KAAK;AAI7B,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,WAAW,KAAK,IAAI,IAAI,KAAK,aAAa,MAAO;AAAA,EACnD;AACF;AAEA,eAAe,yBACb,OACsB;AACtB,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAM,MAAM;AAAA,IACV;AAAA,MACE,KAAK,MAAM;AAAA,MACX,OAAO;AAAA,MACP,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,MAAM;AAAA,IACb;AAAA,IACA,MAAM;AAAA,EACR;AAEA,QAAM,MAAM,MAAM,MAAM,WAAW;AAAA,IACjC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,IAC/D,MAAM,oEAAoE,GAAG;AAAA,EAC/E,CAAC;AACD,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,yBAAyB,IAAI,MAAM,EAAE;AAElE,QAAM,OAAQ,MAAM,IAAI,KAAK;AAI7B,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,WAAW,KAAK,IAAI,IAAI,KAAK,aAAa,MAAO;AAAA,EACnD;AACF;AAEA,SAAS,UAAU,OAAgC;AACjD,QAAM,MAAM,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI;AAC7D,SAAO,IAAI,SAAS,WAAW;AACjC;AAEA,SAAS,QAAQ,SAAiB,YAA4B;AAC5D,QAAM,SAAS,UAAU,KAAK,UAAU,EAAE,KAAK,SAAS,KAAK,MAAM,CAAC,CAAC;AACrE,QAAM,OAAO,UAAU,KAAK,UAAU,OAAO,CAAC;AAC9C,QAAM,WAAO,+BAAW,YAAY;AACpC,OAAK,OAAO,GAAG,MAAM,IAAI,IAAI,EAAE;AAC/B,SAAO,GAAG,MAAM,IAAI,IAAI,IAAI,KAAK,KAAK,YAAY,WAAW,CAAC;AAChE;;;ACxFA,IAAM,WAAW;AAEjB,SAAS,WAAW,KAAsB;AACxC,MAAI,CAAC,OAAO,IAAI,WAAW,GAAG,KAAK,IAAI,WAAW,IAAI,EAAG,QAAO;AAChE,SAAO,CAAC,IAAI,MAAM,OAAO,EAAE,SAAS,IAAI;AAC1C;AAEA,SAAS,gBAAgB,QAAyB;AAChD,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,OAAO,QAAQ,cAAc,EAAE;AAC/C,SAAO,UAAU,UAAU,MAAM;AACnC;AAEA,SAAS,iBACP,aACuC;AACvC,MAAI,CAAC,YAAa,QAAO;AACzB,MAAI,OAAO,gBAAgB,SAAU,QAAO,KAAK,MAAM,WAAW;AAClE,SAAO;AACT;AAEO,IAAM,eAA0D,CACrE,YACG;AACH,QAAM,WAAW,QAAQ,OAAO;AAChC,QAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,QAAM,QAAQ,iBAAiB,SAAS,WAAW;AACnD,QAAM,WAAW,oBAAoB,KAAK;AAC1C,QAAM,SAAS,mBAAmB,SAAS,MAAM;AAEjD,WAAS,WAAW,KAAiC;AACnD,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,cAAQ,OAAO,KAAK,wBAAwB,EAAE,IAAI,CAAC;AACnD,aAAO;AAAA,IACT;AACA,WAAO,SAAS;AAAA,EAClB;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,QAAQ;AAAA,IAEhB,MAAM,IAAI,KAA0C;AAClD,YAAM,SAAS,WAAW,GAAG;AAC7B,UAAI,CAAC,OAAQ,QAAO;AAEpB,UAAI;AACF,cAAM,QAAQ,MAAM,SAAS;AAC7B,cAAM,MAAM,GAAG,QAAQ,0BAA0B,MAAM,MAAM,mBAAmB,MAAM,CAAC;AACvF,cAAM,MAAM,MAAM,MAAM,KAAK;AAAA,UAC3B,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,QAC9C,CAAC;AACD,YAAI,CAAC,IAAI,GAAI,QAAO;AACpB,eAAO,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC;AAAA,MAC5C,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,MAAM,IAAI,KAAa,OAA+B;AACpD,YAAM,SAAS,WAAW,GAAG;AAC7B,UAAI,CAAC,OAAQ;AAEb,YAAM,QAAQ,MAAM,SAAS;AAC7B,YAAM,MAAM,GAAG,QAAQ,wBAAwB,MAAM,4BAA4B,mBAAmB,MAAM,CAAC;AAC3G,YAAM,MAAM,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,eAAe,UAAU,KAAK;AAAA,UAC9B,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,IAAI,WAAW,KAAe;AAAA,MACtC,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,OAAO,KAA4B;AACvC,YAAM,SAAS,WAAW,GAAG;AAC7B,UAAI,CAAC,OAAQ;AAEb,UAAI;AACF,cAAM,QAAQ,MAAM,SAAS;AAC7B,cAAM,MAAM,GAAG,QAAQ,iBAAiB,MAAM,MAAM,mBAAmB,MAAM,CAAC;AAC9E,cAAM,MAAM,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,QAC9C,CAAC;AAAA,MACH,SAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +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(t){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(t){}}}};export{c as default,c as storeGcsInit};//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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":[]}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$meta": {
|
|
3
|
+
"package": "@walkeros/server-store-gcs",
|
|
4
|
+
"version": "2.1.0",
|
|
5
|
+
"type": "store",
|
|
6
|
+
"platform": "server",
|
|
7
|
+
"docs": "https://www.walkeros.io/docs/stores/server/gcs",
|
|
8
|
+
"source": "https://github.com/elbwalker/walkerOS/tree/main/packages/server/stores/gcs/src"
|
|
9
|
+
},
|
|
10
|
+
"schemas": {
|
|
11
|
+
"settings": {
|
|
12
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"bucket": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1,
|
|
18
|
+
"description": "GCS bucket name"
|
|
19
|
+
},
|
|
20
|
+
"prefix": {
|
|
21
|
+
"description": "Key prefix prepended to all store keys for scoping",
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
"credentials": {
|
|
25
|
+
"description": "Service account JSON (string or object). Omit for ADC on Cloud Run/GKE",
|
|
26
|
+
"anyOf": [
|
|
27
|
+
{
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"client_email": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Service account email"
|
|
36
|
+
},
|
|
37
|
+
"private_key": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "RSA private key in PEM format"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"required": [
|
|
43
|
+
"client_email",
|
|
44
|
+
"private_key"
|
|
45
|
+
],
|
|
46
|
+
"additionalProperties": false
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"required": [
|
|
52
|
+
"bucket"
|
|
53
|
+
],
|
|
54
|
+
"additionalProperties": false
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"examples": {
|
|
58
|
+
"cloudRunAdc": {
|
|
59
|
+
"settings": {
|
|
60
|
+
"bucket": "my-assets",
|
|
61
|
+
"prefix": "public"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"serviceAccount": {
|
|
65
|
+
"settings": {
|
|
66
|
+
"bucket": "my-assets",
|
|
67
|
+
"prefix": "public",
|
|
68
|
+
"credentials": "$env.GCS_SA_KEY"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"step": {
|
|
72
|
+
"prefixScoping": {
|
|
73
|
+
"description": "Key \"walker.js\" with prefix \"public/\" resolves to GCS path \"public/walker.js\"",
|
|
74
|
+
"in": {
|
|
75
|
+
"operation": "get",
|
|
76
|
+
"key": "walker.js",
|
|
77
|
+
"settings": {
|
|
78
|
+
"bucket": "my-assets",
|
|
79
|
+
"prefix": "public"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"out": {
|
|
83
|
+
"gcsPath": "public/walker.js",
|
|
84
|
+
"value": "Buffer<...>"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"readWithAdc": {
|
|
88
|
+
"description": "Read object from GCS bucket using ADC — no credentials needed",
|
|
89
|
+
"in": {
|
|
90
|
+
"operation": "get",
|
|
91
|
+
"key": "walker.js"
|
|
92
|
+
},
|
|
93
|
+
"out": {
|
|
94
|
+
"value": "Buffer<(function(){...})()>"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@walkeros/server-store-gcs",
|
|
3
|
+
"description": "Google Cloud Storage for walkerOS server flows",
|
|
4
|
+
"version": "3.0.0-next-1773236214827",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./dev": {
|
|
16
|
+
"types": "./dist/dev.d.ts",
|
|
17
|
+
"import": "./dist/dev.mjs",
|
|
18
|
+
"require": "./dist/dev.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist/**"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup --silent",
|
|
26
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
27
|
+
"dev": "jest --watchAll --colors",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"lint": "eslint \"**/*.ts*\"",
|
|
30
|
+
"test": "jest",
|
|
31
|
+
"update": "npx npm-check-updates -u && npm update"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@walkeros/core": "3.0.0-next-1773236214827"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@walkeros/core": "3.0.0-next-1773236214827"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"url": "git+https://github.com/elbwalker/walkerOS.git",
|
|
41
|
+
"directory": "packages/server/stores/gcs"
|
|
42
|
+
},
|
|
43
|
+
"author": "elbwalker <hello@elbwalker.com>",
|
|
44
|
+
"homepage": "https://github.com/elbwalker/walkerOS#readme",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/elbwalker/walkerOS/issues"
|
|
47
|
+
},
|
|
48
|
+
"walkerOS": {
|
|
49
|
+
"type": "store",
|
|
50
|
+
"platform": "server",
|
|
51
|
+
"docs": "https://www.walkeros.io/docs/stores/server/gcs"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"walkerOS",
|
|
55
|
+
"store",
|
|
56
|
+
"gcs",
|
|
57
|
+
"google-cloud-storage",
|
|
58
|
+
"server"
|
|
59
|
+
],
|
|
60
|
+
"funding": [
|
|
61
|
+
{
|
|
62
|
+
"type": "GitHub Sponsors",
|
|
63
|
+
"url": "https://github.com/sponsors/elbwalker"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|