hikoutei 0.10.10-dev → 0.10.13-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -12,7 +12,7 @@ Google Sheets を利用する MVP 向けの型付きリポジトリであり、
12
12
  Sheets へ非同期で投影されます。
13
13
 
14
14
  <a href="https://www.npmjs.com/package/hikoutei">npm</a> ·
15
- <a href="docs/quick-start.md">クイックスタート</a> ·
15
+ <a href="website/guide/quick-start.md">クイックスタート</a> ·
16
16
  <a href="https://github.com/ManddarinShop/Hikoutei/issues">Issues</a>
17
17
 
18
18
  [![npm version](https://img.shields.io/npm/v/hikoutei?style=flat-square)](https://www.npmjs.com/package/hikoutei)
@@ -176,12 +176,12 @@ Hikoutei は、永続的なローカル outbox・冪等な配信・競合を考
176
176
  ため、一時的な API 障害でコミット済みのアプリケーション書き込みが失われる
177
177
  ことはありません。provider は資格情報・スプレッドシート ID・URL・ペイロードを
178
178
  ログに残さず、Google の割り当て枠に収まるようリクエスト開始間隔を調整します。
179
- 詳細な状態機械と復旧ルールは[内部整合性モデル](docs/internal-consistency-model.md)を
179
+ 詳細な状態機械と復旧ルールは[内部整合性モデル](website/guide/internal-consistency.md)を
180
180
  参照してください。
181
181
 
182
182
  ライブの Google 呼び出しはオプトインであり、通常の検証経路はフェイク
183
183
  provider と SQLite フィクスチャです。詳細なセットアップとトラブルシューティン
184
- グは[クイックスタート](docs/quick-start.md)を参照してください。
184
+ グは[クイックスタート](website/guide/quick-start.md)を参照してください。
185
185
 
186
186
  ## インストール
187
187
 
@@ -196,15 +196,15 @@ MikroORM は実装の詳細であり、Hikoutei の公開エンティティ API
196
196
 
197
197
  ## ドキュメント
198
198
 
199
- - [クイックスタート](docs/quick-start.md) — インストール、ORM ライフサイクル、
199
+ - [クイックスタート](website/guide/quick-start.md) — インストール、ORM ライフサイクル、
200
200
  サービス側の同期設定
201
- - [アーキテクチャ](docs/architecture.md) — ローカルストアとシートビューの関係
202
- - [書き込みと同期フロー](docs/write-and-synchronization-flow.md) — 非同期配信と
201
+ - [アーキテクチャ](website/guide/architecture.md) — ローカルストアとシートビューの関係
202
+ - [書き込みと同期フロー](website/guide/sync-flow.md) — 非同期配信と
203
203
  復旧動作
204
- - [内部整合性モデル](docs/internal-consistency-model.md) — 永続的な outbox、
204
+ - [内部整合性モデル](website/guide/internal-consistency.md) — 永続的な outbox、
205
205
  冪等な配信、競合を考慮した更新
206
- - [開発](docs/development.md) — ローカル開発とテストコマンド
207
- - [ベンチマークノート](docs/sync-bulk-write-benchmark.md) — 日付付きの測定と
206
+ - [開発](website/guide/contributing.md) — ローカル開発とテストコマンド
207
+ - [ベンチマークノート](website/guide/benchmarks.md) — 日付付きの測定と
208
208
  その限界
209
209
 
210
210
  ## 制限事項
package/README.ko.md CHANGED
@@ -11,7 +11,7 @@ Google Sheets 기반 MVP를 위한 타입 안전 리포지토리이자 안전한
11
11
  사람이 검토하고 가볍게 협업할 수 있도록 Google Sheets에 비동기로 투영됩니다.
12
12
 
13
13
  <a href="https://www.npmjs.com/package/hikoutei">npm</a> ·
14
- <a href="docs/quick-start.md">빠른 시작</a> ·
14
+ <a href="website/guide/quick-start.md">빠른 시작</a> ·
15
15
  <a href="https://github.com/ManddarinShop/Hikoutei/issues">이슈</a>
16
16
 
17
17
  [![npm version](https://img.shields.io/npm/v/hikoutei?style=flat-square)](https://www.npmjs.com/package/hikoutei)
@@ -169,11 +169,11 @@ Hikoutei는 내구성 있는 로컬 outbox, 멱등 전달, 충돌을 인지하
169
169
  사용하므로 일시적인 API 실패가 커밋된 애플리케이션 쓰기를 잃게 하지 않습니다.
170
170
  provider는 자격 증명, 스프레드시트 ID, URL, 페이로드를 로그에 남기지 않으며,
171
171
  Google 할당량 창 안에 머물도록 요청 시작 간격을 조절합니다. 상세 상태 머신과
172
- 복구 규칙은 [내부 정합성 모델](docs/internal-consistency-model.md)을
172
+ 복구 규칙은 [내부 정합성 모델](website/guide/internal-consistency.md)을
173
173
  참고하세요.
174
174
 
175
175
  라이브 Google 호출은 opt-in이며, 일반적인 검증 경로는 fake provider와 SQLite
176
- fixture입니다. 자세한 설정과 문제 해결 단계는 [빠른 시작](docs/quick-start.md)을
176
+ fixture입니다. 자세한 설정과 문제 해결 단계는 [빠른 시작](website/guide/quick-start.md)을
177
177
  참고하세요.
178
178
 
179
179
  ## 설치
@@ -190,14 +190,14 @@ MikroORM은 구현 세부 사항이며 Hikoutei의 공개 엔티티 API에는
190
190
 
191
191
  ## 문서
192
192
 
193
- - [빠른 시작](docs/quick-start.md) — 설치, ORM 생명주기, 서비스 측 동기화 설정
194
- - [아키텍처](docs/architecture.md) — 로컬 저장소와 Sheet 화면이 맞물리는 방식
195
- - [쓰기 및 동기화 흐름](docs/write-and-synchronization-flow.md) — 비동기 전달과
193
+ - [빠른 시작](website/guide/quick-start.md) — 설치, ORM 생명주기, 서비스 측 동기화 설정
194
+ - [아키텍처](website/guide/architecture.md) — 로컬 저장소와 Sheet 화면이 맞물리는 방식
195
+ - [쓰기 및 동기화 흐름](website/guide/sync-flow.md) — 비동기 전달과
196
196
  복구 동작
197
- - [내부 정합성 모델](docs/internal-consistency-model.md) — 내구성 outbox,
197
+ - [내부 정합성 모델](website/guide/internal-consistency.md) — 내구성 outbox,
198
198
  멱등 전달, 충돌을 인지하는 업데이트
199
- - [개발](docs/development.md) — 로컬 개발 및 테스트 명령어
200
- - [벤치마크 노트](docs/sync-bulk-write-benchmark.md) — 날짜가 기록된 측정과
199
+ - [개발](website/guide/contributing.md) — 로컬 개발 및 테스트 명령어
200
+ - [벤치마크 노트](website/guide/benchmarks.md) — 날짜가 기록된 측정과
201
201
  그 한계
202
202
 
203
203
  ## 한계
package/README.md CHANGED
@@ -123,11 +123,14 @@ client directly.
123
123
 
124
124
  Google Sheets synchronization is a service-side concern. Applications do not
125
125
  import a provider client, pass Sheet routes to `createTypedSheets()`, or choose
126
- an operation for each write — the root API accepts only `dbName` and
127
- `entities`. The sync runtime uses one internal Google Sheets API provider with
128
- a service account — no Apps Script deployment. Sync auto-start is selected by
129
- `HIKOUTEI_SYNC_SPREADSHEET_URL` plus `GOOGLE_APPLICATION_CREDENTIALS`; there is
130
- no public `googleSheetsApi` bootstrap option to configure.
126
+ an operation for each write — `CreateTypedSheetsOptions` fields (`dbName`,
127
+ `entities`, `providerOptions`) are all optional. The sync runtime uses one
128
+ internal Google Sheets API provider with a service account — no Apps Script
129
+ deployment. Sync auto-start is selected by `HIKOUTEI_SYNC_SPREADSHEET_URL`
130
+ plus `GOOGLE_APPLICATION_CREDENTIALS`. Optional `providerOptions` tunes the
131
+ sync-path provider (telemetry/timeouts; inert when local-only), and
132
+ `createTypedSheetsWithSync()` exposes the same options with a richer result
133
+ for existing-sheet adoption.
131
134
 
132
135
  **Fastest path:** install the gcloud CLI, then run `npx hikoutei setup` from your
133
136
  project directory. On an interactive terminal it offers (press Enter) to
@@ -247,8 +250,8 @@ shared on the spreadsheet (the error tells you which email to share).
247
250
  `GOOGLE_APPLICATION_CREDENTIALS` and `HIKOUTEI_SYNC_SPREADSHEET_URL` set;
248
251
  `createTypedSheets()` detects them and starts the internal sync bootstrap —
249
252
  it creates and verifies headers on the registered tabs, then starts outbox
250
- delivery and User_Input polling. There is no provider option to pass and no
251
- internal bootstrap to start by hand.
253
+ delivery and User_Input polling. Pass `providerOptions` only when sync-path
254
+ tuning is needed; there is no internal bootstrap to start by hand.
252
255
 
253
256
  > **Legacy spreadsheet note.** Spreadsheets provisioned by the old Apps Script
254
257
  > provider with developer-metadata row anchors are not migrated: `User_Input`
@@ -273,15 +276,15 @@ verification path. The detailed setup and troubleshooting steps are in the
273
276
 
274
277
  ## Installation
275
278
 
276
- The project and npm package are both called `hikoutei`. The built-in SQLite
277
- provider currently requires MikroORM:
279
+ The project and npm package are both called `hikoutei`. This repo uses pnpm
280
+ (packageManager `pnpm@11.1.2`):
278
281
 
279
282
  ```sh
280
- npm install hikoutei @mikro-orm/core @mikro-orm/sql
283
+ pnpm install --frozen-lockfile
281
284
  ```
282
285
 
283
- MikroORM is an implementation detail and does not appear in Hikoutei's public
284
- entity API.
286
+ `@mikro-orm/core` and `@mikro-orm/sql` are optional peers (`optional: true`),
287
+ lazy-loaded only when a runtime opens. `npm install` still works for consumers.
285
288
 
286
289
  ## Documentation
287
290
 
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Shared service-account credential-pool ownership (Batch A).
3
+ *
4
+ * This module owns the N-`GoogleAuth` pool every provider line paces and
5
+ * signs with: one auth per key file (one Google quota principal), the
6
+ * deterministic round-robin cursor, and the per-identity index binding that
7
+ * ties admission to transport (the admitted identity IS the signing
8
+ * identity). Providers build their API clients (`sheets({ version, auth })`
9
+ * stays provider-side) from the selected auths; the pacing pool
10
+ * (`CredentialPacingPool` in ikisaki) meets this pool at the existing
11
+ * admission→transport index binding, which is unchanged.
12
+ *
13
+ * Out-of-range selection fails CLOSED through the caller-supplied
14
+ * `onInvalidSelection` (same parameterization as the key loader's `fail`):
15
+ * signing with a different identity than the one admission paced against
16
+ * would silently defeat the per-identity quota contract.
17
+ */
18
+ import type { GoogleAuth } from "google-auth-library";
19
+ import { type ServiceAccountKeyLoadOptions } from "./serviceAccountKey.js";
20
+ /**
21
+ * Advances (and returns) the next round-robin client index for a pool.
22
+ *
23
+ * A preferred (provider-admitted) index is returned WITHOUT advancing the
24
+ * cursor, so admission-bound calls never skew the fallback rotation. The
25
+ * cursor is a mutable carrier so callers keep the rotation across requests;
26
+ * `clientCount` must be ≥ 1.
27
+ */
28
+ export declare function nextPooledClientIndex(cursor: {
29
+ next: number;
30
+ }, clientCount: number, preferredIndex: number | undefined): number;
31
+ /** Selection failure mapping (must throw, never return). */
32
+ export interface ServiceAccountAuthPoolSelection {
33
+ readonly onInvalidSelection: (message: string) => never;
34
+ }
35
+ /** Key-file pool construction: scopes plus both failure mappings. */
36
+ export interface ServiceAccountAuthPoolKeyFilesOptions extends ServiceAccountKeyLoadOptions, ServiceAccountAuthPoolSelection {
37
+ /** OAuth scopes the pooled auths request (provider-owned, e.g. Sheets/Drive). */
38
+ readonly scopes: readonly string[];
39
+ }
40
+ /**
41
+ * The N-`GoogleAuth` credential pool plus its rotation cursor.
42
+ *
43
+ * Generic over the auth value so providers wrap their own auth flavor
44
+ * (injected test auths, the ADC default) together with the file-backed
45
+ * `GoogleAuth` instances under ONE selection cursor.
46
+ */
47
+ export declare class ServiceAccountAuthPool<TAuth = GoogleAuth> {
48
+ /** Every pooled credential; index-aligned with the provider's clients. */
49
+ readonly auths: readonly TAuth[];
50
+ private readonly selection;
51
+ /** Fallback rotation cursor for requests WITHOUT an admitted index. */
52
+ private readonly poolCursor;
53
+ constructor(
54
+ /** Every pooled credential; index-aligned with the provider's clients. */
55
+ auths: readonly TAuth[], selection: ServiceAccountAuthPoolSelection);
56
+ /**
57
+ * Builds the file-backed pool: each key file is read and turned into its
58
+ * own `GoogleAuth` at construction (fail fast on unreadable or malformed
59
+ * files). Error payloads carry the PATH only — never file contents,
60
+ * client emails, or key material.
61
+ */
62
+ static loadFromKeyFiles(keyFiles: readonly string[], options: ServiceAccountAuthPoolKeyFilesOptions): ServiceAccountAuthPool<GoogleAuth>;
63
+ /** Number of pooled credentials (quota principals). */
64
+ get size(): number;
65
+ /**
66
+ * Picks the pool index for one request: the admitted identity when the
67
+ * request carries one, otherwise the next round-robin entry (a 1-auth
68
+ * pool always selects index 0 — the historical single-auth path).
69
+ */
70
+ selectIndex(preferredIndex: number | undefined): number;
71
+ }
72
+ //# sourceMappingURL=serviceAccountAuthPool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceAccountAuthPool.d.ts","sourceRoot":"","sources":["../../../../../../../src/auth/serviceAccountAuthPool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAIL,KAAK,4BAA4B,EAClC,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EACxB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,MAAM,CAOR;AAED,4DAA4D;AAC5D,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;CACzD;AAED,qEAAqE;AACrE,MAAM,WAAW,qCACf,SAAQ,4BAA4B,EAAE,+BAA+B;IACrE,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;GAMG;AACH,qBAAa,sBAAsB,CAAC,KAAK,GAAG,UAAU;IAKlD,0EAA0E;aAC1D,KAAK,EAAE,SAAS,KAAK,EAAE;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAN5B,uEAAuE;IACvE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiC;;IAG1D,0EAA0E;IAC1D,KAAK,EAAE,SAAS,KAAK,EAAE,EACtB,SAAS,EAAE,+BAA+B;IAG7D;;;;;OAKG;WACW,gBAAgB,CAC5B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,EAAE,qCAAqC,GAC7C,sBAAsB,CAAC,UAAU,CAAC;IASrC,uDAAuD;IACvD,IAAW,IAAI,IAAI,MAAM,CAExB;IAED;;;;OAIG;IACI,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;CAe/D"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Shared service-account credential-pool ownership (Batch A).
3
+ *
4
+ * This module owns the N-`GoogleAuth` pool every provider line paces and
5
+ * signs with: one auth per key file (one Google quota principal), the
6
+ * deterministic round-robin cursor, and the per-identity index binding that
7
+ * ties admission to transport (the admitted identity IS the signing
8
+ * identity). Providers build their API clients (`sheets({ version, auth })`
9
+ * stays provider-side) from the selected auths; the pacing pool
10
+ * (`CredentialPacingPool` in ikisaki) meets this pool at the existing
11
+ * admission→transport index binding, which is unchanged.
12
+ *
13
+ * Out-of-range selection fails CLOSED through the caller-supplied
14
+ * `onInvalidSelection` (same parameterization as the key loader's `fail`):
15
+ * signing with a different identity than the one admission paced against
16
+ * would silently defeat the per-identity quota contract.
17
+ */
18
+ import { createServiceAccountAuth, loadServiceAccountKeyFileSync, } from "./serviceAccountKey.js";
19
+ /**
20
+ * Advances (and returns) the next round-robin client index for a pool.
21
+ *
22
+ * A preferred (provider-admitted) index is returned WITHOUT advancing the
23
+ * cursor, so admission-bound calls never skew the fallback rotation. The
24
+ * cursor is a mutable carrier so callers keep the rotation across requests;
25
+ * `clientCount` must be ≥ 1.
26
+ */
27
+ export function nextPooledClientIndex(cursor, clientCount, preferredIndex) {
28
+ if (preferredIndex !== undefined) {
29
+ return preferredIndex;
30
+ }
31
+ const index = cursor.next % clientCount;
32
+ cursor.next = (index + 1) % clientCount;
33
+ return index;
34
+ }
35
+ /**
36
+ * The N-`GoogleAuth` credential pool plus its rotation cursor.
37
+ *
38
+ * Generic over the auth value so providers wrap their own auth flavor
39
+ * (injected test auths, the ADC default) together with the file-backed
40
+ * `GoogleAuth` instances under ONE selection cursor.
41
+ */
42
+ export class ServiceAccountAuthPool {
43
+ auths;
44
+ selection;
45
+ /** Fallback rotation cursor for requests WITHOUT an admitted index. */
46
+ poolCursor = { next: 0 };
47
+ constructor(
48
+ /** Every pooled credential; index-aligned with the provider's clients. */
49
+ auths, selection) {
50
+ this.auths = auths;
51
+ this.selection = selection;
52
+ }
53
+ /**
54
+ * Builds the file-backed pool: each key file is read and turned into its
55
+ * own `GoogleAuth` at construction (fail fast on unreadable or malformed
56
+ * files). Error payloads carry the PATH only — never file contents,
57
+ * client emails, or key material.
58
+ */
59
+ static loadFromKeyFiles(keyFiles, options) {
60
+ const fail = (failure) => options.fail(failure);
61
+ const auths = keyFiles.map((keyFile) => {
62
+ const { credentials } = loadServiceAccountKeyFileSync(keyFile, { fail });
63
+ return createServiceAccountAuth(credentials, options.scopes);
64
+ });
65
+ return new ServiceAccountAuthPool(auths, options);
66
+ }
67
+ /** Number of pooled credentials (quota principals). */
68
+ get size() {
69
+ return this.auths.length;
70
+ }
71
+ /**
72
+ * Picks the pool index for one request: the admitted identity when the
73
+ * request carries one, otherwise the next round-robin entry (a 1-auth
74
+ * pool always selects index 0 — the historical single-auth path).
75
+ */
76
+ selectIndex(preferredIndex) {
77
+ if (preferredIndex !== undefined) {
78
+ if (this.auths[preferredIndex] === undefined) {
79
+ // Fail CLOSED before any wire contact: signing with a different
80
+ // identity than the one admission paced against would silently
81
+ // defeat the per-identity quota contract.
82
+ this.selection.onInvalidSelection("credentialIndex is outside the client pool");
83
+ }
84
+ return preferredIndex;
85
+ }
86
+ if (this.auths.length === 1) {
87
+ return 0;
88
+ }
89
+ return nextPooledClientIndex(this.poolCursor, this.auths.length, undefined);
90
+ }
91
+ }
92
+ //# sourceMappingURL=serviceAccountAuthPool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceAccountAuthPool.js","sourceRoot":"","sources":["../../../../../../../src/auth/serviceAccountAuthPool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EACL,wBAAwB,EACxB,6BAA6B,GAG9B,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAwB,EACxB,WAAmB,EACnB,cAAkC;IAElC,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,cAAc,CAAC;IACxB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,GAAG,WAAW,CAAC;IACxC,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC;IACxC,OAAO,KAAK,CAAC;AACf,CAAC;AAcD;;;;;;GAMG;AACH,MAAM,OAAO,sBAAsB;IAMf;IACC;IANnB,uEAAuE;IACtD,UAAU,GAAqB,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAE5D;IACE,0EAA0E;IAC1D,KAAuB,EACtB,SAA0C;QAD3C,UAAK,GAAL,KAAK,CAAkB;QACtB,cAAS,GAAT,SAAS,CAAiC;IAC1D,CAAC;IAEJ;;;;;OAKG;IACI,MAAM,CAAC,gBAAgB,CAC5B,QAA2B,EAC3B,OAA8C;QAE9C,MAAM,IAAI,GAAG,CAAC,OAAiC,EAAS,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjF,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;YACrC,MAAM,EAAE,WAAW,EAAE,GAAG,6BAA6B,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;YACzE,OAAO,wBAAwB,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,sBAAsB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC;IAED,uDAAuD;IACvD,IAAW,IAAI;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACI,WAAW,CAAC,cAAkC;QACnD,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;YACjC,IAAI,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC7C,gEAAgE;gBAChE,+DAA+D;gBAC/D,0CAA0C;gBAC1C,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,4CAA4C,CAAC,CAAC;YAClF,CAAC;YACD,OAAO,cAAc,CAAC;QACxB,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,qBAAqB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC9E,CAAC;CACF"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared service-account key-file loading + shape validation (Batch A).
3
+ *
4
+ * This is the ONE loader for Google service-account key files, reused by
5
+ * every provider line (Sheets today; Drive/Gmail next) and by the sync
6
+ * auto-start bridge. It reads, JSON-parses, and SHAPE-validates a key file
7
+ * so a misconfigured credential fails fast and locally — a malformed file
8
+ * must never build an auth client that only breaks on first signing.
9
+ *
10
+ * Neutral error contract: this leaf owns no provider vocabulary, so every
11
+ * failure is reported through the caller-supplied `fail` callback as a
12
+ * discriminated `ServiceAccountKeyFailure` (quotaGovernor `timingDefaults`
13
+ * precedent — behavior injected, dependency direction kept acyclic). The
14
+ * Sheets transport maps every kind to its transport error; the sync
15
+ * auto-start bridge maps them to the stable startup codes. Failure payloads
16
+ * carry the PATH (and field names) only — never file contents, client
17
+ * emails, or key material.
18
+ */
19
+ import { GoogleAuth } from "google-auth-library";
20
+ /** Required service-account key-file fields, checked by SHAPE (non-blank string) only at load. */
21
+ export declare const SERVICE_ACCOUNT_KEY_FIELDS: readonly ["type", "client_email", "private_key", "project_id"];
22
+ /** Structured key-file load failure (path-bearing only, never key material). */
23
+ export type ServiceAccountKeyFailure = {
24
+ /** The file could not be read (`code` is the Node errno, e.g. `ENOENT`). */
25
+ readonly kind: "read-error";
26
+ readonly path: string;
27
+ readonly code: string | undefined;
28
+ } | {
29
+ /** The file contents are not valid JSON. */
30
+ readonly kind: "invalid-json";
31
+ readonly path: string;
32
+ } | {
33
+ /** The file parsed but is not a JSON object. */
34
+ readonly kind: "not-object";
35
+ readonly path: string;
36
+ } | {
37
+ /** Required service-account fields are missing or blank (NAMES only, never values). */
38
+ readonly kind: "field-missing";
39
+ readonly path: string;
40
+ readonly fields: readonly string[];
41
+ };
42
+ /** Loader options: the caller's failure mapping (must throw, never return). */
43
+ export interface ServiceAccountKeyLoadOptions {
44
+ readonly fail: (failure: ServiceAccountKeyFailure) => never;
45
+ }
46
+ /** One validated service-account key: raw credentials plus the client email. */
47
+ export interface LoadedServiceAccountKey {
48
+ /** Parsed key JSON, passed straight into `GoogleAuth.credentials` (never logged). */
49
+ readonly credentials: Record<string, unknown>;
50
+ /** The `client_email` field (used for access-denied hints, never logged by this leaf). */
51
+ readonly clientEmail: string;
52
+ }
53
+ /**
54
+ * Validates parsed key JSON by SHAPE and promotes it to a loaded key.
55
+ *
56
+ * Shared by the sync/async file loaders so both paths enforce the identical
57
+ * contract: the payload must be a JSON object whose required
58
+ * service-account fields are non-blank strings. Without this, ANY JSON
59
+ * object builds a GoogleAuth client and the failure surfaces mid-run on
60
+ * first signing instead of at load.
61
+ */
62
+ export declare function parseServiceAccountKeyJson(raw: string, path: string, options: ServiceAccountKeyLoadOptions): LoadedServiceAccountKey;
63
+ /**
64
+ * Loads + validates one key file synchronously (transport construction path).
65
+ *
66
+ * Reads and shape-validates the file at construction so a misconfigured
67
+ * pool fails fast and locally. Failure messages carry the path only.
68
+ */
69
+ export declare function loadServiceAccountKeyFileSync(keyFile: string, options: ServiceAccountKeyLoadOptions): LoadedServiceAccountKey;
70
+ /**
71
+ * Loads + validates one key file asynchronously (startup-bridge path).
72
+ *
73
+ * Same contract as the sync loader; the bridge keeps its own env parsing
74
+ * and error-code mapping and delegates only file loading/validation here.
75
+ */
76
+ export declare function loadServiceAccountKeyFile(keyFile: string, options: ServiceAccountKeyLoadOptions): Promise<LoadedServiceAccountKey>;
77
+ /**
78
+ * Builds one pooled `GoogleAuth` from validated key credentials.
79
+ *
80
+ * The caller supplies the scopes (Sheets/Drive/Gmail each request their
81
+ * own); the cast keeps the google-auth-library version-shape mismatch
82
+ * inside this leaf, exactly like the former per-provider boundary casts.
83
+ */
84
+ export declare function createServiceAccountAuth(credentials: Record<string, unknown>, scopes: readonly string[]): GoogleAuth;
85
+ //# sourceMappingURL=serviceAccountKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceAccountKey.d.ts","sourceRoot":"","sources":["../../../../../../../src/auth/serviceAccountKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAIjD,kGAAkG;AAClG,eAAO,MAAM,0BAA0B,gEAAiE,CAAC;AAEzG,gFAAgF;AAChF,MAAM,MAAM,wBAAwB,GAChC;IACE,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC,GACD;IACE,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GACD;IACE,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GACD;IACE,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC,CAAC;AAEN,+EAA+E;AAC/E,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,KAAK,CAAC;CAC7D;AAED,gFAAgF;AAChF,MAAM,WAAW,uBAAuB;IACtC,qFAAqF;IACrF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,0FAA0F;IAC1F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,4BAA4B,GACpC,uBAAuB,CAkBzB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,4BAA4B,GACpC,uBAAuB,CAQzB;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,uBAAuB,CAAC,CAQlC;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,MAAM,EAAE,SAAS,MAAM,EAAE,GACxB,UAAU,CAKZ"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Shared service-account key-file loading + shape validation (Batch A).
3
+ *
4
+ * This is the ONE loader for Google service-account key files, reused by
5
+ * every provider line (Sheets today; Drive/Gmail next) and by the sync
6
+ * auto-start bridge. It reads, JSON-parses, and SHAPE-validates a key file
7
+ * so a misconfigured credential fails fast and locally — a malformed file
8
+ * must never build an auth client that only breaks on first signing.
9
+ *
10
+ * Neutral error contract: this leaf owns no provider vocabulary, so every
11
+ * failure is reported through the caller-supplied `fail` callback as a
12
+ * discriminated `ServiceAccountKeyFailure` (quotaGovernor `timingDefaults`
13
+ * precedent — behavior injected, dependency direction kept acyclic). The
14
+ * Sheets transport maps every kind to its transport error; the sync
15
+ * auto-start bridge maps them to the stable startup codes. Failure payloads
16
+ * carry the PATH (and field names) only — never file contents, client
17
+ * emails, or key material.
18
+ */
19
+ import { GoogleAuth } from "google-auth-library";
20
+ import { readFileSync } from "node:fs";
21
+ import { readFile } from "node:fs/promises";
22
+ /** Required service-account key-file fields, checked by SHAPE (non-blank string) only at load. */
23
+ export const SERVICE_ACCOUNT_KEY_FIELDS = ["type", "client_email", "private_key", "project_id"];
24
+ /**
25
+ * Validates parsed key JSON by SHAPE and promotes it to a loaded key.
26
+ *
27
+ * Shared by the sync/async file loaders so both paths enforce the identical
28
+ * contract: the payload must be a JSON object whose required
29
+ * service-account fields are non-blank strings. Without this, ANY JSON
30
+ * object builds a GoogleAuth client and the failure surfaces mid-run on
31
+ * first signing instead of at load.
32
+ */
33
+ export function parseServiceAccountKeyJson(raw, path, options) {
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(raw);
37
+ }
38
+ catch {
39
+ options.fail({ kind: "invalid-json", path });
40
+ }
41
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
42
+ options.fail({ kind: "not-object", path });
43
+ }
44
+ const record = parsed;
45
+ const invalidFields = SERVICE_ACCOUNT_KEY_FIELDS.filter((field) => typeof record[field] !== "string" || record[field].trim() === "");
46
+ if (invalidFields.length > 0) {
47
+ options.fail({ kind: "field-missing", path, fields: invalidFields });
48
+ }
49
+ return { credentials: record, clientEmail: record.client_email };
50
+ }
51
+ /**
52
+ * Loads + validates one key file synchronously (transport construction path).
53
+ *
54
+ * Reads and shape-validates the file at construction so a misconfigured
55
+ * pool fails fast and locally. Failure messages carry the path only.
56
+ */
57
+ export function loadServiceAccountKeyFileSync(keyFile, options) {
58
+ let raw;
59
+ try {
60
+ raw = readFileSync(keyFile, "utf8");
61
+ }
62
+ catch (error) {
63
+ options.fail({ kind: "read-error", path: keyFile, code: nodeErrorCode(error) });
64
+ }
65
+ return parseServiceAccountKeyJson(raw, keyFile, options);
66
+ }
67
+ /**
68
+ * Loads + validates one key file asynchronously (startup-bridge path).
69
+ *
70
+ * Same contract as the sync loader; the bridge keeps its own env parsing
71
+ * and error-code mapping and delegates only file loading/validation here.
72
+ */
73
+ export async function loadServiceAccountKeyFile(keyFile, options) {
74
+ let raw;
75
+ try {
76
+ raw = await readFile(keyFile, "utf8");
77
+ }
78
+ catch (error) {
79
+ options.fail({ kind: "read-error", path: keyFile, code: nodeErrorCode(error) });
80
+ }
81
+ return parseServiceAccountKeyJson(raw, keyFile, options);
82
+ }
83
+ /**
84
+ * Builds one pooled `GoogleAuth` from validated key credentials.
85
+ *
86
+ * The caller supplies the scopes (Sheets/Drive/Gmail each request their
87
+ * own); the cast keeps the google-auth-library version-shape mismatch
88
+ * inside this leaf, exactly like the former per-provider boundary casts.
89
+ */
90
+ export function createServiceAccountAuth(credentials, scopes) {
91
+ return new GoogleAuth({
92
+ scopes: [...scopes],
93
+ credentials,
94
+ });
95
+ }
96
+ /** Extracts the Node errno (`ENOENT`, ...) from a caught read failure. */
97
+ function nodeErrorCode(error) {
98
+ if (error !== null && typeof error === "object") {
99
+ const code = error.code;
100
+ return typeof code === "string" ? code : undefined;
101
+ }
102
+ return undefined;
103
+ }
104
+ //# sourceMappingURL=serviceAccountKey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceAccountKey.js","sourceRoot":"","sources":["../../../../../../../src/auth/serviceAccountKey.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,7 @@ import { sheets } from "@googleapis/sheets";
13
13
  import { presentValue, absentValue, PRESENCE_KINDS } from "../../../../contracts/state/index.js";
14
14
  import { HIKOUTEI_LOG_LEVELS, logHikouteiInternalEvent, } from "../../../../contracts/shared/observability/internalLog.js";
15
15
  import { HIKOUTEI_LOG_COMPONENTS, HIKOUTEI_LOG_EVENTS, } from "../../../../contracts/shared/observability/logEvents.js";
16
- import { ServiceAccountAuthPool, } from "@hikoutei/google-auth/auth/serviceAccountAuthPool.js";
16
+ import { ServiceAccountAuthPool, } from "../../../../auth/serviceAccountAuthPool.js";
17
17
  import { GOOGLE_SHEETS_API_SCOPES } from "../constants.js";
18
18
  import { GOOGLE_SHEETS_API_TRANSPORT_ERROR_CODES, GoogleSheetsApiTransportError, invalidProviderRequest, } from "../errors.js";
19
19
  import { parseRawErrorRecord, parseRawErrorText, parseRawHttpStatus, } from "@hikoutei/ikisaki";
@@ -21,7 +21,7 @@
21
21
  * the diagnostic sink (console by default), and thrown fail-closed: a startup
22
22
  * failure never leaves a half-open runtime behind.
23
23
  */
24
- import { loadServiceAccountKeyFile } from "@hikoutei/google-auth/auth/serviceAccountKey.js";
24
+ import { loadServiceAccountKeyFile } from "../../../auth/serviceAccountKey.js";
25
25
  import { getEntityDescriptor, } from "../../api/entity.js";
26
26
  import { validateTypedSheetsOptions, } from "../../api/hikouteiCore.js";
27
27
  import { requireSyncEngineLocalRuntime } from "./compositionPorts.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hikoutei",
3
- "version": "0.10.10-dev",
3
+ "version": "0.10.13-dev",
4
4
  "description": "Typed repository and safe write layer for Google Sheets-backed MVPs. SQLite-authoritative entity lifecycle with asynchronous Google Sheets projection.",
5
5
  "repository": {
6
6
  "type": "git",