@walkeros/server-store-sheets 4.2.0-next-1780690800063 → 4.2.0-next-1780909874350

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @walkeros/server-store-sheets
2
2
 
3
- ## 4.2.0-next-1780690800063
3
+ ## 4.2.0-next-1780909874350
4
4
 
5
5
  ### Minor Changes
6
6
 
@@ -13,6 +13,11 @@
13
13
 
14
14
  ### Patch Changes
15
15
 
16
+ - 7d8ebf9: Stores now use one structured value type with binary (`Uint8Array`)
17
+ as a first-class leaf, serialized by a shared codec. A new `file: true` store
18
+ option serves byte-exact assets such as walker.js (default is structured
19
+ key-value). TTL is owned by the cache layer, not the store. Sheets is
20
+ structured-only and rejects `file: true`.
16
21
  - Updated dependencies [76d32c1]
17
22
  - Updated dependencies [908d6f0]
18
23
  - Updated dependencies [e8f6909]
@@ -23,7 +28,8 @@
23
28
  - Updated dependencies [654ba38]
24
29
  - Updated dependencies [6a72a32]
25
30
  - Updated dependencies [3eb2467]
26
- - @walkeros/core@4.2.0-next-1780690800063
31
+ - Updated dependencies [7d8ebf9]
32
+ - @walkeros/core@4.2.0-next-1780909874350
27
33
 
28
34
  ## 4.1.2
29
35
 
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 { Flow, Store } from '@walkeros/core';
3
+ import { Flow, Store, Hint } from '@walkeros/core';
4
4
 
