@walkeros/server-store-sheets 4.7.0-next-1790245733116 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -13
- package/dist/dev.d.mts +25 -2
- package/dist/dev.d.ts +25 -2
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/dev.mjs +1 -1
- package/dist/dev.mjs.map +1 -1
- package/dist/index.d.mts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/walkerOS.json +15 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
# @walkeros/server-store-sheets
|
|
2
2
|
|
|
3
|
-
## 4.7.0
|
|
3
|
+
## 4.7.0
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- 74821ed: Simulate records every vendor call through one recorder, `observeEnv`
|
|
8
|
+
from `@walkeros/core`, and runs offline: BigQuery, Data Manager and the
|
|
9
|
+
Sheets, GCS and S3 stores reach the network through `env`. Client and `Env`
|
|
10
|
+
types of BigQuery, Firehose, PostHog, SQS and Pub/Sub are structural.
|
|
11
|
+
`@walkeros/cli` exports `resolveExportName` and `selectDevExamples` and drops
|
|
12
|
+
`findExample` and `compareOutput`.
|
|
13
|
+
- Updated dependencies [74821ed]
|
|
8
14
|
- Updated dependencies [06b498a]
|
|
9
|
-
- Updated dependencies [
|
|
10
|
-
- Updated dependencies [
|
|
15
|
+
- Updated dependencies [74821ed]
|
|
16
|
+
- Updated dependencies [64b06de]
|
|
17
|
+
- Updated dependencies [4b4937f]
|
|
18
|
+
- Updated dependencies [74821ed]
|
|
19
|
+
- Updated dependencies [74821ed]
|
|
20
|
+
- Updated dependencies [74821ed]
|
|
11
21
|
- Updated dependencies [06b498a]
|
|
12
|
-
- Updated dependencies [
|
|
13
|
-
- Updated dependencies [
|
|
14
|
-
- Updated dependencies [
|
|
15
|
-
- Updated dependencies [
|
|
16
|
-
- Updated dependencies [
|
|
17
|
-
- Updated dependencies [
|
|
18
|
-
- Updated dependencies [
|
|
19
|
-
- Updated dependencies [
|
|
20
|
-
|
|
22
|
+
- Updated dependencies [74821ed]
|
|
23
|
+
- Updated dependencies [74821ed]
|
|
24
|
+
- Updated dependencies [74821ed]
|
|
25
|
+
- Updated dependencies [74821ed]
|
|
26
|
+
- Updated dependencies [74821ed]
|
|
27
|
+
- Updated dependencies [e860006]
|
|
28
|
+
- Updated dependencies [74821ed]
|
|
29
|
+
- Updated dependencies [74821ed]
|
|
30
|
+
- Updated dependencies [4f89234]
|
|
31
|
+
- @walkeros/core@4.7.0
|
|
21
32
|
|
|
22
33
|
## 4.6.1
|
|
23
34
|
|
package/dist/dev.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _walkeros_core_dev from '@walkeros/core/dev';
|
|
2
2
|
import { z } from '@walkeros/core/dev';
|
|
3
|
-
import {
|
|
3
|
+
import { Store, Flow, Hint } from '@walkeros/core';
|
|
4
4
|
|
|
5
5
|
declare const SettingsSchema: z.ZodObject<{
|
|
6
6
|
id: z.ZodString;
|
|
@@ -31,6 +31,28 @@ declare namespace index$1 {
|
|
|
31
31
|
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 };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Injected dependencies (tests, simulate). Every Sheets and token request
|
|
36
|
+
* goes through `fetch`; without it the global `fetch` is used.
|
|
37
|
+
*/
|
|
38
|
+
interface Env extends Store.BaseEnv {
|
|
39
|
+
fetch?: typeof fetch;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Create a fresh in-memory Sheets backend, seeded with `rows` from row 2. */
|
|
43
|
+
declare function createSheetsFetch(rows?: Array<[string, string]>): typeof fetch;
|
|
44
|
+
/** Each read of `fetch` is a fresh backend, so runs never share writes. */
|
|
45
|
+
declare const push: Env;
|
|
46
|
+
/** Every request goes through `fetch`; `args[0]` is the URL. */
|
|
47
|
+
declare const simulation: string[];
|
|
48
|
+
|
|
49
|
+
declare const env_createSheetsFetch: typeof createSheetsFetch;
|
|
50
|
+
declare const env_push: typeof push;
|
|
51
|
+
declare const env_simulation: typeof simulation;
|
|
52
|
+
declare namespace env {
|
|
53
|
+
export { env_createSheetsFetch as createSheetsFetch, env_push as push, env_simulation as simulation };
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
/** Read a JSON value with ADC (Cloud Run / GKE service account). */
|
|
35
57
|
declare const readWithAdc: Flow.StepExample;
|
|
36
58
|
/** Write a JSON value with an explicit service account JSON. */
|
|
@@ -48,10 +70,11 @@ declare const cloudRunAdc: Store.Config;
|
|
|
48
70
|
declare const serviceAccount: Store.Config;
|
|
49
71
|
|
|
50
72
|
declare const index_cloudRunAdc: typeof cloudRunAdc;
|
|
73
|
+
declare const index_env: typeof env;
|
|
51
74
|
declare const index_serviceAccount: typeof serviceAccount;
|
|
52
75
|
declare const index_step: typeof step;
|
|
53
76
|
declare namespace index {
|
|
54
|
-
export { index_cloudRunAdc as cloudRunAdc, index_serviceAccount as serviceAccount, index_step as step };
|
|
77
|
+
export { index_cloudRunAdc as cloudRunAdc, index_env as env, index_serviceAccount as serviceAccount, index_step as step };
|
|
55
78
|
}
|
|
56
79
|
|
|
57
80
|
declare const hints: Hint.Hints;
|
package/dist/dev.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _walkeros_core_dev from '@walkeros/core/dev';
|
|
2
2
|
import { z } from '@walkeros/core/dev';
|
|
3
|
-
import {
|
|
3
|
+
import { Store, Flow, Hint } from '@walkeros/core';
|
|
4
4
|
|
|
5
5
|
declare const SettingsSchema: z.ZodObject<{
|
|
6
6
|
id: z.ZodString;
|
|
@@ -31,6 +31,28 @@ declare namespace index$1 {
|
|
|
31
31
|
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 };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Injected dependencies (tests, simulate). Every Sheets and token request
|
|
36
|
+
* goes through `fetch`; without it the global `fetch` is used.
|
|
37
|
+
*/
|
|
38
|
+
interface Env extends Store.BaseEnv {
|
|
39
|
+
fetch?: typeof fetch;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Create a fresh in-memory Sheets backend, seeded with `rows` from row 2. */
|
|
43
|
+
declare function createSheetsFetch(rows?: Array<[string, string]>): typeof fetch;
|
|
44
|
+
/** Each read of `fetch` is a fresh backend, so runs never share writes. */
|
|
45
|
+
declare const push: Env;
|
|
46
|
+
/** Every request goes through `fetch`; `args[0]` is the URL. */
|
|
47
|
+
declare const simulation: string[];
|
|
48
|
+
|
|
49
|
+
declare const env_createSheetsFetch: typeof createSheetsFetch;
|
|
50
|
+
declare const env_push: typeof push;
|
|
51
|
+
declare const env_simulation: typeof simulation;
|
|
52
|
+
declare namespace env {
|
|
53
|
+
export { env_createSheetsFetch as createSheetsFetch, env_push as push, env_simulation as simulation };
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
/** Read a JSON value with ADC (Cloud Run / GKE service account). */
|
|
35
57
|
declare const readWithAdc: Flow.StepExample;
|
|
36
58
|
/** Write a JSON value with an explicit service account JSON. */
|
|
@@ -48,10 +70,11 @@ declare const cloudRunAdc: Store.Config;
|
|
|
48
70
|
declare const serviceAccount: Store.Config;
|
|
49
71
|
|
|
50
72
|
declare const index_cloudRunAdc: typeof cloudRunAdc;
|
|
73
|
+
declare const index_env: typeof env;
|
|
51
74
|
declare const index_serviceAccount: typeof serviceAccount;
|
|
52
75
|
declare const index_step: typeof step;
|
|
53
76
|
declare namespace index {
|
|
54
|
-
export { index_cloudRunAdc as cloudRunAdc, index_serviceAccount as serviceAccount, index_step as step };
|
|
77
|
+
export { index_cloudRunAdc as cloudRunAdc, index_env as env, index_serviceAccount as serviceAccount, index_step as step };
|
|
55
78
|
}
|
|
56
79
|
|
|
57
80
|
declare const hints: Hint.Hints;
|
package/dist/dev.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,
|
|
1
|
+
"use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,s=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,o=(e,r)=>{for(var s in r)t(e,s,{get:r[s],enumerable:!0})},i={};o(i,{examples:()=>g,hints:()=>x,schemas:()=>a}),module.exports=(e=i,((e,o,i,a)=>{if(o&&"object"==typeof o||"function"==typeof o)for(let c of s(o))n.call(e,c)||c===i||t(e,c,{get:()=>o[c],enumerable:!(a=r(o,c))||a.enumerable});return e})(t({},"__esModule",{value:!0}),e));var a={};o(a,{SettingsSchema:()=>d,SetupSchema:()=>h,settings:()=>p,setup:()=>f});var c=require("@walkeros/core/dev"),u=require("@walkeros/core/dev"),d=u.z.object({id:u.z.string().min(1).describe("Spreadsheet ID, the segment between /d/ and /edit in the URL"),sheet:u.z.string().default("Sheet1").describe("Sheet (tab) name within the spreadsheet"),key:u.z.string().default("A").describe("Column letter for keys (the lookup column)"),value:u.z.string().default("B").describe("Column letter for values (JSON-serialized blob)"),headerRows:u.z.number().int().min(0).default(1).describe("Number of header rows to skip when reading the key column"),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 (deprecated: use config.credentials)")}),l=require("@walkeros/core/dev"),h=l.z.object({headers:l.z.array(l.z.string()).optional().describe("Header values to write into row 1 of the configured sheet")}).describe('Provisioning options for "walkeros setup store.<id>". Idempotent: re-running with the same headers is a no-op overwrite, never alters existing data rows.'),p=(0,c.zodToSchema)(d),f=(0,c.zodToSchema)(h),g={};o(g,{cloudRunAdc:()=>$,env:()=>b,serviceAccount:()=>j,step:()=>k});var b={};o(b,{createSheetsFetch:()=>S,push:()=>w,simulation:()=>A});var v={access_token:"mock-access-token",expires_in:3600};function m(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}})}function y(e){if("string"!=typeof e)return[];const t=JSON.parse(e);if("object"!=typeof t||null===t)return[];const r=Reflect.get(t,"values");return Array.isArray(r)&&Array.isArray(r[0])?r[0].map(e=>String(e)):[]}function S(e=[["alice",'{"tier":"gold"}']]){const t=new Map([["A1","key"],["B1","value"]]);e.forEach(([e,r],s)=>{t.set(`A${s+2}`,e),t.set(`B${s+2}`,r)});const r=()=>{let e=0;for(const r of t.keys()){const t=Number.parseInt(r.replace(/^[A-Z]+/,""),10);t>e&&(e=t)}return e};return async(e,s)=>{const n=function(e){return"string"==typeof e?e:e instanceof URL?e.toString():e.url}(e),o=s?.method??"GET";if(n.startsWith("https://oauth2.googleapis.com/token"))return m(v);if(n.startsWith("http://metadata.google.internal/"))return m(v);const i=/\/spreadsheets\/([^/?]+)\/values\/([^?]+)/.exec(n);if(!i){const e=/\/spreadsheets\/([^/?]+)\?/.exec(n);return e?m({spreadsheetId:e[1]}):m({error:{code:404}},404)}const a=decodeURIComponent(i[2]),c=/!([A-Z]+):([A-Z]+):append$/.exec(a);if(c&&"POST"===o){const e=r()+1,[n="",o=""]=y(s?.body);return t.set(`${c[1]}${e}`,n),t.set(`${c[2]}${e}`,o),m({updates:{updatedRange:`Sheet1!${c[1]}${e}:${c[2]}${e}`}})}const u=/!([A-Z]+)(\d+):([A-Z]+)$/.exec(a);if(u){const e=[];for(let s=Number(u[2]);s<=r();s++)e.push([t.get(`${u[1]}${s}`)??""]);return m({range:a,values:e})}const d=/!([A-Z]+\d+)$/.exec(a);if(d){if("PUT"===o)return t.set(d[1],y(s?.body)[0]??""),m({updatedRange:a});const e=t.get(d[1]);return m(void 0===e?{range:a}:{range:a,values:[[e]]})}return m({error:{code:400}},400)}}var w={get fetch(){return S()}},A=["fetch"],k={};o(k,{readWithAdc:()=>z,writeWithServiceAccount:()=>O});var z={title:"Read with ADC",description:"Read a value from the Sheets store using ADC, no credentials field needed on Cloud Run or GKE",in:{operation:"get",key:"alice"},out:[["get","alice",'{ tier: "gold" }']]},O={title:"Write with service account",description:"Append or update a JSON value in the configured sheet using an explicit service account JSON",in:{operation:"set",key:"bob",value:{tier:"silver"}},out:[["set","bob",'{ tier: "silver" }']]},$={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"}},j={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"},credentials:"$env.SHEETS_SA_KEY"},x={"structured-only":{text:"The Sheets store is structured-only: each cell holds a structured StoreValue JSON value. There is no byte-native mode, so config.file: true is a hard error at init and a value carrying a binary (Uint8Array) leaf is rejected. For byte-exact asset serving (walker.js and friends) use fs, s3, or gcs with file: true instead. Sheets is also not a cache backing; pair it with a cache wrapper to absorb its rate limit, but the Sheets store itself stores only structured cells."}};//# 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/schemas/setup.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\nexport { hints } from './hints';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\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 id: z\n .string()\n .min(1)\n .describe('Spreadsheet ID, the segment between /d/ and /edit in the URL'),\n sheet: z\n .string()\n .default('Sheet1')\n .describe('Sheet (tab) name within the spreadsheet'),\n key: z\n .string()\n .default('A')\n .describe('Column letter for keys (the lookup column)'),\n value: z\n .string()\n .default('B')\n .describe('Column letter for values (JSON-serialized blob)'),\n headerRows: z\n .number()\n .int()\n .min(0)\n .default(1)\n .describe('Number of header rows to skip when reading the key column'),\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 (deprecated: use config.credentials)',\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 headers: z\n .array(z.string())\n .optional()\n .describe('Header values to write into row 1 of the configured sheet'),\n })\n .describe(\n 'Provisioning options for \"walkeros setup store.<id>\". Idempotent: re-running with the same headers is a no-op overwrite, never alters existing data rows.',\n );\n\nexport type Setup = z.infer<typeof SetupSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Cloud Run with ADC, no credentials field needed. */\nexport const cloudRunAdc: Store.Config = {\n settings: {\n id: '1AbCdEfGhIjKlMnOpQrStUvWxYz',\n sheet: 'Customers',\n },\n};\n\n/** Explicit service account JSON for non-GCP environments. */\nexport const serviceAccount: Store.Config = {\n settings: {\n id: '1AbCdEfGhIjKlMnOpQrStUvWxYz',\n sheet: 'Customers',\n },\n credentials: '$env.SHEETS_SA_KEY',\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a JSON value with ADC (Cloud Run / GKE service account). */\nexport const readWithAdc: Flow.StepExample = {\n title: 'Read with ADC',\n description:\n 'Read a value from the Sheets store using ADC, no credentials field needed on Cloud Run or GKE',\n in: { operation: 'get', key: 'alice' },\n out: [['get', 'alice', '{ tier: \"gold\" }']],\n};\n\n/** Write a JSON value with an explicit service account JSON. */\nexport const writeWithServiceAccount: Flow.StepExample = {\n title: 'Write with service account',\n description:\n 'Append or update a JSON value in the configured sheet using an explicit service account JSON',\n in: { operation: 'set', key: 'bob', value: { tier: 'silver' } },\n out: [['set', 'bob', '{ tier: \"silver\" }']],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'structured-only': {\n text: 'The Sheets store is structured-only: each cell holds a structured StoreValue JSON value. There is no byte-native mode, so config.file: true is a hard error at init and a value carrying a binary (Uint8Array) leaf is rejected. For byte-exact asset serving (walker.js and friends) use fs, s3, or gcs with file: true instead. Sheets is also not a cache backing; pair it with a cache wrapper to absorb its rate limit, but the Sheets store itself stores only structured cells.',\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;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,IAAI,aACD,OAAO,EACP,IAAI,CAAC,EACL,SAAS,8DAA8D;AAAA,EAC1E,OAAO,aACJ,OAAO,EACP,QAAQ,QAAQ,EAChB,SAAS,yCAAyC;AAAA,EACrD,KAAK,aACF,OAAO,EACP,QAAQ,GAAG,EACX,SAAS,4CAA4C;AAAA,EACxD,OAAO,aACJ,OAAO,EACP,QAAQ,GAAG,EACX,SAAS,iDAAiD;AAAA,EAC7D,YAAY,aACT,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,QAAQ,CAAC,EACT,SAAS,2DAA2D;AAAA,EACvE,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;;;ACrCD,IAAAC,cAAkB;AAEX,IAAM,cAAc,cACxB,OAAO;AAAA,EACN,SAAS,cACN,MAAM,cAAE,OAAO,CAAC,EAChB,SAAS,EACT,SAAS,2DAA2D;AACzE,CAAC,EACA;AAAA,EACC;AACF;;;AFJK,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,aACE;AAAA,EACF,IAAI,EAAE,WAAW,OAAO,KAAK,QAAQ;AAAA,EACrC,KAAK,CAAC,CAAC,OAAO,SAAS,kBAAkB,CAAC;AAC5C;AAGO,IAAM,0BAA4C;AAAA,EACvD,OAAO;AAAA,EACP,aACE;AAAA,EACF,IAAI,EAAE,WAAW,OAAO,KAAK,OAAO,OAAO,EAAE,MAAM,SAAS,EAAE;AAAA,EAC9D,KAAK,CAAC,CAAC,OAAO,OAAO,oBAAoB,CAAC;AAC5C;;;ADfO,IAAM,cAA4B;AAAA,EACvC,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,EACT;AACF;AAGO,IAAM,iBAA+B;AAAA,EAC1C,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,EACT;AAAA,EACA,aAAa;AACf;;;AEfO,IAAM,QAAoB;AAAA,EAC/B,mBAAmB;AAAA,IACjB,MAAM;AAAA,EACR;AACF;","names":["import_dev","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/env.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\nexport { hints } from './hints';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\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 id: z\n .string()\n .min(1)\n .describe('Spreadsheet ID, the segment between /d/ and /edit in the URL'),\n sheet: z\n .string()\n .default('Sheet1')\n .describe('Sheet (tab) name within the spreadsheet'),\n key: z\n .string()\n .default('A')\n .describe('Column letter for keys (the lookup column)'),\n value: z\n .string()\n .default('B')\n .describe('Column letter for values (JSON-serialized blob)'),\n headerRows: z\n .number()\n .int()\n .min(0)\n .default(1)\n .describe('Number of header rows to skip when reading the key column'),\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 (deprecated: use config.credentials)',\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 headers: z\n .array(z.string())\n .optional()\n .describe('Header values to write into row 1 of the configured sheet'),\n })\n .describe(\n 'Provisioning options for \"walkeros setup store.<id>\". Idempotent: re-running with the same headers is a no-op overwrite, never alters existing data rows.',\n );\n\nexport type Setup = z.infer<typeof SetupSchema>;\n","import type { Store } from '@walkeros/core';\n\nexport * as env from './env';\n\n/** Cloud Run with ADC, no credentials field needed. */\nexport const cloudRunAdc: Store.Config = {\n settings: {\n id: '1AbCdEfGhIjKlMnOpQrStUvWxYz',\n sheet: 'Customers',\n },\n};\n\n/** Explicit service account JSON for non-GCP environments. */\nexport const serviceAccount: Store.Config = {\n settings: {\n id: '1AbCdEfGhIjKlMnOpQrStUvWxYz',\n sheet: 'Customers',\n },\n credentials: '$env.SHEETS_SA_KEY',\n};\n\nexport * as step from './step';\n","import type { Env } from '../types';\n\n/**\n * Mock env for the Sheets store: an in-memory spreadsheet behind `fetch`.\n *\n * It answers every request the store makes, at start and per operation: the\n * token exchange (service account) or metadata token (ADC), the spreadsheet\n * existence check, the key column read, cell reads, appends and cell writes.\n * Values live in a `Map` per cell, so a `get` after a `set` returns what was\n * written. One sheet is modelled; the spreadsheet id and sheet name are not\n * checked. Row 1 is the header row, row 2 holds the seeded `alice` entry.\n */\n\nconst TOKEN_BODY = { access_token: 'mock-access-token', expires_in: 3600 };\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n\nfunction requestUrl(input: Parameters<typeof fetch>[0]): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n return input.url;\n}\n\nfunction firstCell(body: unknown): string[] {\n if (typeof body !== 'string') return [];\n const parsed: unknown = JSON.parse(body);\n if (typeof parsed !== 'object' || parsed === null) return [];\n const values: unknown = Reflect.get(parsed, 'values');\n if (!Array.isArray(values) || !Array.isArray(values[0])) return [];\n return values[0].map((cell: unknown) => String(cell));\n}\n\n/** Create a fresh in-memory Sheets backend, seeded with `rows` from row 2. */\nexport function createSheetsFetch(\n rows: Array<[string, string]> = [['alice', '{\"tier\":\"gold\"}']],\n): typeof fetch {\n const cells = new Map<string, string>([\n ['A1', 'key'],\n ['B1', 'value'],\n ]);\n rows.forEach(([key, value], i) => {\n cells.set(`A${i + 2}`, key);\n cells.set(`B${i + 2}`, value);\n });\n\n const lastRow = (): number => {\n let max = 0;\n for (const ref of cells.keys()) {\n const row = Number.parseInt(ref.replace(/^[A-Z]+/, ''), 10);\n if (row > max) max = row;\n }\n return max;\n };\n\n return async (input, init) => {\n const url = requestUrl(input);\n const method = init?.method ?? 'GET';\n\n if (url.startsWith('https://oauth2.googleapis.com/token'))\n return jsonResponse(TOKEN_BODY);\n if (url.startsWith('http://metadata.google.internal/'))\n return jsonResponse(TOKEN_BODY);\n\n const values = /\\/spreadsheets\\/([^/?]+)\\/values\\/([^?]+)/.exec(url);\n if (!values) {\n const sheet = /\\/spreadsheets\\/([^/?]+)\\?/.exec(url);\n if (sheet) return jsonResponse({ spreadsheetId: sheet[1] });\n return jsonResponse({ error: { code: 404 } }, 404);\n }\n\n const range = decodeURIComponent(values[2]);\n\n // Append across the key and value columns, e.g. `Sheet1!A:B:append`.\n const append = /!([A-Z]+):([A-Z]+):append$/.exec(range);\n if (append && method === 'POST') {\n const row = lastRow() + 1;\n const [key = '', value = ''] = firstCell(init?.body);\n cells.set(`${append[1]}${row}`, key);\n cells.set(`${append[2]}${row}`, value);\n return jsonResponse({\n updates: {\n updatedRange: `Sheet1!${append[1]}${row}:${append[2]}${row}`,\n },\n });\n }\n\n // Whole column from a row, e.g. `Sheet1!A2:A`.\n const column = /!([A-Z]+)(\\d+):([A-Z]+)$/.exec(range);\n if (column) {\n const out: string[][] = [];\n for (let row = Number(column[2]); row <= lastRow(); row++) {\n out.push([cells.get(`${column[1]}${row}`) ?? '']);\n }\n return jsonResponse({ range, values: out });\n }\n\n // Single cell, e.g. `Sheet1!B5`.\n const cell = /!([A-Z]+\\d+)$/.exec(range);\n if (cell) {\n if (method === 'PUT') {\n cells.set(cell[1], firstCell(init?.body)[0] ?? '');\n return jsonResponse({ updatedRange: range });\n }\n const value = cells.get(cell[1]);\n return jsonResponse(\n value === undefined ? { range } : { range, values: [[value]] },\n );\n }\n\n return jsonResponse({ error: { code: 400 } }, 400);\n };\n}\n\n/** Each read of `fetch` is a fresh backend, so runs never share writes. */\nexport const push: Env = {\n get fetch() {\n return createSheetsFetch();\n },\n};\n\n/** Every request goes through `fetch`; `args[0]` is the URL. */\nexport const simulation = ['fetch'];\n","import type { Flow } from '@walkeros/core';\n\n/** Read a JSON value with ADC (Cloud Run / GKE service account). */\nexport const readWithAdc: Flow.StepExample = {\n title: 'Read with ADC',\n description:\n 'Read a value from the Sheets store using ADC, no credentials field needed on Cloud Run or GKE',\n in: { operation: 'get', key: 'alice' },\n out: [['get', 'alice', '{ tier: \"gold\" }']],\n};\n\n/** Write a JSON value with an explicit service account JSON. */\nexport const writeWithServiceAccount: Flow.StepExample = {\n title: 'Write with service account',\n description:\n 'Append or update a JSON value in the configured sheet using an explicit service account JSON',\n in: { operation: 'set', key: 'bob', value: { tier: 'silver' } },\n out: [['set', 'bob', '{ tier: \"silver\" }']],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'structured-only': {\n text: 'The Sheets store is structured-only: each cell holds a structured StoreValue JSON value. There is no byte-native mode, so config.file: true is a hard error at init and a value carrying a binary (Uint8Array) leaf is rejected. For byte-exact asset serving (walker.js and friends) use fs, s3, or gcs with file: true instead. Sheets is also not a cache backing; pair it with a cache wrapper to absorb its rate limit, but the Sheets store itself stores only structured cells.',\n },\n};\n"],"mappings":"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","names":["import_dev","import_dev"]}
|
package/dist/dev.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=Object.defineProperty,t=(t,r)=>{for(var
|
|
1
|
+
var e=Object.defineProperty,t=(t,r)=>{for(var s in r)e(t,s,{get:r[s],enumerable:!0})},r={};t(r,{SettingsSchema:()=>i,SetupSchema:()=>a,settings:()=>c,setup:()=>d});import{zodToSchema as s}from"@walkeros/core/dev";import{z as n}from"@walkeros/core/dev";var i=n.object({id:n.string().min(1).describe("Spreadsheet ID, the segment between /d/ and /edit in the URL"),sheet:n.string().default("Sheet1").describe("Sheet (tab) name within the spreadsheet"),key:n.string().default("A").describe("Column letter for keys (the lookup column)"),value:n.string().default("B").describe("Column letter for values (JSON-serialized blob)"),headerRows:n.number().int().min(0).default(1).describe("Number of header rows to skip when reading the key column"),credentials:n.union([n.string(),n.object({client_email:n.string().describe("Service account email"),private_key:n.string().describe("RSA private key in PEM format")})]).optional().describe("Service account JSON (string or object). Omit for ADC on Cloud Run/GKE (deprecated: use config.credentials)")});import{z as o}from"@walkeros/core/dev";var a=o.object({headers:o.array(o.string()).optional().describe("Header values to write into row 1 of the configured sheet")}).describe('Provisioning options for "walkeros setup store.<id>". Idempotent: re-running with the same headers is a no-op overwrite, never alters existing data rows.'),c=s(i),d=s(a),u={};t(u,{cloudRunAdc:()=>A,env:()=>l,serviceAccount:()=>w,step:()=>b});var l={};t(l,{createSheetsFetch:()=>g,push:()=>v,simulation:()=>m});var h={access_token:"mock-access-token",expires_in:3600};function p(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}})}function f(e){if("string"!=typeof e)return[];const t=JSON.parse(e);if("object"!=typeof t||null===t)return[];const r=Reflect.get(t,"values");return Array.isArray(r)&&Array.isArray(r[0])?r[0].map(e=>String(e)):[]}function g(e=[["alice",'{"tier":"gold"}']]){const t=new Map([["A1","key"],["B1","value"]]);e.forEach(([e,r],s)=>{t.set(`A${s+2}`,e),t.set(`B${s+2}`,r)});const r=()=>{let e=0;for(const r of t.keys()){const t=Number.parseInt(r.replace(/^[A-Z]+/,""),10);t>e&&(e=t)}return e};return async(e,s)=>{const n=function(e){return"string"==typeof e?e:e instanceof URL?e.toString():e.url}(e),i=s?.method??"GET";if(n.startsWith("https://oauth2.googleapis.com/token"))return p(h);if(n.startsWith("http://metadata.google.internal/"))return p(h);const o=/\/spreadsheets\/([^/?]+)\/values\/([^?]+)/.exec(n);if(!o){const e=/\/spreadsheets\/([^/?]+)\?/.exec(n);return e?p({spreadsheetId:e[1]}):p({error:{code:404}},404)}const a=decodeURIComponent(o[2]),c=/!([A-Z]+):([A-Z]+):append$/.exec(a);if(c&&"POST"===i){const e=r()+1,[n="",i=""]=f(s?.body);return t.set(`${c[1]}${e}`,n),t.set(`${c[2]}${e}`,i),p({updates:{updatedRange:`Sheet1!${c[1]}${e}:${c[2]}${e}`}})}const d=/!([A-Z]+)(\d+):([A-Z]+)$/.exec(a);if(d){const e=[];for(let s=Number(d[2]);s<=r();s++)e.push([t.get(`${d[1]}${s}`)??""]);return p({range:a,values:e})}const u=/!([A-Z]+\d+)$/.exec(a);if(u){if("PUT"===i)return t.set(u[1],f(s?.body)[0]??""),p({updatedRange:a});const e=t.get(u[1]);return p(void 0===e?{range:a}:{range:a,values:[[e]]})}return p({error:{code:400}},400)}}var v={get fetch(){return g()}},m=["fetch"],b={};t(b,{readWithAdc:()=>S,writeWithServiceAccount:()=>y});var S={title:"Read with ADC",description:"Read a value from the Sheets store using ADC, no credentials field needed on Cloud Run or GKE",in:{operation:"get",key:"alice"},out:[["get","alice",'{ tier: "gold" }']]},y={title:"Write with service account",description:"Append or update a JSON value in the configured sheet using an explicit service account JSON",in:{operation:"set",key:"bob",value:{tier:"silver"}},out:[["set","bob",'{ tier: "silver" }']]},A={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"}},w={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"},credentials:"$env.SHEETS_SA_KEY"},k={"structured-only":{text:"The Sheets store is structured-only: each cell holds a structured StoreValue JSON value. There is no byte-native mode, so config.file: true is a hard error at init and a value carrying a binary (Uint8Array) leaf is rejected. For byte-exact asset serving (walker.js and friends) use fs, s3, or gcs with file: true instead. Sheets is also not a cache backing; pair it with a cache wrapper to absorb its rate limit, but the Sheets store itself stores only structured cells."}};export{u as examples,k as hints,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/schemas/setup.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.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 id: z\n .string()\n .min(1)\n .describe('Spreadsheet ID, the segment between /d/ and /edit in the URL'),\n sheet: z\n .string()\n .default('Sheet1')\n .describe('Sheet (tab) name within the spreadsheet'),\n key: z\n .string()\n .default('A')\n .describe('Column letter for keys (the lookup column)'),\n value: z\n .string()\n .default('B')\n .describe('Column letter for values (JSON-serialized blob)'),\n headerRows: z\n .number()\n .int()\n .min(0)\n .default(1)\n .describe('Number of header rows to skip when reading the key column'),\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 (deprecated: use config.credentials)',\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 headers: z\n .array(z.string())\n .optional()\n .describe('Header values to write into row 1 of the configured sheet'),\n })\n .describe(\n 'Provisioning options for \"walkeros setup store.<id>\". Idempotent: re-running with the same headers is a no-op overwrite, never alters existing data rows.',\n );\n\nexport type Setup = z.infer<typeof SetupSchema>;\n","import type { Store } from '@walkeros/core';\n\n/** Cloud Run with ADC, no credentials field needed. */\nexport const cloudRunAdc: Store.Config = {\n settings: {\n id: '1AbCdEfGhIjKlMnOpQrStUvWxYz',\n sheet: 'Customers',\n },\n};\n\n/** Explicit service account JSON for non-GCP environments. */\nexport const serviceAccount: Store.Config = {\n settings: {\n id: '1AbCdEfGhIjKlMnOpQrStUvWxYz',\n sheet: 'Customers',\n },\n credentials: '$env.SHEETS_SA_KEY',\n};\n\nexport * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\n/** Read a JSON value with ADC (Cloud Run / GKE service account). */\nexport const readWithAdc: Flow.StepExample = {\n title: 'Read with ADC',\n description:\n 'Read a value from the Sheets store using ADC, no credentials field needed on Cloud Run or GKE',\n in: { operation: 'get', key: 'alice' },\n out: [['get', 'alice', '{ tier: \"gold\" }']],\n};\n\n/** Write a JSON value with an explicit service account JSON. */\nexport const writeWithServiceAccount: Flow.StepExample = {\n title: 'Write with service account',\n description:\n 'Append or update a JSON value in the configured sheet using an explicit service account JSON',\n in: { operation: 'set', key: 'bob', value: { tier: 'silver' } },\n out: [['set', 'bob', '{ tier: \"silver\" }']],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'structured-only': {\n text: 'The Sheets store is structured-only: each cell holds a structured StoreValue JSON value. There is no byte-native mode, so config.file: true is a hard error at init and a value carrying a binary (Uint8Array) leaf is rejected. For byte-exact asset serving (walker.js and friends) use fs, s3, or gcs with file: true instead. Sheets is also not a cache backing; pair it with a cache wrapper to absorb its rate limit, but the Sheets store itself stores only structured cells.',\n },\n};\n"],"mappings":";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;;;ACA5B,SAAS,SAAS;AAEX,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,IAAI,EACD,OAAO,EACP,IAAI,CAAC,EACL,SAAS,8DAA8D;AAAA,EAC1E,OAAO,EACJ,OAAO,EACP,QAAQ,QAAQ,EAChB,SAAS,yCAAyC;AAAA,EACrD,KAAK,EACF,OAAO,EACP,QAAQ,GAAG,EACX,SAAS,4CAA4C;AAAA,EACxD,OAAO,EACJ,OAAO,EACP,QAAQ,GAAG,EACX,SAAS,iDAAiD;AAAA,EAC7D,YAAY,EACT,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,QAAQ,CAAC,EACT,SAAS,2DAA2D;AAAA,EACvE,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;;;ACrCD,SAAS,KAAAA,UAAS;AAEX,IAAM,cAAcA,GACxB,OAAO;AAAA,EACN,SAASA,GACN,MAAMA,GAAE,OAAO,CAAC,EAChB,SAAS,EACT,SAAS,2DAA2D;AACzE,CAAC,EACA;AAAA,EACC;AACF;;;AFJK,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,aACE;AAAA,EACF,IAAI,EAAE,WAAW,OAAO,KAAK,QAAQ;AAAA,EACrC,KAAK,CAAC,CAAC,OAAO,SAAS,kBAAkB,CAAC;AAC5C;AAGO,IAAM,0BAA4C;AAAA,EACvD,OAAO;AAAA,EACP,aACE;AAAA,EACF,IAAI,EAAE,WAAW,OAAO,KAAK,OAAO,OAAO,EAAE,MAAM,SAAS,EAAE;AAAA,EAC9D,KAAK,CAAC,CAAC,OAAO,OAAO,oBAAoB,CAAC;AAC5C;;;ADfO,IAAM,cAA4B;AAAA,EACvC,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,EACT;AACF;AAGO,IAAM,iBAA+B;AAAA,EAC1C,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,EACT;AAAA,EACA,aAAa;AACf;;;AEfO,IAAM,QAAoB;AAAA,EAC/B,mBAAmB;AAAA,IACjB,MAAM;AAAA,EACR;AACF;","names":["z"]}
|
|
1
|
+
{"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/schemas/setup.ts","../src/examples/index.ts","../src/examples/env.ts","../src/examples/step.ts","../src/hints.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 id: z\n .string()\n .min(1)\n .describe('Spreadsheet ID, the segment between /d/ and /edit in the URL'),\n sheet: z\n .string()\n .default('Sheet1')\n .describe('Sheet (tab) name within the spreadsheet'),\n key: z\n .string()\n .default('A')\n .describe('Column letter for keys (the lookup column)'),\n value: z\n .string()\n .default('B')\n .describe('Column letter for values (JSON-serialized blob)'),\n headerRows: z\n .number()\n .int()\n .min(0)\n .default(1)\n .describe('Number of header rows to skip when reading the key column'),\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 (deprecated: use config.credentials)',\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 headers: z\n .array(z.string())\n .optional()\n .describe('Header values to write into row 1 of the configured sheet'),\n })\n .describe(\n 'Provisioning options for \"walkeros setup store.<id>\". Idempotent: re-running with the same headers is a no-op overwrite, never alters existing data rows.',\n );\n\nexport type Setup = z.infer<typeof SetupSchema>;\n","import type { Store } from '@walkeros/core';\n\nexport * as env from './env';\n\n/** Cloud Run with ADC, no credentials field needed. */\nexport const cloudRunAdc: Store.Config = {\n settings: {\n id: '1AbCdEfGhIjKlMnOpQrStUvWxYz',\n sheet: 'Customers',\n },\n};\n\n/** Explicit service account JSON for non-GCP environments. */\nexport const serviceAccount: Store.Config = {\n settings: {\n id: '1AbCdEfGhIjKlMnOpQrStUvWxYz',\n sheet: 'Customers',\n },\n credentials: '$env.SHEETS_SA_KEY',\n};\n\nexport * as step from './step';\n","import type { Env } from '../types';\n\n/**\n * Mock env for the Sheets store: an in-memory spreadsheet behind `fetch`.\n *\n * It answers every request the store makes, at start and per operation: the\n * token exchange (service account) or metadata token (ADC), the spreadsheet\n * existence check, the key column read, cell reads, appends and cell writes.\n * Values live in a `Map` per cell, so a `get` after a `set` returns what was\n * written. One sheet is modelled; the spreadsheet id and sheet name are not\n * checked. Row 1 is the header row, row 2 holds the seeded `alice` entry.\n */\n\nconst TOKEN_BODY = { access_token: 'mock-access-token', expires_in: 3600 };\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n\nfunction requestUrl(input: Parameters<typeof fetch>[0]): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n return input.url;\n}\n\nfunction firstCell(body: unknown): string[] {\n if (typeof body !== 'string') return [];\n const parsed: unknown = JSON.parse(body);\n if (typeof parsed !== 'object' || parsed === null) return [];\n const values: unknown = Reflect.get(parsed, 'values');\n if (!Array.isArray(values) || !Array.isArray(values[0])) return [];\n return values[0].map((cell: unknown) => String(cell));\n}\n\n/** Create a fresh in-memory Sheets backend, seeded with `rows` from row 2. */\nexport function createSheetsFetch(\n rows: Array<[string, string]> = [['alice', '{\"tier\":\"gold\"}']],\n): typeof fetch {\n const cells = new Map<string, string>([\n ['A1', 'key'],\n ['B1', 'value'],\n ]);\n rows.forEach(([key, value], i) => {\n cells.set(`A${i + 2}`, key);\n cells.set(`B${i + 2}`, value);\n });\n\n const lastRow = (): number => {\n let max = 0;\n for (const ref of cells.keys()) {\n const row = Number.parseInt(ref.replace(/^[A-Z]+/, ''), 10);\n if (row > max) max = row;\n }\n return max;\n };\n\n return async (input, init) => {\n const url = requestUrl(input);\n const method = init?.method ?? 'GET';\n\n if (url.startsWith('https://oauth2.googleapis.com/token'))\n return jsonResponse(TOKEN_BODY);\n if (url.startsWith('http://metadata.google.internal/'))\n return jsonResponse(TOKEN_BODY);\n\n const values = /\\/spreadsheets\\/([^/?]+)\\/values\\/([^?]+)/.exec(url);\n if (!values) {\n const sheet = /\\/spreadsheets\\/([^/?]+)\\?/.exec(url);\n if (sheet) return jsonResponse({ spreadsheetId: sheet[1] });\n return jsonResponse({ error: { code: 404 } }, 404);\n }\n\n const range = decodeURIComponent(values[2]);\n\n // Append across the key and value columns, e.g. `Sheet1!A:B:append`.\n const append = /!([A-Z]+):([A-Z]+):append$/.exec(range);\n if (append && method === 'POST') {\n const row = lastRow() + 1;\n const [key = '', value = ''] = firstCell(init?.body);\n cells.set(`${append[1]}${row}`, key);\n cells.set(`${append[2]}${row}`, value);\n return jsonResponse({\n updates: {\n updatedRange: `Sheet1!${append[1]}${row}:${append[2]}${row}`,\n },\n });\n }\n\n // Whole column from a row, e.g. `Sheet1!A2:A`.\n const column = /!([A-Z]+)(\\d+):([A-Z]+)$/.exec(range);\n if (column) {\n const out: string[][] = [];\n for (let row = Number(column[2]); row <= lastRow(); row++) {\n out.push([cells.get(`${column[1]}${row}`) ?? '']);\n }\n return jsonResponse({ range, values: out });\n }\n\n // Single cell, e.g. `Sheet1!B5`.\n const cell = /!([A-Z]+\\d+)$/.exec(range);\n if (cell) {\n if (method === 'PUT') {\n cells.set(cell[1], firstCell(init?.body)[0] ?? '');\n return jsonResponse({ updatedRange: range });\n }\n const value = cells.get(cell[1]);\n return jsonResponse(\n value === undefined ? { range } : { range, values: [[value]] },\n );\n }\n\n return jsonResponse({ error: { code: 400 } }, 400);\n };\n}\n\n/** Each read of `fetch` is a fresh backend, so runs never share writes. */\nexport const push: Env = {\n get fetch() {\n return createSheetsFetch();\n },\n};\n\n/** Every request goes through `fetch`; `args[0]` is the URL. */\nexport const simulation = ['fetch'];\n","import type { Flow } from '@walkeros/core';\n\n/** Read a JSON value with ADC (Cloud Run / GKE service account). */\nexport const readWithAdc: Flow.StepExample = {\n title: 'Read with ADC',\n description:\n 'Read a value from the Sheets store using ADC, no credentials field needed on Cloud Run or GKE',\n in: { operation: 'get', key: 'alice' },\n out: [['get', 'alice', '{ tier: \"gold\" }']],\n};\n\n/** Write a JSON value with an explicit service account JSON. */\nexport const writeWithServiceAccount: Flow.StepExample = {\n title: 'Write with service account',\n description:\n 'Append or update a JSON value in the configured sheet using an explicit service account JSON',\n in: { operation: 'set', key: 'bob', value: { tier: 'silver' } },\n out: [['set', 'bob', '{ tier: \"silver\" }']],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'structured-only': {\n text: 'The Sheets store is structured-only: each cell holds a structured StoreValue JSON value. There is no byte-native mode, so config.file: true is a hard error at init and a value carrying a binary (Uint8Array) leaf is rejected. For byte-exact asset serving (walker.js and friends) use fs, s3, or gcs with file: true instead. Sheets is also not a cache backing; pair it with a cache wrapper to absorb its rate limit, but the Sheets store itself stores only structured cells.',\n },\n};\n"],"mappings":"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","names":["z"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -40,7 +40,14 @@ interface Setup {
|
|
|
40
40
|
*/
|
|
41
41
|
headers?: string[];
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Injected dependencies (tests, simulate). Every Sheets and token request
|
|
45
|
+
* goes through `fetch`; without it the global `fetch` is used.
|
|
46
|
+
*/
|
|
47
|
+
interface Env extends Store.BaseEnv {
|
|
48
|
+
fetch?: typeof fetch;
|
|
49
|
+
}
|
|
50
|
+
type Types = Store.Types<SheetsStoreSettings, Env, SheetsStoreSettings, Setup, Credential<ServiceAccount>>;
|
|
44
51
|
|
|
45
52
|
declare const storeSheetsInit: Store.Init<Types>;
|
|
46
53
|
|
|
@@ -63,6 +70,6 @@ type SheetsStoreConfig = Store.Config<Types>;
|
|
|
63
70
|
* `setup.headers` row. Never alters existing data: re-running with the same
|
|
64
71
|
* headers is a no-op overwrite.
|
|
65
72
|
*/
|
|
66
|
-
declare const setup: SetupFn<SheetsStoreConfig,
|
|
73
|
+
declare const setup: SetupFn<SheetsStoreConfig, Env>;
|
|
67
74
|
|
|
68
|
-
export { DEFAULT_SETUP, type ServiceAccountCredentials, type Setup, type SetupResult, type SheetsStoreConfig, type SheetsStoreSettings, type Types, storeSheetsInit as default, setup, storeSheetsInit };
|
|
75
|
+
export { DEFAULT_SETUP, type Env, type ServiceAccountCredentials, type Setup, type SetupResult, type SheetsStoreConfig, type SheetsStoreSettings, type Types, storeSheetsInit as default, setup, storeSheetsInit };
|
package/dist/index.d.ts
CHANGED
|
@@ -40,7 +40,14 @@ interface Setup {
|
|
|
40
40
|
*/
|
|
41
41
|
headers?: string[];
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Injected dependencies (tests, simulate). Every Sheets and token request
|
|
45
|
+
* goes through `fetch`; without it the global `fetch` is used.
|
|
46
|
+
*/
|
|
47
|
+
interface Env extends Store.BaseEnv {
|
|
48
|
+
fetch?: typeof fetch;
|
|
49
|
+
}
|
|
50
|
+
type Types = Store.Types<SheetsStoreSettings, Env, SheetsStoreSettings, Setup, Credential<ServiceAccount>>;
|
|
44
51
|
|
|
45
52
|
declare const storeSheetsInit: Store.Init<Types>;
|
|
46
53
|
|
|
@@ -63,6 +70,6 @@ type SheetsStoreConfig = Store.Config<Types>;
|
|
|
63
70
|
* `setup.headers` row. Never alters existing data: re-running with the same
|
|
64
71
|
* headers is a no-op overwrite.
|
|
65
72
|
*/
|
|
66
|
-
declare const setup: SetupFn<SheetsStoreConfig,
|
|
73
|
+
declare const setup: SetupFn<SheetsStoreConfig, Env>;
|
|
67
74
|
|
|
68
|
-
export { DEFAULT_SETUP, type ServiceAccountCredentials, type Setup, type SetupResult, type SheetsStoreConfig, type SheetsStoreSettings, type Types, storeSheetsInit as default, setup, storeSheetsInit };
|
|
75
|
+
export { DEFAULT_SETUP, type Env, type ServiceAccountCredentials, type Setup, type SetupResult, type SheetsStoreConfig, type SheetsStoreSettings, type Types, storeSheetsInit as default, setup, storeSheetsInit };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,s={};((e,r)=>{for(var n in r)t(e,n,{get:r[n],enumerable:!0})})(s,{DEFAULT_SETUP:()=>m,default:()=>x,setup:()=>S,storeSheetsInit:()=>x}),module.exports=(e=s,((e,s,i,a)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let u of n(s))o.call(e,u)||u===i||t(e,u,{get:()=>s[u],enumerable:!(a=r(s,u))||a.enumerable});return e})(t({},"__esModule",{value:!0}),e));var i=require("crypto"),a="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",u="https://oauth2.googleapis.com/token",c="https://www.googleapis.com/auth/spreadsheets",f=6e4;function d(e){let
|
|
1
|
+
"use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,s={};((e,r)=>{for(var n in r)t(e,n,{get:r[n],enumerable:!0})})(s,{DEFAULT_SETUP:()=>m,default:()=>x,setup:()=>S,storeSheetsInit:()=>x}),module.exports=(e=s,((e,s,i,a)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let u of n(s))o.call(e,u)||u===i||t(e,u,{get:()=>s[u],enumerable:!(a=r(s,u))||a.enumerable});return e})(t({},"__esModule",{value:!0}),e));var i=require("crypto"),a="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",u="https://oauth2.googleapis.com/token",c="https://www.googleapis.com/auth/spreadsheets",f=6e4;function d(e,t=fetch){let r;return async function(){return r&&Date.now()<r.expiresAt||(r=e?await async function(e,t){const r=Math.floor(Date.now()/1e3),n=function(e,t){const r=p(JSON.stringify({alg:"RS256",typ:"JWT"})),n=p(JSON.stringify(e)),o=(0,i.createSign)("RSA-SHA256");return o.update(`${r}.${n}`),`${r}.${n}.${o.sign(t,"base64url")}`}({iss:e.client_email,scope:c,aud:u,iat:r,exp:r+3600},e.private_key),o=await t(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${n}`});if(!o.ok)throw new Error(`Token exchange error: ${o.status}`);const s=await o.json();return{token:s.access_token,expiresAt:Date.now()+1e3*s.expires_in-f}}(e,t):await async function(e){const t=await e(a,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!t.ok)throw new Error(`Metadata server error: ${t.status}`);const r=await t.json();return{token:r.access_token,expiresAt:Date.now()+1e3*r.expires_in-f}}(t)),r.token}}function p(e){return("string"==typeof e?Buffer.from(e):e).toString("base64url")}function h(e,t){if(void 0!==e.credentials)return e.credentials;const r=e.settings?.credentials;return void 0!==r?(t.warn("settings.credentials is deprecated; use config.credentials"),r):void 0}var l=require("@walkeros/core"),g="https://sheets.googleapis.com/v4/spreadsheets";function y(e){return e.sheet??"Sheet1"}function w(e,t,r){return`${$(e)}!${t}${r}`}function v(e,t){const r=function(e){if(e<1)throw new Error(`columnLetter: index must be >=1, got ${e}`);let t=e,r="";for(;t>0;){const e=(t-1)%26;r=String.fromCharCode(65+e)+r,t=Math.floor((t-1)/26)}return r}(t);return`${$(e)}!A1:${r}1`}function $(e){return/^[A-Za-z0-9_]+$/.test(e)?e:`'${e.replace(/'/g,"''")}'`}var m={},S=async e=>{const{config:t,logger:r,id:n}=e,o=(0,l.resolveSetup)(t.setup,m);if(!o)return void r.debug("setup: skipped (config.setup is false or unset)");!function(e){if(!e||"string"!=typeof e.id||0===e.id.length)throw new Error("setup: settings.id (spreadsheet ID) is required")}(t.settings);const s=t.settings,i=function(e){if(!e)return;if("string"==typeof e){let t;try{t=JSON.parse(e)}catch{return}return b(t)?t:void 0}return b(e)?e:void 0}(h(t,r)),a=e.env?.fetch??e.config.env?.fetch??fetch,u=d(i,a),c=await u(),f=`${g}/${encodeURIComponent(s.id)}?fields=spreadsheetId`,p=await a(f,{headers:{Authorization:`Bearer ${c}`}});if(404===p.status)throw new Error(`Spreadsheet not found: ${s.id}. Run "walkeros setup store.${n}" to ensure the sheet exists and is shared with the service account.`);if(!p.ok){const e=await A(p);throw new Error(`setup: spreadsheet probe failed (${p.status}): ${e}`)}let w=!1;if(o.headers&&o.headers.length>0){const e=v(y(s),o.headers.length),t=`${g}/${encodeURIComponent(s.id)}/values/${encodeURIComponent(e)}?valueInputOption=RAW`,n={values:[o.headers],range:e},i=await a(t,{method:"PUT",headers:{Authorization:`Bearer ${c}`,"Content-Type":"application/json"},body:JSON.stringify(n)});if(!i.ok){const e=await A(i);throw new Error(`setup: header write failed (${i.status}): ${e}`)}w=!0,r.info("setup: headers written",{spreadsheetId:s.id,headers:o.headers.length})}else r.debug("setup: spreadsheet verified, no headers configured",{spreadsheetId:s.id});return{headersWritten:w}};function b(e){return!!function(e){return"object"==typeof e&&null!==e}(e)&&("string"==typeof e.client_email&&"string"==typeof e.private_key)}async function A(e){try{return await e.text()}catch{return""}}var k=new Map;function I(e){return"object"==typeof e&&null!==e}function O(e){return!!I(e)&&("string"==typeof e.client_email&&"string"==typeof e.private_key)}async function C(e,t,r,n,o,s){const i=await o(),a=function(e,t,r){return`${$(e)}!${t}${r}:${t}`}(t,r,n+1),u=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}`,c=await s(u,{headers:{Authorization:`Bearer ${i}`}});if(!c.ok)throw new Error(`storeSheetsInit: failed to read key column (${c.status})`);const f=function(e){if(!I(e))return[];const t=e.values;if(!Array.isArray(t))return[];const r=[];for(const e of t)Array.isArray(e)&&"string"==typeof e[0]?r.push(e[0]):r.push("");return r}(await c.json()),d=new Map;return f.forEach((e,t)=>{e&&d.set(e,n+t+1)}),d}async function R(e,t,r,n,o,s,i){const a=await s(),u=w(t,r,n),c=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(u)}?valueInputOption=RAW`,f=await i(c,{method:"PUT",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({values:[[o]],range:u})});if(!f.ok)throw new Error(`storeSheets.writeCell: write failed (${f.status})`)}async function U(e,t,r,n,o,s,i){const a=await s(),u=function(e,t,r){return`${$(e)}!${t}:${r}`}(t,r,n),c=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(u)}:append?valueInputOption=RAW`,f=await i(c,{method:"POST",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({values:[o]})});if(!f.ok)throw new Error(`storeSheets.appendRow: append failed (${f.status})`);const d=await f.json();if(!I(d))return;const p=d.updates;if(!I(p))return;const h=p.updatedRange;return"string"==typeof h?function(e){const t=/![A-Z]+(\d+)/.exec(e);if(!t)return;const r=Number.parseInt(t[1],10);return Number.isFinite(r)?r:void 0}(h):void 0}function E(e){return null===e||"string"==typeof e||"number"==typeof e||"boolean"==typeof e||(Array.isArray(e)?e.every(E):"object"==typeof e&&Object.values(e).every(E))}function j(e){return e instanceof Uint8Array||e instanceof ArrayBuffer||(Array.isArray(e)?e.some(j):"object"==typeof e&&null!==e&&Object.values(e).some(j))}var x=async e=>{if(function(e){if(!e||"string"!=typeof e.id||0===e.id.length)throw new Error("storeSheetsInit: settings.id (spreadsheet ID) is required (non-empty string)")}(e.config.settings),e.config.file)throw new Error("Sheets store does not support file mode (file: true). Sheets stores structured JSON values per cell. Use fs, s3, or gcs for byte-exact serving.");const t=e.config.settings,{logger:r}=e,n=e.id,o=function(e){if(e){if("string"==typeof e){let t;try{t=JSON.parse(e)}catch{return}return O(t)?t:void 0}return O(e)?e:void 0}}(h(e.config,r)),s=e.env?.fetch??e.config.env?.fetch??fetch,i=d(o,s),a=y(t),u=function(e){return(e.key??"A").toUpperCase()}(t),c=function(e){return(e.value??"B").toUpperCase()}(t),f=function(e){return e.headerRows??1}(t);await async function(e,t,r,n,o){const s=k.get(e);if(void 0!==s)return void await s;const i=(async()=>{let s;try{const t=await r(),n=`${g}/${encodeURIComponent(e)}?fields=spreadsheetId`;s=await o(n,{headers:{Authorization:`Bearer ${t}`}})}catch(t){return n.debug("ensureSpreadsheetExists check failed (non-fatal)",{spreadsheetId:e,error:t instanceof Error?t.message:String(t)}),!0}if(404===s.status)throw new Error(`Spreadsheet not found: ${e}. Run "walkeros setup store.${t}" to ensure the sheet exists and is shared with the service account.`);return s.ok||n.debug("ensureSpreadsheetExists check failed (non-fatal)",{spreadsheetId:e,status:s.status}),!0})();k.set(e,i);try{await i}catch(t){throw k.delete(e),t}}(t.id,n,i,r,s);const p=await C(t.id,a,u,f,i,s);return{type:"sheets",config:{settings:t,env:e.config.env,id:e.config.id,logger:e.config.logger},setup:S,async get(e){const n=p.get(e);if(void 0===n)return;const o=await async function(e,t,r,n,o,s){const i=await o(),a=w(t,r,n),u=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}`,c=await s(u,{headers:{Authorization:`Bearer ${i}`}});if(!c.ok)return;const f=await c.json();if(!I(f))return;const d=f.values;if(!Array.isArray(d)||0===d.length)return;const p=d[0];if(!Array.isArray(p)||0===p.length)return;const h=p[0];return"string"==typeof h?h:void 0}(t.id,a,c,n,i,s);if(void 0===o||""===o)return;let u;try{u=JSON.parse(o)}catch(t){return void r.debug("storeSheets.get: JSON parse failed",{key:e,error:t instanceof Error?t.message:String(t)})}return E(u)?u:void 0},async set(e,r){if(j(r))throw new Error("Sheets store cannot persist binary values (Uint8Array). Cells hold structured JSON only; the 50k-character cell limit makes base64 binary unsafe. Use fs, s3, or gcs.");const n=JSON.stringify(r),o=p.get(e);if(void 0===o){const r=await U(t.id,a,u,c,[e,n],i,s);void 0!==r&&p.set(e,r)}else await R(t.id,a,c,o,n,i,s)},async delete(e){const r=p.get(e);void 0!==r&&await R(t.id,a,c,r,"",i,s)}}};//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/auth.ts","../src/credentials.ts","../src/setup.ts","../src/setup-helpers.ts","../src/store.ts"],"sourcesContent":["export { storeSheetsInit } from './store';\nexport { setup, DEFAULT_SETUP } from './setup';\nexport type { SetupResult, SheetsStoreConfig } from './setup';\nexport type {\n ServiceAccountCredentials,\n Setup,\n SheetsStoreSettings,\n Types,\n} from './types';\nexport { storeSheetsInit 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/spreadsheets';\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 { Logger } from '@walkeros/core';\n\n/**\n * Structural view of the credential-bearing parts of a store config, shared by\n * the `storeSheetsInit` and `setup` read sites (whose config generics differ:\n * the init context erases the credentials slot to `unknown`). The resolved\n * value is validated downstream by `parseCredentials`.\n */\ninterface CredentialConfig {\n credentials?: unknown;\n settings?: { credentials?: unknown };\n}\n\n/**\n * Resolve the credential value from the config, preferring the standard\n * `config.credentials` slot and falling back to the deprecated\n * `settings.credentials`. Uses `??` semantics (`!== undefined`) so an explicit\n * empty-string credential surfaces a misconfig instead of silently falling\n * through to settings/ADC.\n *\n * Warns once per instance when the deprecated `settings.credentials` path is\n * used. Call this at the init-time read site (which runs once per instance);\n * there is no module-level once-flag so a second instance still warns.\n */\nexport function resolveCredentials(\n config: CredentialConfig,\n logger: Logger.Instance,\n): unknown {\n if (config.credentials !== undefined) return config.credentials;\n\n const fromSettings = config.settings?.credentials;\n if (fromSettings !== undefined) {\n logger.warn('settings.credentials is deprecated; use config.credentials');\n return fromSettings;\n }\n\n return undefined;\n}\n","import type { LifecycleContext, SetupFn, Store } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type {\n ServiceAccountCredentials,\n Setup,\n SheetsStoreSettings,\n Types,\n} from './types';\nimport { createTokenProvider } from './auth';\nimport { resolveCredentials } from './credentials';\nimport { SHEETS_BASE, buildHeaderRange, resolveSheet } from './setup-helpers';\n\n/**\n * Default setup options. `headers` is intentionally omitted so callers must\n * opt in to writing a header row.\n */\nexport const DEFAULT_SETUP: Setup = {};\n\nexport interface SetupResult {\n headersWritten: 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 SheetsStoreConfig = Store.Config<Types>;\n\n/**\n * Provision a Google Sheets store described in the flow config.\n * Verifies the spreadsheet exists and (idempotently) writes the configured\n * `setup.headers` row. Never alters existing data: re-running with the same\n * headers is a no-op overwrite.\n */\nexport const setup: SetupFn<SheetsStoreConfig, Store.BaseEnv> = async (\n context: LifecycleContext<SheetsStoreConfig, Store.BaseEnv>,\n) => {\n const { config, logger, id } = 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 assertSheetsSettings(config.settings);\n const settings = config.settings;\n\n const creds = parseCredentials(resolveCredentials(config, logger));\n const getToken = createTokenProvider(creds);\n const token = await getToken();\n\n const probeUrl = `${SHEETS_BASE}/${encodeURIComponent(settings.id)}?fields=spreadsheetId`;\n const probeRes = await fetch(probeUrl, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (probeRes.status === 404) {\n throw new Error(\n `Spreadsheet not found: ${settings.id}. Run \"walkeros setup store.${id}\" to ensure the sheet exists and is shared with the service account.`,\n );\n }\n if (!probeRes.ok) {\n const text = await safeText(probeRes);\n throw new Error(\n `setup: spreadsheet probe failed (${probeRes.status}): ${text}`,\n );\n }\n\n let headersWritten = false;\n if (options.headers && options.headers.length > 0) {\n const sheet = resolveSheet(settings);\n const range = buildHeaderRange(sheet, options.headers.length);\n const url = `${SHEETS_BASE}/${encodeURIComponent(settings.id)}/values/${encodeURIComponent(range)}?valueInputOption=RAW`;\n const body = { values: [options.headers], range };\n const writeRes = await fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n if (!writeRes.ok) {\n const text = await safeText(writeRes);\n throw new Error(\n `setup: header write failed (${writeRes.status}): ${text}`,\n );\n }\n headersWritten = true;\n logger.info('setup: headers written', {\n spreadsheetId: settings.id,\n headers: options.headers.length,\n });\n } else {\n logger.debug('setup: spreadsheet verified, no headers configured', {\n spreadsheetId: settings.id,\n });\n }\n\n return { headersWritten };\n};\n\nfunction assertSheetsSettings(\n settings: Partial<SheetsStoreSettings> | undefined,\n): asserts settings is SheetsStoreSettings {\n if (\n !settings ||\n typeof settings.id !== 'string' ||\n settings.id.length === 0\n ) {\n throw new Error('setup: settings.id (spreadsheet ID) is required');\n }\n}\n\nfunction parseCredentials(\n credentials?: unknown,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n if (isServiceAccountShape(credentials)) return credentials;\n return undefined;\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\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text();\n } catch {\n return '';\n }\n}\n","import type { SheetsStoreSettings } from './types';\n\nexport const SHEETS_BASE = 'https://sheets.googleapis.com/v4/spreadsheets';\n\nexport function resolveSheet(settings: SheetsStoreSettings): string {\n return settings.sheet ?? 'Sheet1';\n}\n\nexport function resolveKeyColumn(settings: SheetsStoreSettings): string {\n return (settings.key ?? 'A').toUpperCase();\n}\n\nexport function resolveValueColumn(settings: SheetsStoreSettings): string {\n return (settings.value ?? 'B').toUpperCase();\n}\n\nexport function resolveHeaderRows(settings: SheetsStoreSettings): number {\n return settings.headerRows ?? 1;\n}\n\n/** Build an A1 range like 'Sheet1!A2:A' for reading a whole column from row N. */\nexport function buildColumnRange(\n sheet: string,\n column: string,\n fromRow: number,\n): string {\n return `${encodeSheet(sheet)}!${column}${fromRow}:${column}`;\n}\n\n/** Build an A1 range like 'Sheet1!B5' for a single cell. */\nexport function buildCellRange(\n sheet: string,\n column: string,\n row: number,\n): string {\n return `${encodeSheet(sheet)}!${column}${row}`;\n}\n\n/** Build an A1 range for writing the header row. */\nexport function buildHeaderRange(sheet: string, columnsCount: number): string {\n const last = columnLetter(columnsCount);\n return `${encodeSheet(sheet)}!A1:${last}1`;\n}\n\n/** Build an A1 range for appending rows across the key+value columns. */\nexport function buildAppendRange(\n sheet: string,\n keyColumn: string,\n valueColumn: string,\n): string {\n return `${encodeSheet(sheet)}!${keyColumn}:${valueColumn}`;\n}\n\n/** Convert a 1-based column index into a column letter (1 -> A, 27 -> AA). */\nexport function columnLetter(index: number): string {\n if (index < 1) {\n throw new Error(`columnLetter: index must be >=1, got ${index}`);\n }\n let n = index;\n let out = '';\n while (n > 0) {\n const rem = (n - 1) % 26;\n out = String.fromCharCode(65 + rem) + out;\n n = Math.floor((n - 1) / 26);\n }\n return out;\n}\n\n/** Sheet names with spaces or special chars must be wrapped in single quotes. */\nexport function encodeSheet(name: string): string {\n if (/^[A-Za-z0-9_]+$/.test(name)) return name;\n return `'${name.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Parse a row index out of a Sheets API `updates.updatedRange` value such as\n * `Sheet1!A5:B5` or `'My Sheet'!A5:B5`. Returns the first row number found.\n */\nexport function parseRowFromRange(range: string): number | undefined {\n const match = /![A-Z]+(\\d+)/.exec(range);\n if (!match) return undefined;\n const row = Number.parseInt(match[1], 10);\n return Number.isFinite(row) ? row : undefined;\n}\n","import type { Logger, Store } from '@walkeros/core';\nimport type {\n ServiceAccountCredentials,\n SheetsStoreSettings,\n Types,\n} from './types';\nimport { createTokenProvider, type TokenProvider } from './auth';\nimport { resolveCredentials } from './credentials';\nimport { setup as sheetsSetup } from './setup';\nimport {\n SHEETS_BASE,\n buildAppendRange,\n buildCellRange,\n buildColumnRange,\n parseRowFromRange,\n resolveHeaderRows,\n resolveKeyColumn,\n resolveSheet,\n resolveValueColumn,\n} from './setup-helpers';\n\n/**\n * Module-level cache for the spreadsheet existence pre-check, keyed by\n * spreadsheet ID. Shared across `storeSheetsInit` invocations so the same\n * spreadsheet is probed at most once per process.\n */\nconst spreadsheetExistsCache: Map<string, Promise<boolean>> = new Map();\n\n/** @internal Test-only: clear the existence cache. */\nexport function __resetSpreadsheetExistenceCache(): void {\n spreadsheetExistsCache.clear();\n}\n\n/** @internal Test-only: seed the existence cache for a spreadsheet. */\nexport function __seedSpreadsheetExists(spreadsheetId: string): void {\n spreadsheetExistsCache.set(spreadsheetId, Promise.resolve(true));\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?: unknown,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n if (isServiceAccountShape(credentials)) return credentials;\n return undefined;\n}\n\nfunction assertSheetsSettings(\n settings: Partial<SheetsStoreSettings> | undefined,\n): asserts settings is SheetsStoreSettings {\n if (\n !settings ||\n typeof settings.id !== 'string' ||\n settings.id.length === 0\n ) {\n throw new Error(\n 'storeSheetsInit: settings.id (spreadsheet ID) is required (non-empty string)',\n );\n }\n}\n\nasync function ensureSpreadsheetExists(\n spreadsheetId: string,\n storeId: string,\n getToken: TokenProvider,\n logger: Logger.Instance,\n): Promise<void> {\n const existing = spreadsheetExistsCache.get(spreadsheetId);\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 = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}?fields=spreadsheetId`;\n res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch (err) {\n logger.debug('ensureSpreadsheetExists check failed (non-fatal)', {\n spreadsheetId,\n error: err instanceof Error ? err.message : String(err),\n });\n return true;\n }\n\n if (res.status === 404) {\n throw new Error(\n `Spreadsheet not found: ${spreadsheetId}. Run \"walkeros setup store.${storeId}\" to ensure the sheet exists and is shared with the service account.`,\n );\n }\n if (!res.ok) {\n logger.debug('ensureSpreadsheetExists check failed (non-fatal)', {\n spreadsheetId,\n status: res.status,\n });\n }\n return true;\n })();\n\n spreadsheetExistsCache.set(spreadsheetId, promise);\n try {\n await promise;\n } catch (err) {\n spreadsheetExistsCache.delete(spreadsheetId);\n throw err;\n }\n}\n\ninterface KeyColumnResponse {\n values?: unknown[];\n}\n\nfunction readKeyColumnRows(payload: unknown): string[] {\n if (!isRecord(payload)) return [];\n const values = (payload as KeyColumnResponse).values;\n if (!Array.isArray(values)) return [];\n const out: string[] = [];\n for (const row of values) {\n if (Array.isArray(row) && typeof row[0] === 'string') {\n out.push(row[0]);\n } else {\n out.push('');\n }\n }\n return out;\n}\n\nasync function buildKeyIndex(\n spreadsheetId: string,\n sheet: string,\n keyCol: string,\n headerRows: number,\n getToken: TokenProvider,\n): Promise<Map<string, number>> {\n const token = await getToken();\n const range = buildColumnRange(sheet, keyCol, headerRows + 1);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) {\n throw new Error(\n `storeSheetsInit: failed to read key column (${res.status})`,\n );\n }\n const payload: unknown = await res.json();\n const rows = readKeyColumnRows(payload);\n const index = new Map<string, number>();\n rows.forEach((key, i) => {\n if (key) index.set(key, headerRows + i + 1);\n });\n return index;\n}\n\nasync function readCell(\n spreadsheetId: string,\n sheet: string,\n column: string,\n row: number,\n getToken: TokenProvider,\n): Promise<string | undefined> {\n const token = await getToken();\n const range = buildCellRange(sheet, column, row);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n const payload: unknown = await res.json();\n if (!isRecord(payload)) return undefined;\n const values = (payload as KeyColumnResponse).values;\n if (!Array.isArray(values) || values.length === 0) return undefined;\n const first = values[0];\n if (!Array.isArray(first) || first.length === 0) return undefined;\n const cell = first[0];\n return typeof cell === 'string' ? cell : undefined;\n}\n\nasync function writeCell(\n spreadsheetId: string,\n sheet: string,\n column: string,\n row: number,\n value: string,\n getToken: TokenProvider,\n): Promise<void> {\n const token = await getToken();\n const range = buildCellRange(sheet, column, row);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}?valueInputOption=RAW`;\n const res = await fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ values: [[value]], range }),\n });\n if (!res.ok) {\n throw new Error(`storeSheets.writeCell: write failed (${res.status})`);\n }\n}\n\ninterface AppendResponse {\n updates?: { updatedRange?: unknown };\n}\n\nasync function appendRow(\n spreadsheetId: string,\n sheet: string,\n keyCol: string,\n valueCol: string,\n values: [string, string],\n getToken: TokenProvider,\n): Promise<number | undefined> {\n const token = await getToken();\n const range = buildAppendRange(sheet, keyCol, valueCol);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}:append?valueInputOption=RAW`;\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ values: [values] }),\n });\n if (!res.ok) {\n throw new Error(`storeSheets.appendRow: append failed (${res.status})`);\n }\n const payload: unknown = await res.json();\n if (!isRecord(payload)) return undefined;\n const updates = (payload as AppendResponse).updates;\n if (!isRecord(updates)) return undefined;\n const updatedRange = updates.updatedRange;\n if (typeof updatedRange !== 'string') return undefined;\n return parseRowFromRange(updatedRange);\n}\n\n/**\n * Recursive type guard: a JSON-derived value is structurally a `StoreValue`.\n * Sheets cells hold JSON only, so a parsed cell never carries a `Uint8Array`\n * leaf; the guard narrows the `JSON.parse` result cast-free at the boundary.\n */\nfunction isStoreValue(value: unknown): value is Store.StoreValue {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n )\n return true;\n if (Array.isArray(value)) return value.every(isStoreValue);\n if (typeof value === 'object')\n return Object.values(value).every(isStoreValue);\n return false;\n}\n\n/**\n * Recursively detect any binary leaf (Uint8Array, Buffer, or ArrayBuffer)\n * anywhere in a structured value. Buffer is a Uint8Array subclass, so the\n * single `instanceof Uint8Array` check covers it.\n */\nfunction hasBinaryLeaf(value: unknown): boolean {\n if (value instanceof Uint8Array || value instanceof ArrayBuffer) return true;\n if (Array.isArray(value)) return value.some(hasBinaryLeaf);\n if (typeof value === 'object' && value !== null)\n return Object.values(value).some(hasBinaryLeaf);\n return false;\n}\n\nexport const storeSheetsInit: Store.Init<Types> = async (context) => {\n assertSheetsSettings(context.config.settings);\n // Sheets stores structured JSON per cell; it cannot serve byte-exact raw\n // bytes. file:true is a hard error at init: pick fs, s3, or gcs instead.\n if (context.config.file) {\n throw new Error(\n 'Sheets store does not support file mode (file: true). Sheets stores structured JSON values per cell. Use fs, s3, or gcs for byte-exact serving.',\n );\n }\n const settings: SheetsStoreSettings = context.config.settings;\n const { logger } = context;\n const id = context.id;\n const creds = parseCredentials(resolveCredentials(context.config, logger));\n const getToken = createTokenProvider(creds);\n const sheet = resolveSheet(settings);\n const keyCol = resolveKeyColumn(settings);\n const valueCol = resolveValueColumn(settings);\n const headerRows = resolveHeaderRows(settings);\n\n await ensureSpreadsheetExists(settings.id, id, getToken, logger);\n\n const keyToRow = await buildKeyIndex(\n settings.id,\n sheet,\n keyCol,\n headerRows,\n getToken,\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 };\n\n return {\n type: 'sheets',\n config,\n setup: sheetsSetup,\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const row = keyToRow.get(key);\n if (row === undefined) return undefined;\n const cell = await readCell(settings.id, sheet, valueCol, row, getToken);\n if (cell === undefined || cell === '') return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(cell);\n } catch (err) {\n logger.debug('storeSheets.get: JSON parse failed', {\n key,\n error: err instanceof Error ? err.message : String(err),\n });\n return undefined;\n }\n return isStoreValue(parsed) ? parsed : undefined;\n },\n\n async set(key: string, value: Store.StoreValue): Promise<void> {\n // Sheets cells hold structured JSON only. A binary leaf anywhere in the\n // value (e.g. a request-cache encoded Buffer) would JSON.stringify into a\n // base64 blob, and the 50k-character cell limit makes that unsafe. Reject\n // any binary leaf, at any depth, loudly. Plain JSON values are unaffected.\n if (hasBinaryLeaf(value)) {\n throw new Error(\n 'Sheets store cannot persist binary values (Uint8Array). Cells hold structured JSON only; the 50k-character cell limit makes base64 binary unsafe. Use fs, s3, or gcs.',\n );\n }\n const serialized = JSON.stringify(value);\n const existingRow = keyToRow.get(key);\n if (existingRow === undefined) {\n const newRow = await appendRow(\n settings.id,\n sheet,\n keyCol,\n valueCol,\n [key, serialized],\n getToken,\n );\n if (newRow !== undefined) keyToRow.set(key, newRow);\n } else {\n await writeCell(\n settings.id,\n sheet,\n valueCol,\n existingRow,\n serialized,\n getToken,\n );\n }\n },\n\n async delete(key: string): Promise<void> {\n const row = keyToRow.get(key);\n if (row === undefined) return;\n await writeCell(settings.id, sheet, valueCol, row, '', getToken);\n },\n };\n};\n"],"mappings":"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","names":["isRecord","isServiceAccountShape","parseCredentials","assertSheetsSettings"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/auth.ts","../src/credentials.ts","../src/setup.ts","../src/setup-helpers.ts","../src/store.ts"],"sourcesContent":["export { storeSheetsInit } from './store';\nexport { setup, DEFAULT_SETUP } from './setup';\nexport type { SetupResult, SheetsStoreConfig } from './setup';\nexport type {\n Env,\n ServiceAccountCredentials,\n Setup,\n SheetsStoreSettings,\n Types,\n} from './types';\nexport { storeSheetsInit 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/spreadsheets';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n doFetch: typeof fetch = fetch,\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, doFetch)\n : await fetchMetadataToken(doFetch);\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(doFetch: typeof fetch): Promise<CachedToken> {\n const res = await doFetch(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 doFetch: typeof fetch,\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 doFetch(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 { Logger } from '@walkeros/core';\n\n/**\n * Structural view of the credential-bearing parts of a store config, shared by\n * the `storeSheetsInit` and `setup` read sites (whose config generics differ:\n * the init context erases the credentials slot to `unknown`). The resolved\n * value is validated downstream by `parseCredentials`.\n */\ninterface CredentialConfig {\n credentials?: unknown;\n settings?: { credentials?: unknown };\n}\n\n/**\n * Resolve the credential value from the config, preferring the standard\n * `config.credentials` slot and falling back to the deprecated\n * `settings.credentials`. Uses `??` semantics (`!== undefined`) so an explicit\n * empty-string credential surfaces a misconfig instead of silently falling\n * through to settings/ADC.\n *\n * Warns once per instance when the deprecated `settings.credentials` path is\n * used. Call this at the init-time read site (which runs once per instance);\n * there is no module-level once-flag so a second instance still warns.\n */\nexport function resolveCredentials(\n config: CredentialConfig,\n logger: Logger.Instance,\n): unknown {\n if (config.credentials !== undefined) return config.credentials;\n\n const fromSettings = config.settings?.credentials;\n if (fromSettings !== undefined) {\n logger.warn('settings.credentials is deprecated; use config.credentials');\n return fromSettings;\n }\n\n return undefined;\n}\n","import type { LifecycleContext, SetupFn, Store } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type {\n Env,\n ServiceAccountCredentials,\n Setup,\n SheetsStoreSettings,\n Types,\n} from './types';\nimport { createTokenProvider } from './auth';\nimport { resolveCredentials } from './credentials';\nimport { SHEETS_BASE, buildHeaderRange, resolveSheet } from './setup-helpers';\n\n/**\n * Default setup options. `headers` is intentionally omitted so callers must\n * opt in to writing a header row.\n */\nexport const DEFAULT_SETUP: Setup = {};\n\nexport interface SetupResult {\n headersWritten: 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 SheetsStoreConfig = Store.Config<Types>;\n\n/**\n * Provision a Google Sheets store described in the flow config.\n * Verifies the spreadsheet exists and (idempotently) writes the configured\n * `setup.headers` row. Never alters existing data: re-running with the same\n * headers is a no-op overwrite.\n */\nexport const setup: SetupFn<SheetsStoreConfig, Env> = async (\n context: LifecycleContext<SheetsStoreConfig, Env>,\n) => {\n const { config, logger, id } = 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 assertSheetsSettings(config.settings);\n const settings = config.settings;\n\n const creds = parseCredentials(resolveCredentials(config, logger));\n // An injected fetch (tests, simulate) carries every request, token exchange\n // included, as in the store runtime; the global fetch is the default.\n const doFetch = context.env?.fetch ?? context.config.env?.fetch ?? fetch;\n const getToken = createTokenProvider(creds, doFetch);\n const token = await getToken();\n\n const probeUrl = `${SHEETS_BASE}/${encodeURIComponent(settings.id)}?fields=spreadsheetId`;\n const probeRes = await doFetch(probeUrl, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (probeRes.status === 404) {\n throw new Error(\n `Spreadsheet not found: ${settings.id}. Run \"walkeros setup store.${id}\" to ensure the sheet exists and is shared with the service account.`,\n );\n }\n if (!probeRes.ok) {\n const text = await safeText(probeRes);\n throw new Error(\n `setup: spreadsheet probe failed (${probeRes.status}): ${text}`,\n );\n }\n\n let headersWritten = false;\n if (options.headers && options.headers.length > 0) {\n const sheet = resolveSheet(settings);\n const range = buildHeaderRange(sheet, options.headers.length);\n const url = `${SHEETS_BASE}/${encodeURIComponent(settings.id)}/values/${encodeURIComponent(range)}?valueInputOption=RAW`;\n const body = { values: [options.headers], range };\n const writeRes = await doFetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n if (!writeRes.ok) {\n const text = await safeText(writeRes);\n throw new Error(\n `setup: header write failed (${writeRes.status}): ${text}`,\n );\n }\n headersWritten = true;\n logger.info('setup: headers written', {\n spreadsheetId: settings.id,\n headers: options.headers.length,\n });\n } else {\n logger.debug('setup: spreadsheet verified, no headers configured', {\n spreadsheetId: settings.id,\n });\n }\n\n return { headersWritten };\n};\n\nfunction assertSheetsSettings(\n settings: Partial<SheetsStoreSettings> | undefined,\n): asserts settings is SheetsStoreSettings {\n if (\n !settings ||\n typeof settings.id !== 'string' ||\n settings.id.length === 0\n ) {\n throw new Error('setup: settings.id (spreadsheet ID) is required');\n }\n}\n\nfunction parseCredentials(\n credentials?: unknown,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n if (isServiceAccountShape(credentials)) return credentials;\n return undefined;\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\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text();\n } catch {\n return '';\n }\n}\n","import type { SheetsStoreSettings } from './types';\n\nexport const SHEETS_BASE = 'https://sheets.googleapis.com/v4/spreadsheets';\n\nexport function resolveSheet(settings: SheetsStoreSettings): string {\n return settings.sheet ?? 'Sheet1';\n}\n\nexport function resolveKeyColumn(settings: SheetsStoreSettings): string {\n return (settings.key ?? 'A').toUpperCase();\n}\n\nexport function resolveValueColumn(settings: SheetsStoreSettings): string {\n return (settings.value ?? 'B').toUpperCase();\n}\n\nexport function resolveHeaderRows(settings: SheetsStoreSettings): number {\n return settings.headerRows ?? 1;\n}\n\n/** Build an A1 range like 'Sheet1!A2:A' for reading a whole column from row N. */\nexport function buildColumnRange(\n sheet: string,\n column: string,\n fromRow: number,\n): string {\n return `${encodeSheet(sheet)}!${column}${fromRow}:${column}`;\n}\n\n/** Build an A1 range like 'Sheet1!B5' for a single cell. */\nexport function buildCellRange(\n sheet: string,\n column: string,\n row: number,\n): string {\n return `${encodeSheet(sheet)}!${column}${row}`;\n}\n\n/** Build an A1 range for writing the header row. */\nexport function buildHeaderRange(sheet: string, columnsCount: number): string {\n const last = columnLetter(columnsCount);\n return `${encodeSheet(sheet)}!A1:${last}1`;\n}\n\n/** Build an A1 range for appending rows across the key+value columns. */\nexport function buildAppendRange(\n sheet: string,\n keyColumn: string,\n valueColumn: string,\n): string {\n return `${encodeSheet(sheet)}!${keyColumn}:${valueColumn}`;\n}\n\n/** Convert a 1-based column index into a column letter (1 -> A, 27 -> AA). */\nexport function columnLetter(index: number): string {\n if (index < 1) {\n throw new Error(`columnLetter: index must be >=1, got ${index}`);\n }\n let n = index;\n let out = '';\n while (n > 0) {\n const rem = (n - 1) % 26;\n out = String.fromCharCode(65 + rem) + out;\n n = Math.floor((n - 1) / 26);\n }\n return out;\n}\n\n/** Sheet names with spaces or special chars must be wrapped in single quotes. */\nexport function encodeSheet(name: string): string {\n if (/^[A-Za-z0-9_]+$/.test(name)) return name;\n return `'${name.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Parse a row index out of a Sheets API `updates.updatedRange` value such as\n * `Sheet1!A5:B5` or `'My Sheet'!A5:B5`. Returns the first row number found.\n */\nexport function parseRowFromRange(range: string): number | undefined {\n const match = /![A-Z]+(\\d+)/.exec(range);\n if (!match) return undefined;\n const row = Number.parseInt(match[1], 10);\n return Number.isFinite(row) ? row : undefined;\n}\n","import type { Logger, Store } from '@walkeros/core';\nimport type {\n ServiceAccountCredentials,\n SheetsStoreSettings,\n Types,\n} from './types';\nimport { createTokenProvider, type TokenProvider } from './auth';\nimport { resolveCredentials } from './credentials';\nimport { setup as sheetsSetup } from './setup';\nimport {\n SHEETS_BASE,\n buildAppendRange,\n buildCellRange,\n buildColumnRange,\n parseRowFromRange,\n resolveHeaderRows,\n resolveKeyColumn,\n resolveSheet,\n resolveValueColumn,\n} from './setup-helpers';\n\n/**\n * Module-level cache for the spreadsheet existence pre-check, keyed by\n * spreadsheet ID. Shared across `storeSheetsInit` invocations so the same\n * spreadsheet is probed at most once per process.\n */\nconst spreadsheetExistsCache: Map<string, Promise<boolean>> = new Map();\n\n/** @internal Test-only: clear the existence cache. */\nexport function __resetSpreadsheetExistenceCache(): void {\n spreadsheetExistsCache.clear();\n}\n\n/** @internal Test-only: seed the existence cache for a spreadsheet. */\nexport function __seedSpreadsheetExists(spreadsheetId: string): void {\n spreadsheetExistsCache.set(spreadsheetId, Promise.resolve(true));\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?: unknown,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n if (isServiceAccountShape(credentials)) return credentials;\n return undefined;\n}\n\nfunction assertSheetsSettings(\n settings: Partial<SheetsStoreSettings> | undefined,\n): asserts settings is SheetsStoreSettings {\n if (\n !settings ||\n typeof settings.id !== 'string' ||\n settings.id.length === 0\n ) {\n throw new Error(\n 'storeSheetsInit: settings.id (spreadsheet ID) is required (non-empty string)',\n );\n }\n}\n\nasync function ensureSpreadsheetExists(\n spreadsheetId: string,\n storeId: string,\n getToken: TokenProvider,\n logger: Logger.Instance,\n doFetch: typeof fetch,\n): Promise<void> {\n const existing = spreadsheetExistsCache.get(spreadsheetId);\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 = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}?fields=spreadsheetId`;\n res = await doFetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch (err) {\n logger.debug('ensureSpreadsheetExists check failed (non-fatal)', {\n spreadsheetId,\n error: err instanceof Error ? err.message : String(err),\n });\n return true;\n }\n\n if (res.status === 404) {\n throw new Error(\n `Spreadsheet not found: ${spreadsheetId}. Run \"walkeros setup store.${storeId}\" to ensure the sheet exists and is shared with the service account.`,\n );\n }\n if (!res.ok) {\n logger.debug('ensureSpreadsheetExists check failed (non-fatal)', {\n spreadsheetId,\n status: res.status,\n });\n }\n return true;\n })();\n\n spreadsheetExistsCache.set(spreadsheetId, promise);\n try {\n await promise;\n } catch (err) {\n spreadsheetExistsCache.delete(spreadsheetId);\n throw err;\n }\n}\n\ninterface KeyColumnResponse {\n values?: unknown[];\n}\n\nfunction readKeyColumnRows(payload: unknown): string[] {\n if (!isRecord(payload)) return [];\n const values = (payload as KeyColumnResponse).values;\n if (!Array.isArray(values)) return [];\n const out: string[] = [];\n for (const row of values) {\n if (Array.isArray(row) && typeof row[0] === 'string') {\n out.push(row[0]);\n } else {\n out.push('');\n }\n }\n return out;\n}\n\nasync function buildKeyIndex(\n spreadsheetId: string,\n sheet: string,\n keyCol: string,\n headerRows: number,\n getToken: TokenProvider,\n doFetch: typeof fetch,\n): Promise<Map<string, number>> {\n const token = await getToken();\n const range = buildColumnRange(sheet, keyCol, headerRows + 1);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}`;\n const res = await doFetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) {\n throw new Error(\n `storeSheetsInit: failed to read key column (${res.status})`,\n );\n }\n const payload: unknown = await res.json();\n const rows = readKeyColumnRows(payload);\n const index = new Map<string, number>();\n rows.forEach((key, i) => {\n if (key) index.set(key, headerRows + i + 1);\n });\n return index;\n}\n\nasync function readCell(\n spreadsheetId: string,\n sheet: string,\n column: string,\n row: number,\n getToken: TokenProvider,\n doFetch: typeof fetch,\n): Promise<string | undefined> {\n const token = await getToken();\n const range = buildCellRange(sheet, column, row);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}`;\n const res = await doFetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n const payload: unknown = await res.json();\n if (!isRecord(payload)) return undefined;\n const values = (payload as KeyColumnResponse).values;\n if (!Array.isArray(values) || values.length === 0) return undefined;\n const first = values[0];\n if (!Array.isArray(first) || first.length === 0) return undefined;\n const cell = first[0];\n return typeof cell === 'string' ? cell : undefined;\n}\n\nasync function writeCell(\n spreadsheetId: string,\n sheet: string,\n column: string,\n row: number,\n value: string,\n getToken: TokenProvider,\n doFetch: typeof fetch,\n): Promise<void> {\n const token = await getToken();\n const range = buildCellRange(sheet, column, row);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}?valueInputOption=RAW`;\n const res = await doFetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ values: [[value]], range }),\n });\n if (!res.ok) {\n throw new Error(`storeSheets.writeCell: write failed (${res.status})`);\n }\n}\n\ninterface AppendResponse {\n updates?: { updatedRange?: unknown };\n}\n\nasync function appendRow(\n spreadsheetId: string,\n sheet: string,\n keyCol: string,\n valueCol: string,\n values: [string, string],\n getToken: TokenProvider,\n doFetch: typeof fetch,\n): Promise<number | undefined> {\n const token = await getToken();\n const range = buildAppendRange(sheet, keyCol, valueCol);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}:append?valueInputOption=RAW`;\n const res = await doFetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ values: [values] }),\n });\n if (!res.ok) {\n throw new Error(`storeSheets.appendRow: append failed (${res.status})`);\n }\n const payload: unknown = await res.json();\n if (!isRecord(payload)) return undefined;\n const updates = (payload as AppendResponse).updates;\n if (!isRecord(updates)) return undefined;\n const updatedRange = updates.updatedRange;\n if (typeof updatedRange !== 'string') return undefined;\n return parseRowFromRange(updatedRange);\n}\n\n/**\n * Recursive type guard: a JSON-derived value is structurally a `StoreValue`.\n * Sheets cells hold JSON only, so a parsed cell never carries a `Uint8Array`\n * leaf; the guard narrows the `JSON.parse` result cast-free at the boundary.\n */\nfunction isStoreValue(value: unknown): value is Store.StoreValue {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n )\n return true;\n if (Array.isArray(value)) return value.every(isStoreValue);\n if (typeof value === 'object')\n return Object.values(value).every(isStoreValue);\n return false;\n}\n\n/**\n * Recursively detect any binary leaf (Uint8Array, Buffer, or ArrayBuffer)\n * anywhere in a structured value. Buffer is a Uint8Array subclass, so the\n * single `instanceof Uint8Array` check covers it.\n */\nfunction hasBinaryLeaf(value: unknown): boolean {\n if (value instanceof Uint8Array || value instanceof ArrayBuffer) return true;\n if (Array.isArray(value)) return value.some(hasBinaryLeaf);\n if (typeof value === 'object' && value !== null)\n return Object.values(value).some(hasBinaryLeaf);\n return false;\n}\n\nexport const storeSheetsInit: Store.Init<Types> = async (context) => {\n assertSheetsSettings(context.config.settings);\n // Sheets stores structured JSON per cell; it cannot serve byte-exact raw\n // bytes. file:true is a hard error at init: pick fs, s3, or gcs instead.\n if (context.config.file) {\n throw new Error(\n 'Sheets store does not support file mode (file: true). Sheets stores structured JSON values per cell. Use fs, s3, or gcs for byte-exact serving.',\n );\n }\n const settings: SheetsStoreSettings = context.config.settings;\n const { logger } = context;\n const id = context.id;\n const creds = parseCredentials(resolveCredentials(context.config, logger));\n // An injected fetch (tests, simulate) carries every request, token exchange\n // included; the global fetch is the runtime default.\n const doFetch = context.env?.fetch ?? context.config.env?.fetch ?? fetch;\n const getToken = createTokenProvider(creds, doFetch);\n const sheet = resolveSheet(settings);\n const keyCol = resolveKeyColumn(settings);\n const valueCol = resolveValueColumn(settings);\n const headerRows = resolveHeaderRows(settings);\n\n await ensureSpreadsheetExists(settings.id, id, getToken, logger, doFetch);\n\n const keyToRow = await buildKeyIndex(\n settings.id,\n sheet,\n keyCol,\n headerRows,\n getToken,\n doFetch,\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 };\n\n return {\n type: 'sheets',\n config,\n setup: sheetsSetup,\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const row = keyToRow.get(key);\n if (row === undefined) return undefined;\n const cell = await readCell(\n settings.id,\n sheet,\n valueCol,\n row,\n getToken,\n doFetch,\n );\n if (cell === undefined || cell === '') return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(cell);\n } catch (err) {\n logger.debug('storeSheets.get: JSON parse failed', {\n key,\n error: err instanceof Error ? err.message : String(err),\n });\n return undefined;\n }\n return isStoreValue(parsed) ? parsed : undefined;\n },\n\n async set(key: string, value: Store.StoreValue): Promise<void> {\n // Sheets cells hold structured JSON only. A binary leaf anywhere in the\n // value (e.g. a request-cache encoded Buffer) would JSON.stringify into a\n // base64 blob, and the 50k-character cell limit makes that unsafe. Reject\n // any binary leaf, at any depth, loudly. Plain JSON values are unaffected.\n if (hasBinaryLeaf(value)) {\n throw new Error(\n 'Sheets store cannot persist binary values (Uint8Array). Cells hold structured JSON only; the 50k-character cell limit makes base64 binary unsafe. Use fs, s3, or gcs.',\n );\n }\n const serialized = JSON.stringify(value);\n const existingRow = keyToRow.get(key);\n if (existingRow === undefined) {\n const newRow = await appendRow(\n settings.id,\n sheet,\n keyCol,\n valueCol,\n [key, serialized],\n getToken,\n doFetch,\n );\n if (newRow !== undefined) keyToRow.set(key, newRow);\n } else {\n await writeCell(\n settings.id,\n sheet,\n valueCol,\n existingRow,\n serialized,\n getToken,\n doFetch,\n );\n }\n },\n\n async delete(key: string): Promise<void> {\n const row = keyToRow.get(key);\n if (row === undefined) return;\n await writeCell(settings.id, sheet, valueCol, row, '', getToken, doFetch);\n },\n };\n};\n"],"mappings":"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","names":["isRecord","isServiceAccountShape","parseCredentials","assertSheetsSettings"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createSign as e}from"crypto";var t="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",r="https://oauth2.googleapis.com/token",n="https://www.googleapis.com/auth/spreadsheets",o=6e4;function s(s){let
|
|
1
|
+
import{createSign as e}from"crypto";var t="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",r="https://oauth2.googleapis.com/token",n="https://www.googleapis.com/auth/spreadsheets",o=6e4;function s(s,a=fetch){let c;return async function(){return c&&Date.now()<c.expiresAt||(c=s?await async function(t,s){const a=Math.floor(Date.now()/1e3),c=function(t,r){const n=i(JSON.stringify({alg:"RS256",typ:"JWT"})),o=i(JSON.stringify(t)),s=e("RSA-SHA256");return s.update(`${n}.${o}`),`${n}.${o}.${s.sign(r,"base64url")}`}({iss:t.client_email,scope:n,aud:r,iat:a,exp:a+3600},t.private_key),u=await s(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${c}`});if(!u.ok)throw new Error(`Token exchange error: ${u.status}`);const d=await u.json();return{token:d.access_token,expiresAt:Date.now()+1e3*d.expires_in-o}}(s,a):await async function(e){const r=await e(t,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!r.ok)throw new Error(`Metadata server error: ${r.status}`);const n=await r.json();return{token:n.access_token,expiresAt:Date.now()+1e3*n.expires_in-o}}(a)),c.token}}function i(e){return("string"==typeof e?Buffer.from(e):e).toString("base64url")}function a(e,t){if(void 0!==e.credentials)return e.credentials;const r=e.settings?.credentials;return void 0!==r?(t.warn("settings.credentials is deprecated; use config.credentials"),r):void 0}import{resolveSetup as c}from"@walkeros/core";var u="https://sheets.googleapis.com/v4/spreadsheets";function d(e){return e.sheet??"Sheet1"}function f(e,t,r){return`${h(e)}!${t}${r}`}function p(e,t){const r=function(e){if(e<1)throw new Error(`columnLetter: index must be >=1, got ${e}`);let t=e,r="";for(;t>0;){const e=(t-1)%26;r=String.fromCharCode(65+e)+r,t=Math.floor((t-1)/26)}return r}(t);return`${h(e)}!A1:${r}1`}function h(e){return/^[A-Za-z0-9_]+$/.test(e)?e:`'${e.replace(/'/g,"''")}'`}var l={},g=async e=>{const{config:t,logger:r,id:n}=e,o=c(t.setup,l);if(!o)return void r.debug("setup: skipped (config.setup is false or unset)");!function(e){if(!e||"string"!=typeof e.id||0===e.id.length)throw new Error("setup: settings.id (spreadsheet ID) is required")}(t.settings);const i=t.settings,f=function(e){if(!e)return;if("string"==typeof e){let t;try{t=JSON.parse(e)}catch{return}return w(t)?t:void 0}return w(e)?e:void 0}(a(t,r)),h=e.env?.fetch??e.config.env?.fetch??fetch,g=s(f,h),v=await g(),$=`${u}/${encodeURIComponent(i.id)}?fields=spreadsheetId`,m=await h($,{headers:{Authorization:`Bearer ${v}`}});if(404===m.status)throw new Error(`Spreadsheet not found: ${i.id}. Run "walkeros setup store.${n}" to ensure the sheet exists and is shared with the service account.`);if(!m.ok){const e=await y(m);throw new Error(`setup: spreadsheet probe failed (${m.status}): ${e}`)}let A=!1;if(o.headers&&o.headers.length>0){const e=p(d(i),o.headers.length),t=`${u}/${encodeURIComponent(i.id)}/values/${encodeURIComponent(e)}?valueInputOption=RAW`,n={values:[o.headers],range:e},s=await h(t,{method:"PUT",headers:{Authorization:`Bearer ${v}`,"Content-Type":"application/json"},body:JSON.stringify(n)});if(!s.ok){const e=await y(s);throw new Error(`setup: header write failed (${s.status}): ${e}`)}A=!0,r.info("setup: headers written",{spreadsheetId:i.id,headers:o.headers.length})}else r.debug("setup: spreadsheet verified, no headers configured",{spreadsheetId:i.id});return{headersWritten:A}};function w(e){return!!function(e){return"object"==typeof e&&null!==e}(e)&&("string"==typeof e.client_email&&"string"==typeof e.private_key)}async function y(e){try{return await e.text()}catch{return""}}var v=new Map;function $(e){return"object"==typeof e&&null!==e}function m(e){return!!$(e)&&("string"==typeof e.client_email&&"string"==typeof e.private_key)}async function A(e,t,r,n,o,s){const i=await o(),a=function(e,t,r){return`${h(e)}!${t}${r}:${t}`}(t,r,n+1),c=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}`,d=await s(c,{headers:{Authorization:`Bearer ${i}`}});if(!d.ok)throw new Error(`storeSheetsInit: failed to read key column (${d.status})`);const f=function(e){if(!$(e))return[];const t=e.values;if(!Array.isArray(t))return[];const r=[];for(const e of t)Array.isArray(e)&&"string"==typeof e[0]?r.push(e[0]):r.push("");return r}(await d.json()),p=new Map;return f.forEach((e,t)=>{e&&p.set(e,n+t+1)}),p}async function S(e,t,r,n,o,s,i){const a=await s(),c=f(t,r,n),d=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(c)}?valueInputOption=RAW`,p=await i(d,{method:"PUT",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({values:[[o]],range:c})});if(!p.ok)throw new Error(`storeSheets.writeCell: write failed (${p.status})`)}async function k(e,t,r,n,o,s,i){const a=await s(),c=function(e,t,r){return`${h(e)}!${t}:${r}`}(t,r,n),d=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(c)}:append?valueInputOption=RAW`,f=await i(d,{method:"POST",headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:JSON.stringify({values:[o]})});if(!f.ok)throw new Error(`storeSheets.appendRow: append failed (${f.status})`);const p=await f.json();if(!$(p))return;const l=p.updates;if(!$(l))return;const g=l.updatedRange;return"string"==typeof g?function(e){const t=/![A-Z]+(\d+)/.exec(e);if(!t)return;const r=Number.parseInt(t[1],10);return Number.isFinite(r)?r:void 0}(g):void 0}function b(e){return null===e||"string"==typeof e||"number"==typeof e||"boolean"==typeof e||(Array.isArray(e)?e.every(b):"object"==typeof e&&Object.values(e).every(b))}function I(e){return e instanceof Uint8Array||e instanceof ArrayBuffer||(Array.isArray(e)?e.some(I):"object"==typeof e&&null!==e&&Object.values(e).some(I))}var C=async e=>{if(function(e){if(!e||"string"!=typeof e.id||0===e.id.length)throw new Error("storeSheetsInit: settings.id (spreadsheet ID) is required (non-empty string)")}(e.config.settings),e.config.file)throw new Error("Sheets store does not support file mode (file: true). Sheets stores structured JSON values per cell. Use fs, s3, or gcs for byte-exact serving.");const t=e.config.settings,{logger:r}=e,n=e.id,o=function(e){if(e){if("string"==typeof e){let t;try{t=JSON.parse(e)}catch{return}return m(t)?t:void 0}return m(e)?e:void 0}}(a(e.config,r)),i=e.env?.fetch??e.config.env?.fetch??fetch,c=s(o,i),p=d(t),h=function(e){return(e.key??"A").toUpperCase()}(t),l=function(e){return(e.value??"B").toUpperCase()}(t),w=function(e){return e.headerRows??1}(t);await async function(e,t,r,n,o){const s=v.get(e);if(void 0!==s)return void await s;const i=(async()=>{let s;try{const t=await r(),n=`${u}/${encodeURIComponent(e)}?fields=spreadsheetId`;s=await o(n,{headers:{Authorization:`Bearer ${t}`}})}catch(t){return n.debug("ensureSpreadsheetExists check failed (non-fatal)",{spreadsheetId:e,error:t instanceof Error?t.message:String(t)}),!0}if(404===s.status)throw new Error(`Spreadsheet not found: ${e}. Run "walkeros setup store.${t}" to ensure the sheet exists and is shared with the service account.`);return s.ok||n.debug("ensureSpreadsheetExists check failed (non-fatal)",{spreadsheetId:e,status:s.status}),!0})();v.set(e,i);try{await i}catch(t){throw v.delete(e),t}}(t.id,n,c,r,i);const y=await A(t.id,p,h,w,c,i);return{type:"sheets",config:{settings:t,env:e.config.env,id:e.config.id,logger:e.config.logger},setup:g,async get(e){const n=y.get(e);if(void 0===n)return;const o=await async function(e,t,r,n,o,s){const i=await o(),a=f(t,r,n),c=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}`,d=await s(c,{headers:{Authorization:`Bearer ${i}`}});if(!d.ok)return;const p=await d.json();if(!$(p))return;const h=p.values;if(!Array.isArray(h)||0===h.length)return;const l=h[0];if(!Array.isArray(l)||0===l.length)return;const g=l[0];return"string"==typeof g?g:void 0}(t.id,p,l,n,c,i);if(void 0===o||""===o)return;let s;try{s=JSON.parse(o)}catch(t){return void r.debug("storeSheets.get: JSON parse failed",{key:e,error:t instanceof Error?t.message:String(t)})}return b(s)?s:void 0},async set(e,r){if(I(r))throw new Error("Sheets store cannot persist binary values (Uint8Array). Cells hold structured JSON only; the 50k-character cell limit makes base64 binary unsafe. Use fs, s3, or gcs.");const n=JSON.stringify(r),o=y.get(e);if(void 0===o){const r=await k(t.id,p,h,l,[e,n],c,i);void 0!==r&&y.set(e,r)}else await S(t.id,p,l,o,n,c,i)},async delete(e){const r=y.get(e);void 0!==r&&await S(t.id,p,l,r,"",c,i)}}};export{l as DEFAULT_SETUP,C as default,g as setup,C as storeSheetsInit};//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth.ts","../src/credentials.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/spreadsheets';\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 { Logger } from '@walkeros/core';\n\n/**\n * Structural view of the credential-bearing parts of a store config, shared by\n * the `storeSheetsInit` and `setup` read sites (whose config generics differ:\n * the init context erases the credentials slot to `unknown`). The resolved\n * value is validated downstream by `parseCredentials`.\n */\ninterface CredentialConfig {\n credentials?: unknown;\n settings?: { credentials?: unknown };\n}\n\n/**\n * Resolve the credential value from the config, preferring the standard\n * `config.credentials` slot and falling back to the deprecated\n * `settings.credentials`. Uses `??` semantics (`!== undefined`) so an explicit\n * empty-string credential surfaces a misconfig instead of silently falling\n * through to settings/ADC.\n *\n * Warns once per instance when the deprecated `settings.credentials` path is\n * used. Call this at the init-time read site (which runs once per instance);\n * there is no module-level once-flag so a second instance still warns.\n */\nexport function resolveCredentials(\n config: CredentialConfig,\n logger: Logger.Instance,\n): unknown {\n if (config.credentials !== undefined) return config.credentials;\n\n const fromSettings = config.settings?.credentials;\n if (fromSettings !== undefined) {\n logger.warn('settings.credentials is deprecated; use config.credentials');\n return fromSettings;\n }\n\n return undefined;\n}\n","import type { LifecycleContext, SetupFn, Store } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type {\n ServiceAccountCredentials,\n Setup,\n SheetsStoreSettings,\n Types,\n} from './types';\nimport { createTokenProvider } from './auth';\nimport { resolveCredentials } from './credentials';\nimport { SHEETS_BASE, buildHeaderRange, resolveSheet } from './setup-helpers';\n\n/**\n * Default setup options. `headers` is intentionally omitted so callers must\n * opt in to writing a header row.\n */\nexport const DEFAULT_SETUP: Setup = {};\n\nexport interface SetupResult {\n headersWritten: 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 SheetsStoreConfig = Store.Config<Types>;\n\n/**\n * Provision a Google Sheets store described in the flow config.\n * Verifies the spreadsheet exists and (idempotently) writes the configured\n * `setup.headers` row. Never alters existing data: re-running with the same\n * headers is a no-op overwrite.\n */\nexport const setup: SetupFn<SheetsStoreConfig, Store.BaseEnv> = async (\n context: LifecycleContext<SheetsStoreConfig, Store.BaseEnv>,\n) => {\n const { config, logger, id } = 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 assertSheetsSettings(config.settings);\n const settings = config.settings;\n\n const creds = parseCredentials(resolveCredentials(config, logger));\n const getToken = createTokenProvider(creds);\n const token = await getToken();\n\n const probeUrl = `${SHEETS_BASE}/${encodeURIComponent(settings.id)}?fields=spreadsheetId`;\n const probeRes = await fetch(probeUrl, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (probeRes.status === 404) {\n throw new Error(\n `Spreadsheet not found: ${settings.id}. Run \"walkeros setup store.${id}\" to ensure the sheet exists and is shared with the service account.`,\n );\n }\n if (!probeRes.ok) {\n const text = await safeText(probeRes);\n throw new Error(\n `setup: spreadsheet probe failed (${probeRes.status}): ${text}`,\n );\n }\n\n let headersWritten = false;\n if (options.headers && options.headers.length > 0) {\n const sheet = resolveSheet(settings);\n const range = buildHeaderRange(sheet, options.headers.length);\n const url = `${SHEETS_BASE}/${encodeURIComponent(settings.id)}/values/${encodeURIComponent(range)}?valueInputOption=RAW`;\n const body = { values: [options.headers], range };\n const writeRes = await fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n if (!writeRes.ok) {\n const text = await safeText(writeRes);\n throw new Error(\n `setup: header write failed (${writeRes.status}): ${text}`,\n );\n }\n headersWritten = true;\n logger.info('setup: headers written', {\n spreadsheetId: settings.id,\n headers: options.headers.length,\n });\n } else {\n logger.debug('setup: spreadsheet verified, no headers configured', {\n spreadsheetId: settings.id,\n });\n }\n\n return { headersWritten };\n};\n\nfunction assertSheetsSettings(\n settings: Partial<SheetsStoreSettings> | undefined,\n): asserts settings is SheetsStoreSettings {\n if (\n !settings ||\n typeof settings.id !== 'string' ||\n settings.id.length === 0\n ) {\n throw new Error('setup: settings.id (spreadsheet ID) is required');\n }\n}\n\nfunction parseCredentials(\n credentials?: unknown,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n if (isServiceAccountShape(credentials)) return credentials;\n return undefined;\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\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text();\n } catch {\n return '';\n }\n}\n","import type { SheetsStoreSettings } from './types';\n\nexport const SHEETS_BASE = 'https://sheets.googleapis.com/v4/spreadsheets';\n\nexport function resolveSheet(settings: SheetsStoreSettings): string {\n return settings.sheet ?? 'Sheet1';\n}\n\nexport function resolveKeyColumn(settings: SheetsStoreSettings): string {\n return (settings.key ?? 'A').toUpperCase();\n}\n\nexport function resolveValueColumn(settings: SheetsStoreSettings): string {\n return (settings.value ?? 'B').toUpperCase();\n}\n\nexport function resolveHeaderRows(settings: SheetsStoreSettings): number {\n return settings.headerRows ?? 1;\n}\n\n/** Build an A1 range like 'Sheet1!A2:A' for reading a whole column from row N. */\nexport function buildColumnRange(\n sheet: string,\n column: string,\n fromRow: number,\n): string {\n return `${encodeSheet(sheet)}!${column}${fromRow}:${column}`;\n}\n\n/** Build an A1 range like 'Sheet1!B5' for a single cell. */\nexport function buildCellRange(\n sheet: string,\n column: string,\n row: number,\n): string {\n return `${encodeSheet(sheet)}!${column}${row}`;\n}\n\n/** Build an A1 range for writing the header row. */\nexport function buildHeaderRange(sheet: string, columnsCount: number): string {\n const last = columnLetter(columnsCount);\n return `${encodeSheet(sheet)}!A1:${last}1`;\n}\n\n/** Build an A1 range for appending rows across the key+value columns. */\nexport function buildAppendRange(\n sheet: string,\n keyColumn: string,\n valueColumn: string,\n): string {\n return `${encodeSheet(sheet)}!${keyColumn}:${valueColumn}`;\n}\n\n/** Convert a 1-based column index into a column letter (1 -> A, 27 -> AA). */\nexport function columnLetter(index: number): string {\n if (index < 1) {\n throw new Error(`columnLetter: index must be >=1, got ${index}`);\n }\n let n = index;\n let out = '';\n while (n > 0) {\n const rem = (n - 1) % 26;\n out = String.fromCharCode(65 + rem) + out;\n n = Math.floor((n - 1) / 26);\n }\n return out;\n}\n\n/** Sheet names with spaces or special chars must be wrapped in single quotes. */\nexport function encodeSheet(name: string): string {\n if (/^[A-Za-z0-9_]+$/.test(name)) return name;\n return `'${name.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Parse a row index out of a Sheets API `updates.updatedRange` value such as\n * `Sheet1!A5:B5` or `'My Sheet'!A5:B5`. Returns the first row number found.\n */\nexport function parseRowFromRange(range: string): number | undefined {\n const match = /![A-Z]+(\\d+)/.exec(range);\n if (!match) return undefined;\n const row = Number.parseInt(match[1], 10);\n return Number.isFinite(row) ? row : undefined;\n}\n","import type { Logger, Store } from '@walkeros/core';\nimport type {\n ServiceAccountCredentials,\n SheetsStoreSettings,\n Types,\n} from './types';\nimport { createTokenProvider, type TokenProvider } from './auth';\nimport { resolveCredentials } from './credentials';\nimport { setup as sheetsSetup } from './setup';\nimport {\n SHEETS_BASE,\n buildAppendRange,\n buildCellRange,\n buildColumnRange,\n parseRowFromRange,\n resolveHeaderRows,\n resolveKeyColumn,\n resolveSheet,\n resolveValueColumn,\n} from './setup-helpers';\n\n/**\n * Module-level cache for the spreadsheet existence pre-check, keyed by\n * spreadsheet ID. Shared across `storeSheetsInit` invocations so the same\n * spreadsheet is probed at most once per process.\n */\nconst spreadsheetExistsCache: Map<string, Promise<boolean>> = new Map();\n\n/** @internal Test-only: clear the existence cache. */\nexport function __resetSpreadsheetExistenceCache(): void {\n spreadsheetExistsCache.clear();\n}\n\n/** @internal Test-only: seed the existence cache for a spreadsheet. */\nexport function __seedSpreadsheetExists(spreadsheetId: string): void {\n spreadsheetExistsCache.set(spreadsheetId, Promise.resolve(true));\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?: unknown,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n if (isServiceAccountShape(credentials)) return credentials;\n return undefined;\n}\n\nfunction assertSheetsSettings(\n settings: Partial<SheetsStoreSettings> | undefined,\n): asserts settings is SheetsStoreSettings {\n if (\n !settings ||\n typeof settings.id !== 'string' ||\n settings.id.length === 0\n ) {\n throw new Error(\n 'storeSheetsInit: settings.id (spreadsheet ID) is required (non-empty string)',\n );\n }\n}\n\nasync function ensureSpreadsheetExists(\n spreadsheetId: string,\n storeId: string,\n getToken: TokenProvider,\n logger: Logger.Instance,\n): Promise<void> {\n const existing = spreadsheetExistsCache.get(spreadsheetId);\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 = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}?fields=spreadsheetId`;\n res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch (err) {\n logger.debug('ensureSpreadsheetExists check failed (non-fatal)', {\n spreadsheetId,\n error: err instanceof Error ? err.message : String(err),\n });\n return true;\n }\n\n if (res.status === 404) {\n throw new Error(\n `Spreadsheet not found: ${spreadsheetId}. Run \"walkeros setup store.${storeId}\" to ensure the sheet exists and is shared with the service account.`,\n );\n }\n if (!res.ok) {\n logger.debug('ensureSpreadsheetExists check failed (non-fatal)', {\n spreadsheetId,\n status: res.status,\n });\n }\n return true;\n })();\n\n spreadsheetExistsCache.set(spreadsheetId, promise);\n try {\n await promise;\n } catch (err) {\n spreadsheetExistsCache.delete(spreadsheetId);\n throw err;\n }\n}\n\ninterface KeyColumnResponse {\n values?: unknown[];\n}\n\nfunction readKeyColumnRows(payload: unknown): string[] {\n if (!isRecord(payload)) return [];\n const values = (payload as KeyColumnResponse).values;\n if (!Array.isArray(values)) return [];\n const out: string[] = [];\n for (const row of values) {\n if (Array.isArray(row) && typeof row[0] === 'string') {\n out.push(row[0]);\n } else {\n out.push('');\n }\n }\n return out;\n}\n\nasync function buildKeyIndex(\n spreadsheetId: string,\n sheet: string,\n keyCol: string,\n headerRows: number,\n getToken: TokenProvider,\n): Promise<Map<string, number>> {\n const token = await getToken();\n const range = buildColumnRange(sheet, keyCol, headerRows + 1);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) {\n throw new Error(\n `storeSheetsInit: failed to read key column (${res.status})`,\n );\n }\n const payload: unknown = await res.json();\n const rows = readKeyColumnRows(payload);\n const index = new Map<string, number>();\n rows.forEach((key, i) => {\n if (key) index.set(key, headerRows + i + 1);\n });\n return index;\n}\n\nasync function readCell(\n spreadsheetId: string,\n sheet: string,\n column: string,\n row: number,\n getToken: TokenProvider,\n): Promise<string | undefined> {\n const token = await getToken();\n const range = buildCellRange(sheet, column, row);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n const payload: unknown = await res.json();\n if (!isRecord(payload)) return undefined;\n const values = (payload as KeyColumnResponse).values;\n if (!Array.isArray(values) || values.length === 0) return undefined;\n const first = values[0];\n if (!Array.isArray(first) || first.length === 0) return undefined;\n const cell = first[0];\n return typeof cell === 'string' ? cell : undefined;\n}\n\nasync function writeCell(\n spreadsheetId: string,\n sheet: string,\n column: string,\n row: number,\n value: string,\n getToken: TokenProvider,\n): Promise<void> {\n const token = await getToken();\n const range = buildCellRange(sheet, column, row);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}?valueInputOption=RAW`;\n const res = await fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ values: [[value]], range }),\n });\n if (!res.ok) {\n throw new Error(`storeSheets.writeCell: write failed (${res.status})`);\n }\n}\n\ninterface AppendResponse {\n updates?: { updatedRange?: unknown };\n}\n\nasync function appendRow(\n spreadsheetId: string,\n sheet: string,\n keyCol: string,\n valueCol: string,\n values: [string, string],\n getToken: TokenProvider,\n): Promise<number | undefined> {\n const token = await getToken();\n const range = buildAppendRange(sheet, keyCol, valueCol);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}:append?valueInputOption=RAW`;\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ values: [values] }),\n });\n if (!res.ok) {\n throw new Error(`storeSheets.appendRow: append failed (${res.status})`);\n }\n const payload: unknown = await res.json();\n if (!isRecord(payload)) return undefined;\n const updates = (payload as AppendResponse).updates;\n if (!isRecord(updates)) return undefined;\n const updatedRange = updates.updatedRange;\n if (typeof updatedRange !== 'string') return undefined;\n return parseRowFromRange(updatedRange);\n}\n\n/**\n * Recursive type guard: a JSON-derived value is structurally a `StoreValue`.\n * Sheets cells hold JSON only, so a parsed cell never carries a `Uint8Array`\n * leaf; the guard narrows the `JSON.parse` result cast-free at the boundary.\n */\nfunction isStoreValue(value: unknown): value is Store.StoreValue {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n )\n return true;\n if (Array.isArray(value)) return value.every(isStoreValue);\n if (typeof value === 'object')\n return Object.values(value).every(isStoreValue);\n return false;\n}\n\n/**\n * Recursively detect any binary leaf (Uint8Array, Buffer, or ArrayBuffer)\n * anywhere in a structured value. Buffer is a Uint8Array subclass, so the\n * single `instanceof Uint8Array` check covers it.\n */\nfunction hasBinaryLeaf(value: unknown): boolean {\n if (value instanceof Uint8Array || value instanceof ArrayBuffer) return true;\n if (Array.isArray(value)) return value.some(hasBinaryLeaf);\n if (typeof value === 'object' && value !== null)\n return Object.values(value).some(hasBinaryLeaf);\n return false;\n}\n\nexport const storeSheetsInit: Store.Init<Types> = async (context) => {\n assertSheetsSettings(context.config.settings);\n // Sheets stores structured JSON per cell; it cannot serve byte-exact raw\n // bytes. file:true is a hard error at init: pick fs, s3, or gcs instead.\n if (context.config.file) {\n throw new Error(\n 'Sheets store does not support file mode (file: true). Sheets stores structured JSON values per cell. Use fs, s3, or gcs for byte-exact serving.',\n );\n }\n const settings: SheetsStoreSettings = context.config.settings;\n const { logger } = context;\n const id = context.id;\n const creds = parseCredentials(resolveCredentials(context.config, logger));\n const getToken = createTokenProvider(creds);\n const sheet = resolveSheet(settings);\n const keyCol = resolveKeyColumn(settings);\n const valueCol = resolveValueColumn(settings);\n const headerRows = resolveHeaderRows(settings);\n\n await ensureSpreadsheetExists(settings.id, id, getToken, logger);\n\n const keyToRow = await buildKeyIndex(\n settings.id,\n sheet,\n keyCol,\n headerRows,\n getToken,\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 };\n\n return {\n type: 'sheets',\n config,\n setup: sheetsSetup,\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const row = keyToRow.get(key);\n if (row === undefined) return undefined;\n const cell = await readCell(settings.id, sheet, valueCol, row, getToken);\n if (cell === undefined || cell === '') return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(cell);\n } catch (err) {\n logger.debug('storeSheets.get: JSON parse failed', {\n key,\n error: err instanceof Error ? err.message : String(err),\n });\n return undefined;\n }\n return isStoreValue(parsed) ? parsed : undefined;\n },\n\n async set(key: string, value: Store.StoreValue): Promise<void> {\n // Sheets cells hold structured JSON only. A binary leaf anywhere in the\n // value (e.g. a request-cache encoded Buffer) would JSON.stringify into a\n // base64 blob, and the 50k-character cell limit makes that unsafe. Reject\n // any binary leaf, at any depth, loudly. Plain JSON values are unaffected.\n if (hasBinaryLeaf(value)) {\n throw new Error(\n 'Sheets store cannot persist binary values (Uint8Array). Cells hold structured JSON only; the 50k-character cell limit makes base64 binary unsafe. Use fs, s3, or gcs.',\n );\n }\n const serialized = JSON.stringify(value);\n const existingRow = keyToRow.get(key);\n if (existingRow === undefined) {\n const newRow = await appendRow(\n settings.id,\n sheet,\n keyCol,\n valueCol,\n [key, serialized],\n getToken,\n );\n if (newRow !== undefined) keyToRow.set(key, newRow);\n } else {\n await writeCell(\n settings.id,\n sheet,\n valueCol,\n existingRow,\n serialized,\n getToken,\n );\n }\n },\n\n async delete(key: string): Promise<void> {\n const row = keyToRow.get(key);\n if (row === undefined) return;\n await writeCell(settings.id, sheet, valueCol, row, '', getToken);\n },\n };\n};\n"],"mappings":"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","names":["isRecord","isServiceAccountShape","parseCredentials","assertSheetsSettings"]}
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts","../src/credentials.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/spreadsheets';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n doFetch: typeof fetch = fetch,\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, doFetch)\n : await fetchMetadataToken(doFetch);\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(doFetch: typeof fetch): Promise<CachedToken> {\n const res = await doFetch(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 doFetch: typeof fetch,\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 doFetch(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 { Logger } from '@walkeros/core';\n\n/**\n * Structural view of the credential-bearing parts of a store config, shared by\n * the `storeSheetsInit` and `setup` read sites (whose config generics differ:\n * the init context erases the credentials slot to `unknown`). The resolved\n * value is validated downstream by `parseCredentials`.\n */\ninterface CredentialConfig {\n credentials?: unknown;\n settings?: { credentials?: unknown };\n}\n\n/**\n * Resolve the credential value from the config, preferring the standard\n * `config.credentials` slot and falling back to the deprecated\n * `settings.credentials`. Uses `??` semantics (`!== undefined`) so an explicit\n * empty-string credential surfaces a misconfig instead of silently falling\n * through to settings/ADC.\n *\n * Warns once per instance when the deprecated `settings.credentials` path is\n * used. Call this at the init-time read site (which runs once per instance);\n * there is no module-level once-flag so a second instance still warns.\n */\nexport function resolveCredentials(\n config: CredentialConfig,\n logger: Logger.Instance,\n): unknown {\n if (config.credentials !== undefined) return config.credentials;\n\n const fromSettings = config.settings?.credentials;\n if (fromSettings !== undefined) {\n logger.warn('settings.credentials is deprecated; use config.credentials');\n return fromSettings;\n }\n\n return undefined;\n}\n","import type { LifecycleContext, SetupFn, Store } from '@walkeros/core';\nimport { resolveSetup } from '@walkeros/core';\nimport type {\n Env,\n ServiceAccountCredentials,\n Setup,\n SheetsStoreSettings,\n Types,\n} from './types';\nimport { createTokenProvider } from './auth';\nimport { resolveCredentials } from './credentials';\nimport { SHEETS_BASE, buildHeaderRange, resolveSheet } from './setup-helpers';\n\n/**\n * Default setup options. `headers` is intentionally omitted so callers must\n * opt in to writing a header row.\n */\nexport const DEFAULT_SETUP: Setup = {};\n\nexport interface SetupResult {\n headersWritten: 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 SheetsStoreConfig = Store.Config<Types>;\n\n/**\n * Provision a Google Sheets store described in the flow config.\n * Verifies the spreadsheet exists and (idempotently) writes the configured\n * `setup.headers` row. Never alters existing data: re-running with the same\n * headers is a no-op overwrite.\n */\nexport const setup: SetupFn<SheetsStoreConfig, Env> = async (\n context: LifecycleContext<SheetsStoreConfig, Env>,\n) => {\n const { config, logger, id } = 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 assertSheetsSettings(config.settings);\n const settings = config.settings;\n\n const creds = parseCredentials(resolveCredentials(config, logger));\n // An injected fetch (tests, simulate) carries every request, token exchange\n // included, as in the store runtime; the global fetch is the default.\n const doFetch = context.env?.fetch ?? context.config.env?.fetch ?? fetch;\n const getToken = createTokenProvider(creds, doFetch);\n const token = await getToken();\n\n const probeUrl = `${SHEETS_BASE}/${encodeURIComponent(settings.id)}?fields=spreadsheetId`;\n const probeRes = await doFetch(probeUrl, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (probeRes.status === 404) {\n throw new Error(\n `Spreadsheet not found: ${settings.id}. Run \"walkeros setup store.${id}\" to ensure the sheet exists and is shared with the service account.`,\n );\n }\n if (!probeRes.ok) {\n const text = await safeText(probeRes);\n throw new Error(\n `setup: spreadsheet probe failed (${probeRes.status}): ${text}`,\n );\n }\n\n let headersWritten = false;\n if (options.headers && options.headers.length > 0) {\n const sheet = resolveSheet(settings);\n const range = buildHeaderRange(sheet, options.headers.length);\n const url = `${SHEETS_BASE}/${encodeURIComponent(settings.id)}/values/${encodeURIComponent(range)}?valueInputOption=RAW`;\n const body = { values: [options.headers], range };\n const writeRes = await doFetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n if (!writeRes.ok) {\n const text = await safeText(writeRes);\n throw new Error(\n `setup: header write failed (${writeRes.status}): ${text}`,\n );\n }\n headersWritten = true;\n logger.info('setup: headers written', {\n spreadsheetId: settings.id,\n headers: options.headers.length,\n });\n } else {\n logger.debug('setup: spreadsheet verified, no headers configured', {\n spreadsheetId: settings.id,\n });\n }\n\n return { headersWritten };\n};\n\nfunction assertSheetsSettings(\n settings: Partial<SheetsStoreSettings> | undefined,\n): asserts settings is SheetsStoreSettings {\n if (\n !settings ||\n typeof settings.id !== 'string' ||\n settings.id.length === 0\n ) {\n throw new Error('setup: settings.id (spreadsheet ID) is required');\n }\n}\n\nfunction parseCredentials(\n credentials?: unknown,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n if (isServiceAccountShape(credentials)) return credentials;\n return undefined;\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\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text();\n } catch {\n return '';\n }\n}\n","import type { SheetsStoreSettings } from './types';\n\nexport const SHEETS_BASE = 'https://sheets.googleapis.com/v4/spreadsheets';\n\nexport function resolveSheet(settings: SheetsStoreSettings): string {\n return settings.sheet ?? 'Sheet1';\n}\n\nexport function resolveKeyColumn(settings: SheetsStoreSettings): string {\n return (settings.key ?? 'A').toUpperCase();\n}\n\nexport function resolveValueColumn(settings: SheetsStoreSettings): string {\n return (settings.value ?? 'B').toUpperCase();\n}\n\nexport function resolveHeaderRows(settings: SheetsStoreSettings): number {\n return settings.headerRows ?? 1;\n}\n\n/** Build an A1 range like 'Sheet1!A2:A' for reading a whole column from row N. */\nexport function buildColumnRange(\n sheet: string,\n column: string,\n fromRow: number,\n): string {\n return `${encodeSheet(sheet)}!${column}${fromRow}:${column}`;\n}\n\n/** Build an A1 range like 'Sheet1!B5' for a single cell. */\nexport function buildCellRange(\n sheet: string,\n column: string,\n row: number,\n): string {\n return `${encodeSheet(sheet)}!${column}${row}`;\n}\n\n/** Build an A1 range for writing the header row. */\nexport function buildHeaderRange(sheet: string, columnsCount: number): string {\n const last = columnLetter(columnsCount);\n return `${encodeSheet(sheet)}!A1:${last}1`;\n}\n\n/** Build an A1 range for appending rows across the key+value columns. */\nexport function buildAppendRange(\n sheet: string,\n keyColumn: string,\n valueColumn: string,\n): string {\n return `${encodeSheet(sheet)}!${keyColumn}:${valueColumn}`;\n}\n\n/** Convert a 1-based column index into a column letter (1 -> A, 27 -> AA). */\nexport function columnLetter(index: number): string {\n if (index < 1) {\n throw new Error(`columnLetter: index must be >=1, got ${index}`);\n }\n let n = index;\n let out = '';\n while (n > 0) {\n const rem = (n - 1) % 26;\n out = String.fromCharCode(65 + rem) + out;\n n = Math.floor((n - 1) / 26);\n }\n return out;\n}\n\n/** Sheet names with spaces or special chars must be wrapped in single quotes. */\nexport function encodeSheet(name: string): string {\n if (/^[A-Za-z0-9_]+$/.test(name)) return name;\n return `'${name.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Parse a row index out of a Sheets API `updates.updatedRange` value such as\n * `Sheet1!A5:B5` or `'My Sheet'!A5:B5`. Returns the first row number found.\n */\nexport function parseRowFromRange(range: string): number | undefined {\n const match = /![A-Z]+(\\d+)/.exec(range);\n if (!match) return undefined;\n const row = Number.parseInt(match[1], 10);\n return Number.isFinite(row) ? row : undefined;\n}\n","import type { Logger, Store } from '@walkeros/core';\nimport type {\n ServiceAccountCredentials,\n SheetsStoreSettings,\n Types,\n} from './types';\nimport { createTokenProvider, type TokenProvider } from './auth';\nimport { resolveCredentials } from './credentials';\nimport { setup as sheetsSetup } from './setup';\nimport {\n SHEETS_BASE,\n buildAppendRange,\n buildCellRange,\n buildColumnRange,\n parseRowFromRange,\n resolveHeaderRows,\n resolveKeyColumn,\n resolveSheet,\n resolveValueColumn,\n} from './setup-helpers';\n\n/**\n * Module-level cache for the spreadsheet existence pre-check, keyed by\n * spreadsheet ID. Shared across `storeSheetsInit` invocations so the same\n * spreadsheet is probed at most once per process.\n */\nconst spreadsheetExistsCache: Map<string, Promise<boolean>> = new Map();\n\n/** @internal Test-only: clear the existence cache. */\nexport function __resetSpreadsheetExistenceCache(): void {\n spreadsheetExistsCache.clear();\n}\n\n/** @internal Test-only: seed the existence cache for a spreadsheet. */\nexport function __seedSpreadsheetExists(spreadsheetId: string): void {\n spreadsheetExistsCache.set(spreadsheetId, Promise.resolve(true));\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?: unknown,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') {\n let parsed: unknown;\n try {\n parsed = JSON.parse(credentials);\n } catch {\n return undefined;\n }\n if (isServiceAccountShape(parsed)) return parsed;\n return undefined;\n }\n if (isServiceAccountShape(credentials)) return credentials;\n return undefined;\n}\n\nfunction assertSheetsSettings(\n settings: Partial<SheetsStoreSettings> | undefined,\n): asserts settings is SheetsStoreSettings {\n if (\n !settings ||\n typeof settings.id !== 'string' ||\n settings.id.length === 0\n ) {\n throw new Error(\n 'storeSheetsInit: settings.id (spreadsheet ID) is required (non-empty string)',\n );\n }\n}\n\nasync function ensureSpreadsheetExists(\n spreadsheetId: string,\n storeId: string,\n getToken: TokenProvider,\n logger: Logger.Instance,\n doFetch: typeof fetch,\n): Promise<void> {\n const existing = spreadsheetExistsCache.get(spreadsheetId);\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 = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}?fields=spreadsheetId`;\n res = await doFetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch (err) {\n logger.debug('ensureSpreadsheetExists check failed (non-fatal)', {\n spreadsheetId,\n error: err instanceof Error ? err.message : String(err),\n });\n return true;\n }\n\n if (res.status === 404) {\n throw new Error(\n `Spreadsheet not found: ${spreadsheetId}. Run \"walkeros setup store.${storeId}\" to ensure the sheet exists and is shared with the service account.`,\n );\n }\n if (!res.ok) {\n logger.debug('ensureSpreadsheetExists check failed (non-fatal)', {\n spreadsheetId,\n status: res.status,\n });\n }\n return true;\n })();\n\n spreadsheetExistsCache.set(spreadsheetId, promise);\n try {\n await promise;\n } catch (err) {\n spreadsheetExistsCache.delete(spreadsheetId);\n throw err;\n }\n}\n\ninterface KeyColumnResponse {\n values?: unknown[];\n}\n\nfunction readKeyColumnRows(payload: unknown): string[] {\n if (!isRecord(payload)) return [];\n const values = (payload as KeyColumnResponse).values;\n if (!Array.isArray(values)) return [];\n const out: string[] = [];\n for (const row of values) {\n if (Array.isArray(row) && typeof row[0] === 'string') {\n out.push(row[0]);\n } else {\n out.push('');\n }\n }\n return out;\n}\n\nasync function buildKeyIndex(\n spreadsheetId: string,\n sheet: string,\n keyCol: string,\n headerRows: number,\n getToken: TokenProvider,\n doFetch: typeof fetch,\n): Promise<Map<string, number>> {\n const token = await getToken();\n const range = buildColumnRange(sheet, keyCol, headerRows + 1);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}`;\n const res = await doFetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) {\n throw new Error(\n `storeSheetsInit: failed to read key column (${res.status})`,\n );\n }\n const payload: unknown = await res.json();\n const rows = readKeyColumnRows(payload);\n const index = new Map<string, number>();\n rows.forEach((key, i) => {\n if (key) index.set(key, headerRows + i + 1);\n });\n return index;\n}\n\nasync function readCell(\n spreadsheetId: string,\n sheet: string,\n column: string,\n row: number,\n getToken: TokenProvider,\n doFetch: typeof fetch,\n): Promise<string | undefined> {\n const token = await getToken();\n const range = buildCellRange(sheet, column, row);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}`;\n const res = await doFetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n const payload: unknown = await res.json();\n if (!isRecord(payload)) return undefined;\n const values = (payload as KeyColumnResponse).values;\n if (!Array.isArray(values) || values.length === 0) return undefined;\n const first = values[0];\n if (!Array.isArray(first) || first.length === 0) return undefined;\n const cell = first[0];\n return typeof cell === 'string' ? cell : undefined;\n}\n\nasync function writeCell(\n spreadsheetId: string,\n sheet: string,\n column: string,\n row: number,\n value: string,\n getToken: TokenProvider,\n doFetch: typeof fetch,\n): Promise<void> {\n const token = await getToken();\n const range = buildCellRange(sheet, column, row);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}?valueInputOption=RAW`;\n const res = await doFetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ values: [[value]], range }),\n });\n if (!res.ok) {\n throw new Error(`storeSheets.writeCell: write failed (${res.status})`);\n }\n}\n\ninterface AppendResponse {\n updates?: { updatedRange?: unknown };\n}\n\nasync function appendRow(\n spreadsheetId: string,\n sheet: string,\n keyCol: string,\n valueCol: string,\n values: [string, string],\n getToken: TokenProvider,\n doFetch: typeof fetch,\n): Promise<number | undefined> {\n const token = await getToken();\n const range = buildAppendRange(sheet, keyCol, valueCol);\n const url = `${SHEETS_BASE}/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(range)}:append?valueInputOption=RAW`;\n const res = await doFetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ values: [values] }),\n });\n if (!res.ok) {\n throw new Error(`storeSheets.appendRow: append failed (${res.status})`);\n }\n const payload: unknown = await res.json();\n if (!isRecord(payload)) return undefined;\n const updates = (payload as AppendResponse).updates;\n if (!isRecord(updates)) return undefined;\n const updatedRange = updates.updatedRange;\n if (typeof updatedRange !== 'string') return undefined;\n return parseRowFromRange(updatedRange);\n}\n\n/**\n * Recursive type guard: a JSON-derived value is structurally a `StoreValue`.\n * Sheets cells hold JSON only, so a parsed cell never carries a `Uint8Array`\n * leaf; the guard narrows the `JSON.parse` result cast-free at the boundary.\n */\nfunction isStoreValue(value: unknown): value is Store.StoreValue {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n )\n return true;\n if (Array.isArray(value)) return value.every(isStoreValue);\n if (typeof value === 'object')\n return Object.values(value).every(isStoreValue);\n return false;\n}\n\n/**\n * Recursively detect any binary leaf (Uint8Array, Buffer, or ArrayBuffer)\n * anywhere in a structured value. Buffer is a Uint8Array subclass, so the\n * single `instanceof Uint8Array` check covers it.\n */\nfunction hasBinaryLeaf(value: unknown): boolean {\n if (value instanceof Uint8Array || value instanceof ArrayBuffer) return true;\n if (Array.isArray(value)) return value.some(hasBinaryLeaf);\n if (typeof value === 'object' && value !== null)\n return Object.values(value).some(hasBinaryLeaf);\n return false;\n}\n\nexport const storeSheetsInit: Store.Init<Types> = async (context) => {\n assertSheetsSettings(context.config.settings);\n // Sheets stores structured JSON per cell; it cannot serve byte-exact raw\n // bytes. file:true is a hard error at init: pick fs, s3, or gcs instead.\n if (context.config.file) {\n throw new Error(\n 'Sheets store does not support file mode (file: true). Sheets stores structured JSON values per cell. Use fs, s3, or gcs for byte-exact serving.',\n );\n }\n const settings: SheetsStoreSettings = context.config.settings;\n const { logger } = context;\n const id = context.id;\n const creds = parseCredentials(resolveCredentials(context.config, logger));\n // An injected fetch (tests, simulate) carries every request, token exchange\n // included; the global fetch is the runtime default.\n const doFetch = context.env?.fetch ?? context.config.env?.fetch ?? fetch;\n const getToken = createTokenProvider(creds, doFetch);\n const sheet = resolveSheet(settings);\n const keyCol = resolveKeyColumn(settings);\n const valueCol = resolveValueColumn(settings);\n const headerRows = resolveHeaderRows(settings);\n\n await ensureSpreadsheetExists(settings.id, id, getToken, logger, doFetch);\n\n const keyToRow = await buildKeyIndex(\n settings.id,\n sheet,\n keyCol,\n headerRows,\n getToken,\n doFetch,\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 };\n\n return {\n type: 'sheets',\n config,\n setup: sheetsSetup,\n\n async get(key: string): Promise<Store.StoreValue | undefined> {\n const row = keyToRow.get(key);\n if (row === undefined) return undefined;\n const cell = await readCell(\n settings.id,\n sheet,\n valueCol,\n row,\n getToken,\n doFetch,\n );\n if (cell === undefined || cell === '') return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(cell);\n } catch (err) {\n logger.debug('storeSheets.get: JSON parse failed', {\n key,\n error: err instanceof Error ? err.message : String(err),\n });\n return undefined;\n }\n return isStoreValue(parsed) ? parsed : undefined;\n },\n\n async set(key: string, value: Store.StoreValue): Promise<void> {\n // Sheets cells hold structured JSON only. A binary leaf anywhere in the\n // value (e.g. a request-cache encoded Buffer) would JSON.stringify into a\n // base64 blob, and the 50k-character cell limit makes that unsafe. Reject\n // any binary leaf, at any depth, loudly. Plain JSON values are unaffected.\n if (hasBinaryLeaf(value)) {\n throw new Error(\n 'Sheets store cannot persist binary values (Uint8Array). Cells hold structured JSON only; the 50k-character cell limit makes base64 binary unsafe. Use fs, s3, or gcs.',\n );\n }\n const serialized = JSON.stringify(value);\n const existingRow = keyToRow.get(key);\n if (existingRow === undefined) {\n const newRow = await appendRow(\n settings.id,\n sheet,\n keyCol,\n valueCol,\n [key, serialized],\n getToken,\n doFetch,\n );\n if (newRow !== undefined) keyToRow.set(key, newRow);\n } else {\n await writeCell(\n settings.id,\n sheet,\n valueCol,\n existingRow,\n serialized,\n getToken,\n doFetch,\n );\n }\n },\n\n async delete(key: string): Promise<void> {\n const row = keyToRow.get(key);\n if (row === undefined) return;\n await writeCell(settings.id, sheet, valueCol, row, '', getToken, doFetch);\n },\n };\n};\n"],"mappings":"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","names":["isRecord","isServiceAccountShape","parseCredentials","assertSheetsSettings"]}
|
package/dist/walkerOS.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$meta": {
|
|
3
3
|
"package": "@walkeros/server-store-sheets",
|
|
4
|
-
"version": "4.7.0
|
|
4
|
+
"version": "4.7.0",
|
|
5
5
|
"type": "store",
|
|
6
6
|
"platform": [
|
|
7
7
|
"server"
|
|
8
8
|
],
|
|
9
|
+
"exports": {},
|
|
9
10
|
"docs": "https://www.walkeros.io/docs/stores/server/sheets",
|
|
10
11
|
"source": "https://github.com/elbwalker/walkerOS/tree/main/packages/server/stores/sheets/src"
|
|
11
12
|
},
|
|
@@ -96,6 +97,19 @@
|
|
|
96
97
|
"sheet": "Customers"
|
|
97
98
|
}
|
|
98
99
|
},
|
|
100
|
+
"env": {
|
|
101
|
+
"createSheetsFetch": {
|
|
102
|
+
"$code": "function g(e=[[\"alice\",'{\"tier\":\"gold\"}']]){const t=new Map([[\"A1\",\"key\"],[\"B1\",\"value\"]]);e.forEach(([e,r],s)=>{t.set(`A${s+2}`,e),t.set(`B${s+2}`,r)});const r=()=>{let e=0;for(const r of t.keys()){const t=Number.parseInt(r.replace(/^[A-Z]+/,\"\"),10);t>e&&(e=t)}return e};return async(e,s)=>{const n=function(e){return\"string\"==typeof e?e:e instanceof URL?e.toString():e.url}(e),i=s?.method??\"GET\";if(n.startsWith(\"https://oauth2.googleapis.com/token\"))return p(h);if(n.startsWith(\"http://metadata.google.internal/\"))return p(h);const o=/\\/spreadsheets\\/([^/?]+)\\/values\\/([^?]+)/.exec(n);if(!o){const e=/\\/spreadsheets\\/([^/?]+)\\?/.exec(n);return e?p({spreadsheetId:e[1]}):p({error:{code:404}},404)}const a=decodeURIComponent(o[2]),c=/!([A-Z]+):([A-Z]+):append$/.exec(a);if(c&&\"POST\"===i){const e=r()+1,[n=\"\",i=\"\"]=f(s?.body);return t.set(`${c[1]}${e}`,n),t.set(`${c[2]}${e}`,i),p({updates:{updatedRange:`Sheet1!${c[1]}${e}:${c[2]}${e}`}})}const d=/!([A-Z]+)(\\d+):([A-Z]+)$/.exec(a);if(d){const e=[];for(let s=Number(d[2]);s<=r();s++)e.push([t.get(`${d[1]}${s}`)??\"\"]);return p({range:a,values:e})}const u=/!([A-Z]+\\d+)$/.exec(a);if(u){if(\"PUT\"===i)return t.set(u[1],f(s?.body)[0]??\"\"),p({updatedRange:a});const e=t.get(u[1]);return p(void 0===e?{range:a}:{range:a,values:[[e]]})}return p({error:{code:400}},400)}}"
|
|
103
|
+
},
|
|
104
|
+
"push": {
|
|
105
|
+
"fetch": {
|
|
106
|
+
"$code": "async(e,s)=>{const n=function(e){return\"string\"==typeof e?e:e instanceof URL?e.toString():e.url}(e),i=s?.method??\"GET\";if(n.startsWith(\"https://oauth2.googleapis.com/token\"))return p(h);if(n.startsWith(\"http://metadata.google.internal/\"))return p(h);const o=/\\/spreadsheets\\/([^/?]+)\\/values\\/([^?]+)/.exec(n);if(!o){const e=/\\/spreadsheets\\/([^/?]+)\\?/.exec(n);return e?p({spreadsheetId:e[1]}):p({error:{code:404}},404)}const a=decodeURIComponent(o[2]),c=/!([A-Z]+):([A-Z]+):append$/.exec(a);if(c&&\"POST\"===i){const e=r()+1,[n=\"\",i=\"\"]=f(s?.body);return t.set(`${c[1]}${e}`,n),t.set(`${c[2]}${e}`,i),p({updates:{updatedRange:`Sheet1!${c[1]}${e}:${c[2]}${e}`}})}const d=/!([A-Z]+)(\\d+):([A-Z]+)$/.exec(a);if(d){const e=[];for(let s=Number(d[2]);s<=r();s++)e.push([t.get(`${d[1]}${s}`)??\"\"]);return p({range:a,values:e})}const u=/!([A-Z]+\\d+)$/.exec(a);if(u){if(\"PUT\"===i)return t.set(u[1],f(s?.body)[0]??\"\"),p({updatedRange:a});const e=t.get(u[1]);return p(void 0===e?{range:a}:{range:a,values:[[e]]})}return p({error:{code:400}},400)}"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"simulation": [
|
|
110
|
+
"fetch"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
99
113
|
"serviceAccount": {
|
|
100
114
|
"settings": {
|
|
101
115
|
"id": "1AbCdEfGhIjKlMnOpQrStUvWxYz",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walkeros/server-store-sheets",
|
|
3
3
|
"description": "Google Sheets store for walkerOS server flows",
|
|
4
|
-
"version": "4.7.0
|
|
4
|
+
"version": "4.7.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"update": "npx npm-check-updates -u && npm update"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@walkeros/core": "4.7.0
|
|
36
|
+
"@walkeros/core": "4.7.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {},
|
|
39
39
|
"repository": {
|