5
5
  declare const SettingsSchema: z.ZodObject<{
6
6
  id: z.ZodString;
@@ -54,4 +54,6 @@ declare namespace index {
54
54
  export { index_cloudRunAdc as cloudRunAdc, index_serviceAccount as serviceAccount, index_step as step };
55
55
  }
56
56
 
57
- export { index as examples, index$1 as schemas };
57
+ declare const hints: Hint.Hints;
58
+
59
+ export { index as examples, hints, index$1 as schemas };
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 { Flow, Store } from '@walkeros/core';
3
+ import { Flow, Store, Hint } from '@walkeros/core';
4
4
 
5
5
  declare const SettingsSchema: z.ZodObject<{
6
6
  id: z.ZodString;
@@ -54,4 +54,6 @@ declare namespace index {
54
54
  export { index_cloudRunAdc as cloudRunAdc, index_serviceAccount as serviceAccount, index_step as step };
55
55
  }
56
56
 
57
- export { index as examples, index$1 as schemas };
57
+ declare const hints: Hint.Hints;
58
+
59
+ export { index as examples, hints, index$1 as schemas };
package/dist/dev.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,n=(e,r)=>{for(var i in r)t(e,i,{get:r[i],enumerable:!0})},s={};n(s,{examples:()=>v,schemas:()=>a}),module.exports=(e=s,((e,n,s,a)=>{if(n&&"object"==typeof n||"function"==typeof n)for(let c of i(n))o.call(e,c)||c===s||t(e,c,{get:()=>n[c],enumerable:!(a=r(n,c))||a.enumerable});return e})(t({},"__esModule",{value:!0}),e));var a={};n(a,{SettingsSchema:()=>l,SetupSchema:()=>h,settings:()=>p,setup:()=>b});var c=require("@walkeros/core/dev"),d=require("@walkeros/core/dev"),l=d.z.object({id:d.z.string().min(1).describe("Spreadsheet ID, the segment between /d/ and /edit in the URL"),sheet:d.z.string().default("Sheet1").describe("Sheet (tab) name within the spreadsheet"),key:d.z.string().default("A").describe("Column letter for keys (the lookup column)"),value:d.z.string().default("B").describe("Column letter for values (JSON-serialized blob)"),headerRows:d.z.number().int().min(0).default(1).describe("Number of header rows to skip when reading the key column"),credentials:d.z.union([d.z.string(),d.z.object({client_email:d.z.string().describe("Service account email"),private_key:d.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)")}),u=require("@walkeros/core/dev"),h=u.z.object({headers:u.z.array(u.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)(l),b=(0,c.zodToSchema)(h),v={};n(v,{cloudRunAdc:()=>S,serviceAccount:()=>w,step:()=>g});var g={};n(g,{readWithAdc:()=>m,writeWithServiceAccount:()=>f});var m={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" }']]},f={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" }']]},S={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"}},w={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"},credentials:"$env.SHEETS_SA_KEY"};//# sourceMappingURL=dev.js.map
1
+ "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,s=Object.prototype.hasOwnProperty,o=(e,r)=>{for(var i in r)t(e,i,{get:r[i],enumerable:!0})},a={};o(a,{examples:()=>v,hints:()=>y,schemas:()=>n}),module.exports=(e=a,((e,o,a,n)=>{if(o&&"object"==typeof o||"function"==typeof o)for(let c of i(o))s.call(e,c)||c===a||t(e,c,{get:()=>o[c],enumerable:!(n=r(o,c))||n.enumerable});return e})(t({},"__esModule",{value:!0}),e));var n={};o(n,{SettingsSchema:()=>l,SetupSchema:()=>h,settings:()=>b,setup:()=>p});var c=require("@walkeros/core/dev"),d=require("@walkeros/core/dev"),l=d.z.object({id:d.z.string().min(1).describe("Spreadsheet ID, the segment between /d/ and /edit in the URL"),sheet:d.z.string().default("Sheet1").describe("Sheet (tab) name within the spreadsheet"),key:d.z.string().default("A").describe("Column letter for keys (the lookup column)"),value:d.z.string().default("B").describe("Column letter for values (JSON-serialized blob)"),headerRows:d.z.number().int().min(0).default(1).describe("Number of header rows to skip when reading the key column"),credentials:d.z.union([d.z.string(),d.z.object({client_email:d.z.string().describe("Service account email"),private_key:d.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)")}),u=require("@walkeros/core/dev"),h=u.z.object({headers:u.z.array(u.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.'),b=(0,c.zodToSchema)(l),p=(0,c.zodToSchema)(h),v={};o(v,{cloudRunAdc:()=>S,serviceAccount:()=>w,step:()=>g});var g={};o(g,{readWithAdc:()=>f,writeWithServiceAccount:()=>m});var f={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" }']]},m={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" }']]},S={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"}},w={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"},credentials:"$env.SHEETS_SA_KEY"},y={"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"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\nimport { SetupSchema } from './setup';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport { SetupSchema } from './setup';\n\nexport const settings = zodToSchema(SettingsSchema);\nexport const setup = zodToSchema(SetupSchema);\n","import { z } from '@walkeros/core/dev';\n\nexport const SettingsSchema = z.object({\n 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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,cAA4B;;;ACA5B,iBAAkB;AAEX,IAAM,iBAAiB,aAAE,OAAO;AAAA,EACrC,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;","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/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"]}
package/dist/dev.mjs CHANGED
@@ -1 +1 @@
1
- var e=Object.defineProperty,t=(t,r)=>{for(var i in r)e(t,i,{get:r[i],enumerable:!0})},r={};t(r,{SettingsSchema:()=>s,SetupSchema:()=>a,settings:()=>d,setup:()=>c});import{zodToSchema as i}from"@walkeros/core/dev";import{z as o}from"@walkeros/core/dev";var s=o.object({id:o.string().min(1).describe("Spreadsheet ID, the segment between /d/ and /edit in the URL"),sheet:o.string().default("Sheet1").describe("Sheet (tab) name within the spreadsheet"),key:o.string().default("A").describe("Column letter for keys (the lookup column)"),value:o.string().default("B").describe("Column letter for values (JSON-serialized blob)"),headerRows:o.number().int().min(0).default(1).describe("Number of header rows to skip when reading the key column"),credentials:o.union([o.string(),o.object({client_email:o.string().describe("Service account email"),private_key:o.string().describe("RSA private key in PEM format")})]).optional().describe("Service account JSON (string or object). Omit for ADC on Cloud Run/GKE (deprecated: use config.credentials)")});import{z as n}from"@walkeros/core/dev";var a=n.object({headers:n.array(n.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.'),d=i(s),c=i(a),l={};t(l,{cloudRunAdc:()=>v,serviceAccount:()=>m,step:()=>u});var u={};t(u,{readWithAdc:()=>h,writeWithServiceAccount:()=>p});var h={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" }']]},p={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" }']]},v={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"}},m={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"},credentials:"$env.SHEETS_SA_KEY"};export{l as examples,r as schemas};//# sourceMappingURL=dev.mjs.map
1
+ var e=Object.defineProperty,t=(t,r)=>{for(var i in r)e(t,i,{get:r[i],enumerable:!0})},r={};t(r,{SettingsSchema:()=>o,SetupSchema:()=>n,settings:()=>c,setup:()=>d});import{zodToSchema as i}from"@walkeros/core/dev";import{z as s}from"@walkeros/core/dev";var o=s.object({id:s.string().min(1).describe("Spreadsheet ID, the segment between /d/ and /edit in the URL"),sheet:s.string().default("Sheet1").describe("Sheet (tab) name within the spreadsheet"),key:s.string().default("A").describe("Column letter for keys (the lookup column)"),value:s.string().default("B").describe("Column letter for values (JSON-serialized blob)"),headerRows:s.number().int().min(0).default(1).describe("Number of header rows to skip when reading the key column"),credentials:s.union([s.string(),s.object({client_email:s.string().describe("Service account email"),private_key:s.string().describe("RSA private key in PEM format")})]).optional().describe("Service account JSON (string or object). Omit for ADC on Cloud Run/GKE (deprecated: use config.credentials)")});import{z as a}from"@walkeros/core/dev";var n=a.object({headers:a.array(a.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=i(o),d=i(n),l={};t(l,{cloudRunAdc:()=>p,serviceAccount:()=>v,step:()=>u});var u={};t(u,{readWithAdc:()=>h,writeWithServiceAccount:()=>b});var h={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" }']]},b={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" }']]},p={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"}},v={settings:{id:"1AbCdEfGhIjKlMnOpQrStUvWxYz",sheet:"Customers"},credentials:"$env.SHEETS_SA_KEY"},g={"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{l as examples,g 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"],"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"],"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;","names":["z"]}
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"]}
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:()=>U,setup:()=>S,storeSheetsInit:()=>U}),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",d=6e4;function f(e){let t;return async function(){return t&&Date.now()<t.expiresAt||(t=e?await async function(e){const t=Math.floor(Date.now()/1e3),r=function(e,t){const r=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:t,exp:t+3600},e.private_key),n=await fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${r}`});if(!n.ok)throw new Error(`Token exchange error: ${n.status}`);const o=await n.json();return{token:o.access_token,expiresAt:Date.now()+1e3*o.expires_in-d}}(e):await async function(){const e=await fetch(a,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Metadata server error: ${e.status}`);const t=await e.json();return{token:t.access_token,expiresAt:Date.now()+1e3*t.expires_in-d}}()),t.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 w(e){return e.sheet??"Sheet1"}function y(e,t,r){return`${v(e)}!${t}${r}`}function $(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`${v(e)}!A1:${r}1`}function v(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=f(function(e){if(!e)return;if("string"==typeof e){let t;try{t=JSON.parse(e)}catch{return}return k(t)?t:void 0}return k(e)?e:void 0}(h(t,r))),a=await i(),u=`${g}/${encodeURIComponent(s.id)}?fields=spreadsheetId`,c=await fetch(u,{headers:{Authorization:`Bearer ${a}`}});if(404===c.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(!c.ok){const e=await A(c);throw new Error(`setup: spreadsheet probe failed (${c.status}): ${e}`)}let d=!1;if(o.headers&&o.headers.length>0){const e=$(w(s),o.headers.length),t=`${g}/${encodeURIComponent(s.id)}/values/${encodeURIComponent(e)}?valueInputOption=RAW`,n={values:[o.headers],range:e},i=await fetch(t,{method:"PUT",headers:{Authorization:`Bearer ${a}`,"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}`)}d=!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:d}};function k(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 b=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 R(e,t,r,n,o){const s=await o(),i=function(e,t,r){return`${v(e)}!${t}${r}:${t}`}(t,r,n+1),a=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(i)}`,u=await fetch(a,{headers:{Authorization:`Bearer ${s}`}});if(!u.ok)throw new Error(`storeSheetsInit: failed to read key column (${u.status})`);const c=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 u.json()),d=new Map;return c.forEach((e,t)=>{e&&d.set(e,n+t+1)}),d}async function C(e,t,r,n,o,s){const i=await s(),a=y(t,r,n),u=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}?valueInputOption=RAW`,c=await fetch(u,{method:"PUT",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({values:[[o]],range:a})});if(!c.ok)throw new Error(`storeSheets.writeCell: write failed (${c.status})`)}async function E(e,t,r,n,o,s){const i=await s(),a=function(e,t,r){return`${v(e)}!${t}:${r}`}(t,r,n),u=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}:append?valueInputOption=RAW`,c=await fetch(u,{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({values:[o]})});if(!c.ok)throw new Error(`storeSheets.appendRow: append failed (${c.status})`);const d=await c.json();if(!I(d))return;const f=d.updates;if(!I(f))return;const p=f.updatedRange;return"string"==typeof p?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}(p):void 0}var U=async e=>{!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);const t=e.config.settings,{logger:r}=e,n=e.id,o=f(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=w(t),i=function(e){return(e.key??"A").toUpperCase()}(t),a=function(e){return(e.value??"B").toUpperCase()}(t),u=function(e){return e.headerRows??1}(t);await async function(e,t,r,n){const o=b.get(e);if(void 0!==o)return void await o;const s=(async()=>{let o;try{const t=await r(),n=`${g}/${encodeURIComponent(e)}?fields=spreadsheetId`;o=await fetch(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===o.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 o.ok||n.debug("ensureSpreadsheetExists check failed (non-fatal)",{spreadsheetId:e,status:o.status}),!0})();b.set(e,s);try{await s}catch(t){throw b.delete(e),t}}(t.id,n,o,r);const c=await R(t.id,s,i,u,o);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=c.get(e);if(void 0===n)return;const i=await async function(e,t,r,n,o){const s=await o(),i=y(t,r,n),a=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(i)}`,u=await fetch(a,{headers:{Authorization:`Bearer ${s}`}});if(!u.ok)return;const c=await u.json();if(!I(c))return;const d=c.values;if(!Array.isArray(d)||0===d.length)return;const f=d[0];if(!Array.isArray(f)||0===f.length)return;const p=f[0];return"string"==typeof p?p:void 0}(t.id,s,a,n,o);if(void 0!==i&&""!==i)try{return JSON.parse(i)}catch(t){return void r.debug("storeSheets.get: JSON parse failed",{key:e,error:t instanceof Error?t.message:String(t)})}},async set(e,r){if(Buffer.isBuffer(r))throw new Error("Sheets store cannot be used as a request cache; use fs, s3, gcs, or an in-memory store");const n=JSON.stringify(r),u=c.get(e);if(void 0===u){const r=await E(t.id,s,i,a,[e,n],o);void 0!==r&&c.set(e,r)}else await C(t.id,s,a,u,n,o)},async delete(e){const r=c.get(e);void 0!==r&&await C(t.id,s,a,r,"",o)}}};//# sourceMappingURL=index.js.map
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 t;return async function(){return t&&Date.now()<t.expiresAt||(t=e?await async function(e){const t=Math.floor(Date.now()/1e3),r=function(e,t){const r=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:t,exp:t+3600},e.private_key),n=await fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${r}`});if(!n.ok)throw new Error(`Token exchange error: ${n.status}`);const o=await n.json();return{token:o.access_token,expiresAt:Date.now()+1e3*o.expires_in-f}}(e):await async function(){const e=await fetch(a,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Metadata server error: ${e.status}`);const t=await e.json();return{token:t.access_token,expiresAt:Date.now()+1e3*t.expires_in-f}}()),t.token}}function 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=d(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=await i(),u=`${g}/${encodeURIComponent(s.id)}?fields=spreadsheetId`,c=await fetch(u,{headers:{Authorization:`Bearer ${a}`}});if(404===c.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(!c.ok){const e=await A(c);throw new Error(`setup: spreadsheet probe failed (${c.status}): ${e}`)}let f=!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 fetch(t,{method:"PUT",headers:{Authorization:`Bearer ${a}`,"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}`)}f=!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:f}};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){const s=await o(),i=function(e,t,r){return`${$(e)}!${t}${r}:${t}`}(t,r,n+1),a=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(i)}`,u=await fetch(a,{headers:{Authorization:`Bearer ${s}`}});if(!u.ok)throw new Error(`storeSheetsInit: failed to read key column (${u.status})`);const c=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 u.json()),f=new Map;return c.forEach((e,t)=>{e&&f.set(e,n+t+1)}),f}async function R(e,t,r,n,o,s){const i=await s(),a=w(t,r,n),u=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}?valueInputOption=RAW`,c=await fetch(u,{method:"PUT",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({values:[[o]],range:a})});if(!c.ok)throw new Error(`storeSheets.writeCell: write failed (${c.status})`)}async function U(e,t,r,n,o,s){const i=await s(),a=function(e,t,r){return`${$(e)}!${t}:${r}`}(t,r,n),u=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}:append?valueInputOption=RAW`,c=await fetch(u,{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({values:[o]})});if(!c.ok)throw new Error(`storeSheets.appendRow: append failed (${c.status})`);const f=await c.json();if(!I(f))return;const d=f.updates;if(!I(d))return;const p=d.updatedRange;return"string"==typeof p?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}(p):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=d(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=y(t),i=function(e){return(e.key??"A").toUpperCase()}(t),a=function(e){return(e.value??"B").toUpperCase()}(t),u=function(e){return e.headerRows??1}(t);await async function(e,t,r,n){const o=k.get(e);if(void 0!==o)return void await o;const s=(async()=>{let o;try{const t=await r(),n=`${g}/${encodeURIComponent(e)}?fields=spreadsheetId`;o=await fetch(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===o.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 o.ok||n.debug("ensureSpreadsheetExists check failed (non-fatal)",{spreadsheetId:e,status:o.status}),!0})();k.set(e,s);try{await s}catch(t){throw k.delete(e),t}}(t.id,n,o,r);const c=await C(t.id,s,i,u,o);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=c.get(e);if(void 0===n)return;const i=await async function(e,t,r,n,o){const s=await o(),i=w(t,r,n),a=`${g}/${encodeURIComponent(e)}/values/${encodeURIComponent(i)}`,u=await fetch(a,{headers:{Authorization:`Bearer ${s}`}});if(!u.ok)return;const c=await u.json();if(!I(c))return;const f=c.values;if(!Array.isArray(f)||0===f.length)return;const d=f[0];if(!Array.isArray(d)||0===d.length)return;const p=d[0];return"string"==typeof p?p:void 0}(t.id,s,a,n,o);if(void 0===i||""===i)return;let u;try{u=JSON.parse(i)}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),u=c.get(e);if(void 0===u){const r=await U(t.id,s,i,a,[e,n],o);void 0!==r&&c.set(e,r)}else await R(t.id,s,a,u,n,o)},async delete(e){const r=c.get(e);void 0!==r&&await R(t.id,s,a,r,"",o)}}};//# 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\nexport const storeSheetsInit: Store.Init<Types> = async (context) => {\n assertSheetsSettings(context.config.settings);\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<unknown> {\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 try {\n return 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 },\n\n async set(key: string, value: unknown): Promise<void> {\n // The request-cache codec hands stores an encoded Buffer (via\n // encodeCacheValue). Sheets would JSON.stringify it into\n // {\"type\":\"Buffer\",\"data\":[...]} and return that object as a HIT with no\n // TTL check on read, serving silent stale garbage. Sheets is not a request-cache\n // backend; reject the Buffer case loudly. Non-Buffer JSON values are\n // unaffected.\n if (Buffer.isBuffer(value)) {\n throw new Error(\n 'Sheets store cannot be used as a request cache; use fs, s3, gcs, or an in-memory store',\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 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"]}
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",n="https://oauth2.googleapis.com/token",r="https://www.googleapis.com/auth/spreadsheets",o=6e4;function s(s){let a;return async function(){return a&&Date.now()<a.expiresAt||(a=s?await async function(t){const s=Math.floor(Date.now()/1e3),a=function(t,n){const r=i(JSON.stringify({alg:"RS256",typ:"JWT"})),o=i(JSON.stringify(t)),s=e("RSA-SHA256");return s.update(`${r}.${o}`),`${r}.${o}.${s.sign(n,"base64url")}`}({iss:t.client_email,scope:r,aud:n,iat:s,exp:s+3600},t.private_key),c=await fetch(n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${a}`});if(!c.ok)throw new Error(`Token exchange error: ${c.status}`);const u=await c.json();return{token:u.access_token,expiresAt:Date.now()+1e3*u.expires_in-o}}(s):await async function(){const e=await fetch(t,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Metadata server error: ${e.status}`);const n=await e.json();return{token:n.access_token,expiresAt:Date.now()+1e3*n.expires_in-o}}()),a.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 n=e.settings?.credentials;return void 0!==n?(t.warn("settings.credentials is deprecated; use config.credentials"),n):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,n){return`${h(e)}!${t}${n}`}function p(e,t){const n=function(e){if(e<1)throw new Error(`columnLetter: index must be >=1, got ${e}`);let t=e,n="";for(;t>0;){const e=(t-1)%26;n=String.fromCharCode(65+e)+n,t=Math.floor((t-1)/26)}return n}(t);return`${h(e)}!A1:${n}1`}function h(e){return/^[A-Za-z0-9_]+$/.test(e)?e:`'${e.replace(/'/g,"''")}'`}var g={},l=async e=>{const{config:t,logger:n,id:r}=e,o=c(t.setup,g);if(!o)return void n.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=s(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,n))),h=await f(),l=`${u}/${encodeURIComponent(i.id)}?fields=spreadsheetId`,$=await fetch(l,{headers:{Authorization:`Bearer ${h}`}});if(404===$.status)throw new Error(`Spreadsheet not found: ${i.id}. Run "walkeros setup store.${r}" to ensure the sheet exists and is shared with the service account.`);if(!$.ok){const e=await y($);throw new Error(`setup: spreadsheet probe failed (${$.status}): ${e}`)}let v=!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`,r={values:[o.headers],range:e},s=await fetch(t,{method:"PUT",headers:{Authorization:`Bearer ${h}`,"Content-Type":"application/json"},body:JSON.stringify(r)});if(!s.ok){const e=await y(s);throw new Error(`setup: header write failed (${s.status}): ${e}`)}v=!0,n.info("setup: headers written",{spreadsheetId:i.id,headers:o.headers.length})}else n.debug("setup: spreadsheet verified, no headers configured",{spreadsheetId:i.id});return{headersWritten:v}};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 $=new Map;function v(e){return"object"==typeof e&&null!==e}function m(e){return!!v(e)&&("string"==typeof e.client_email&&"string"==typeof e.private_key)}async function S(e,t,n,r,o){const s=await o(),i=function(e,t,n){return`${h(e)}!${t}${n}:${t}`}(t,n,r+1),a=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(i)}`,c=await fetch(a,{headers:{Authorization:`Bearer ${s}`}});if(!c.ok)throw new Error(`storeSheetsInit: failed to read key column (${c.status})`);const d=function(e){if(!v(e))return[];const t=e.values;if(!Array.isArray(t))return[];const n=[];for(const e of t)Array.isArray(e)&&"string"==typeof e[0]?n.push(e[0]):n.push("");return n}(await c.json()),f=new Map;return d.forEach((e,t)=>{e&&f.set(e,r+t+1)}),f}async function k(e,t,n,r,o,s){const i=await s(),a=f(t,n,r),c=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}?valueInputOption=RAW`,d=await fetch(c,{method:"PUT",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({values:[[o]],range:a})});if(!d.ok)throw new Error(`storeSheets.writeCell: write failed (${d.status})`)}async function A(e,t,n,r,o,s){const i=await s(),a=function(e,t,n){return`${h(e)}!${t}:${n}`}(t,n,r),c=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}:append?valueInputOption=RAW`,d=await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({values:[o]})});if(!d.ok)throw new Error(`storeSheets.appendRow: append failed (${d.status})`);const f=await d.json();if(!v(f))return;const p=f.updates;if(!v(p))return;const g=p.updatedRange;return"string"==typeof g?function(e){const t=/![A-Z]+(\d+)/.exec(e);if(!t)return;const n=Number.parseInt(t[1],10);return Number.isFinite(n)?n:void 0}(g):void 0}var I=async e=>{!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);const t=e.config.settings,{logger:n}=e,r=e.id,o=s(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,n))),i=d(t),c=function(e){return(e.key??"A").toUpperCase()}(t),p=function(e){return(e.value??"B").toUpperCase()}(t),h=function(e){return e.headerRows??1}(t);await async function(e,t,n,r){const o=$.get(e);if(void 0!==o)return void await o;const s=(async()=>{let o;try{const t=await n(),r=`${u}/${encodeURIComponent(e)}?fields=spreadsheetId`;o=await fetch(r,{headers:{Authorization:`Bearer ${t}`}})}catch(t){return r.debug("ensureSpreadsheetExists check failed (non-fatal)",{spreadsheetId:e,error:t instanceof Error?t.message:String(t)}),!0}if(404===o.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 o.ok||r.debug("ensureSpreadsheetExists check failed (non-fatal)",{spreadsheetId:e,status:o.status}),!0})();$.set(e,s);try{await s}catch(t){throw $.delete(e),t}}(t.id,r,o,n);const g=await S(t.id,i,c,h,o);return{type:"sheets",config:{settings:t,env:e.config.env,id:e.config.id,logger:e.config.logger},setup:l,async get(e){const r=g.get(e);if(void 0===r)return;const s=await async function(e,t,n,r,o){const s=await o(),i=f(t,n,r),a=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(i)}`,c=await fetch(a,{headers:{Authorization:`Bearer ${s}`}});if(!c.ok)return;const d=await c.json();if(!v(d))return;const p=d.values;if(!Array.isArray(p)||0===p.length)return;const h=p[0];if(!Array.isArray(h)||0===h.length)return;const g=h[0];return"string"==typeof g?g:void 0}(t.id,i,p,r,o);if(void 0!==s&&""!==s)try{return JSON.parse(s)}catch(t){return void n.debug("storeSheets.get: JSON parse failed",{key:e,error:t instanceof Error?t.message:String(t)})}},async set(e,n){if(Buffer.isBuffer(n))throw new Error("Sheets store cannot be used as a request cache; use fs, s3, gcs, or an in-memory store");const r=JSON.stringify(n),s=g.get(e);if(void 0===s){const n=await A(t.id,i,c,p,[e,r],o);void 0!==n&&g.set(e,n)}else await k(t.id,i,p,s,r,o)},async delete(e){const n=g.get(e);void 0!==n&&await k(t.id,i,p,n,"",o)}}};export{g as DEFAULT_SETUP,I as default,l as setup,I as storeSheetsInit};//# sourceMappingURL=index.mjs.map
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 a;return async function(){return a&&Date.now()<a.expiresAt||(a=s?await async function(t){const s=Math.floor(Date.now()/1e3),a=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:s,exp:s+3600},t.private_key),c=await fetch(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${a}`});if(!c.ok)throw new Error(`Token exchange error: ${c.status}`);const u=await c.json();return{token:u.access_token,expiresAt:Date.now()+1e3*u.expires_in-o}}(s):await async function(){const e=await fetch(t,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Metadata server error: ${e.status}`);const r=await e.json();return{token:r.access_token,expiresAt:Date.now()+1e3*r.expires_in-o}}()),a.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=s(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=await f(),g=`${u}/${encodeURIComponent(i.id)}?fields=spreadsheetId`,$=await fetch(g,{headers:{Authorization:`Bearer ${h}`}});if(404===$.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(!$.ok){const e=await y($);throw new Error(`setup: spreadsheet probe failed (${$.status}): ${e}`)}let v=!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 fetch(t,{method:"PUT",headers:{Authorization:`Bearer ${h}`,"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}`)}v=!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:v}};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 $=new Map;function v(e){return"object"==typeof e&&null!==e}function m(e){return!!v(e)&&("string"==typeof e.client_email&&"string"==typeof e.private_key)}async function A(e,t,r,n,o){const s=await o(),i=function(e,t,r){return`${h(e)}!${t}${r}:${t}`}(t,r,n+1),a=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(i)}`,c=await fetch(a,{headers:{Authorization:`Bearer ${s}`}});if(!c.ok)throw new Error(`storeSheetsInit: failed to read key column (${c.status})`);const d=function(e){if(!v(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()),f=new Map;return d.forEach((e,t)=>{e&&f.set(e,n+t+1)}),f}async function S(e,t,r,n,o,s){const i=await s(),a=f(t,r,n),c=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}?valueInputOption=RAW`,d=await fetch(c,{method:"PUT",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({values:[[o]],range:a})});if(!d.ok)throw new Error(`storeSheets.writeCell: write failed (${d.status})`)}async function k(e,t,r,n,o,s){const i=await s(),a=function(e,t,r){return`${h(e)}!${t}:${r}`}(t,r,n),c=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(a)}:append?valueInputOption=RAW`,d=await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({values:[o]})});if(!d.ok)throw new Error(`storeSheets.appendRow: append failed (${d.status})`);const f=await d.json();if(!v(f))return;const p=f.updates;if(!v(p))return;const l=p.updatedRange;return"string"==typeof l?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}(l):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=s(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=d(t),c=function(e){return(e.key??"A").toUpperCase()}(t),p=function(e){return(e.value??"B").toUpperCase()}(t),h=function(e){return e.headerRows??1}(t);await async function(e,t,r,n){const o=$.get(e);if(void 0!==o)return void await o;const s=(async()=>{let o;try{const t=await r(),n=`${u}/${encodeURIComponent(e)}?fields=spreadsheetId`;o=await fetch(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===o.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 o.ok||n.debug("ensureSpreadsheetExists check failed (non-fatal)",{spreadsheetId:e,status:o.status}),!0})();$.set(e,s);try{await s}catch(t){throw $.delete(e),t}}(t.id,n,o,r);const l=await A(t.id,i,c,h,o);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=l.get(e);if(void 0===n)return;const s=await async function(e,t,r,n,o){const s=await o(),i=f(t,r,n),a=`${u}/${encodeURIComponent(e)}/values/${encodeURIComponent(i)}`,c=await fetch(a,{headers:{Authorization:`Bearer ${s}`}});if(!c.ok)return;const d=await c.json();if(!v(d))return;const p=d.values;if(!Array.isArray(p)||0===p.length)return;const h=p[0];if(!Array.isArray(h)||0===h.length)return;const l=h[0];return"string"==typeof l?l:void 0}(t.id,i,p,n,o);if(void 0===s||""===s)return;let a;try{a=JSON.parse(s)}catch(t){return void r.debug("storeSheets.get: JSON parse failed",{key:e,error:t instanceof Error?t.message:String(t)})}return b(a)?a: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),s=l.get(e);if(void 0===s){const r=await k(t.id,i,c,p,[e,n],o);void 0!==r&&l.set(e,r)}else await S(t.id,i,p,s,n,o)},async delete(e){const r=l.get(e);void 0!==r&&await S(t.id,i,p,r,"",o)}}};export{l as DEFAULT_SETUP,C as default,g as setup,C as storeSheetsInit};//# sourceMappingURL=index.mjs.map
@@ -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\nexport const storeSheetsInit: Store.Init<Types> = async (context) => {\n assertSheetsSettings(context.config.settings);\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<unknown> {\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 try {\n return 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 },\n\n async set(key: string, value: unknown): Promise<void> {\n // The request-cache codec hands stores an encoded Buffer (via\n // encodeCacheValue). Sheets would JSON.stringify it into\n // {\"type\":\"Buffer\",\"data\":[...]} and return that object as a HIT with no\n // TTL check on read, serving silent stale garbage. Sheets is not a request-cache\n // backend; reject the Buffer case loudly. Non-Buffer JSON values are\n // unaffected.\n if (Buffer.isBuffer(value)) {\n throw new Error(\n 'Sheets store cannot be used as a request cache; use fs, s3, gcs, or an in-memory store',\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): 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,7 +1,7 @@
1
1
  {
2
2
  "$meta": {
3
3
  "package": "@walkeros/server-store-sheets",
4
- "version": "4.2.0-next-1780690800063",
4
+ "version": "4.2.0-next-1780909874350",
5
5
  "type": "store",
6
6
  "platform": [
7
7
  "server"
@@ -138,5 +138,10 @@
138
138
  ]
139
139
  }
140
140
  }
141
+ },
142
+ "hints": {
143
+ "structured-only": {
144
+ "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."
145
+ }
141
146
  }
142
147
  }
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.2.0-next-1780690800063",
4
+ "version": "4.2.0-next-1780909874350",
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.2.0-next-1780690800063"
36
+ "@walkeros/core": "4.2.0-next-1780909874350"
37
37
  },
38
38
  "devDependencies": {},
39
39
  "repository": {