hikoutei 0.10.18-dev → 0.10.20-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.
Files changed (4) hide show
  1. package/README.ja.md +74 -216
  2. package/README.ko.md +57 -196
  3. package/README.md +65 -332
  4. package/package.json +1 -1
package/README.ja.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
+ <img src="assets/hikoutei-icon.png" alt="Hikoutei" width="220" />
6
+
5
7
  # Hikoutei
6
8
 
7
9
  **SQLite でアプリは高速に、Google Sheets でワークフローを見えるままに。**
@@ -12,7 +14,7 @@ Google Sheets を利用する MVP 向けの型付きリポジトリであり、
12
14
  Sheets へ非同期で投影されます。
13
15
 
14
16
  <a href="https://www.npmjs.com/package/hikoutei">npm</a> ·
15
- <a href="website/guide/quick-start.md">クイックスタート</a> ·
17
+ <a href="https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/quick-start.md">クイックスタート</a> ·
16
18
  <a href="https://github.com/ManddarinShop/Hikoutei/issues">Issues</a>
17
19
 
18
20
  [![npm version](https://img.shields.io/npm/v/hikoutei?style=flat-square)](https://www.npmjs.com/package/hikoutei)
@@ -34,7 +36,53 @@ Hikoutei は、TypeScript アプリケーションにローカル SQLite を基
34
36
  > Google Sheets を権威あるアプリケーションデータベースとして扱いません。
35
37
  > SQLite が真実の源泉であり、Sheets は人向けの画面です。
36
38
 
37
- ## クイックスタート
39
+ ## Hikoutei を使う理由
40
+
41
+ - シート行を手動で変換する代わりに、型付きエンティティを定義できます。
42
+ - Google Sheets を待たずにローカル SQLite で読み書きできます。
43
+ - コミットされた変更をバックグラウンドで Sheets に同期します。
44
+ - 想定外の列変更や重複ヘッダーを検出します。
45
+ - 競合時に新しいシート編集を上書きしません。
46
+
47
+ Hikoutei は `google-spreadsheet` や `@googleapis/sheets` の代わりではなく、
48
+ その一段上に位置します。生のスプレッドシートアクセスだけが必要なら API
49
+ クライアントを直接使ってください。
50
+
51
+ | 機能 | Hikoutei | google-spreadsheet | @googleapis/sheets |
52
+ | --- | :-: | :-: | :-: |
53
+ | 型付きエンティティモデル | ✅ | ❌ | ❌ |
54
+ | 高速なローカルアプリ読み取り | ✅ | ❌ | ❌ |
55
+ | Sheets への非同期投影 | ✅ | ❌ | ❌ |
56
+ | 耐久性のある書き込みリトライと重複排除 | ✅ | ❌ | ❌ |
57
+ | 競合を考慮したシート更新 | ✅ | ❌ | ❌ |
58
+ | 行・セルの直接操作 | 限定的 | ✅ | ✅ |
59
+ | Google Sheets API へのフルアクセス | Provider 経由 | 一部 | ✅ |
60
+
61
+ ## インストール
62
+
63
+ ```sh
64
+ npm install hikoutei @mikro-orm/core @mikro-orm/sql
65
+ ```
66
+
67
+ ライブラリのインストールだけでは Google Cloud には何も作られません —
68
+ デフォルトはローカル専用(SQLite)で動作します。シート同期が必要なときだけ
69
+ 下の setup を実行してください。
70
+
71
+ ## セットアップ(Google Sheets 同期)
72
+
73
+ 一回きりの対話操作です。gcloud CLI をインストールしてから:
74
+
75
+ ```sh
76
+ npx hikoutei setup
77
+ ```
78
+
79
+ Cloud プロジェクト・サービスアカウント・キー・スプレッドシートを作成し、
80
+ `.env` まで書き出します。`HIKOUTEI_SYNC_SPREADSHEET_URL` がなければ
81
+ `createTypedSheets()` はローカル専用(SQLite)のままです。詳細な設定、
82
+ クレデンシャルプール、クォータガイド、手動セットアップ:
83
+ [Google Sheets の設定](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/setup.md)。
84
+
85
+ ## 使い方
38
86
 
39
87
  スカラーエンティティを定義し、リクエストローカルなマネージャーでローカル
40
88
  SQLite の権威を利用します。
@@ -48,6 +96,8 @@ const User = defineTypedSheetsEntity({
48
96
  properties: {
49
97
  id: { type: "string", primary: true },
50
98
  name: { type: "string" },
99
+ age: { type: "number" },
100
+ active: { type: "boolean" },
51
101
  },
52
102
  });
53
103
 
@@ -57,231 +107,39 @@ const hikoutei = await createTypedSheets({
57
107
  });
58
108
 
59
109
  const em = hikoutei.em.fork();
60
- const user = em.create(User, { id: "u1", name: "Ada" });
110
+ const user = em.create(User, { id: "u1", name: "Ada", age: 36, active: true });
61
111
  em.persist(user);
62
112
  await em.flush();
63
113
 
64
114
  user.name = "Ada Lovelace";
65
115
  await em.flush();
66
- ```
67
-
68
- **シートには何が起きるのか?** 書き込みは即座にローカル SQLite へコミット
69
- されます — アプリケーションのリクエストは Google を待ちません。同期サービスが
70
- 有効な場合、Hikoutei は後でエンティティを登録済みの Google Sheet へバック
71
- グラウンドで投影します。シート上で行われた人の編集は観察・検証され、SQLite
72
- へ受け入れられるか競合として記録されるかのどちらかで、決して静かに上書き
73
- されません。
74
-
75
- ## Hikoutei を使う理由
76
-
77
- - シートの行を手動で変換する代わりに、型付きエンティティを定義する。
78
- - Google Sheets を待たずにローカル SQLite で読み書きする。
79
- - コミットされた変更を Sheets へバックグラウンドで同期する。
80
- - 予期しない列の変更や重複ヘッダーを検出する。
81
- - 競合時に新しいシート編集を上書きしない。
82
-
83
- ## Hikoutei が適しているケース
84
-
85
- Hikoutei は以下のケースに適しています。
86
-
87
- - 製品ワークフローの一部にスプレッドシートがある MVP・プロトタイプ
88
- - 社内ツールや低トラフィックの管理アプリケーション
89
- - 人が Sheets を簡単に確認しつつ、型付きのアプリケーションデータを扱いたい
90
- チーム
91
- - SQLite をローカルで使い、非同期のシート更新を受け入れられるサービス
92
-
93
- ## 他のツールを選ぶべきケース
94
-
95
- 次の要件がある場合は、通常のデータベースと Google API を直接使ってください。
96
116
 
97
- - 多くの行やサービスにまたがる強いトランザクション
98
- - 高い書き込みスループットや多数の同時ライター
99
- - 複雑なクエリ・JOIN・レポートワークロード
100
- - マルチサーバー・マルチリージョンでの調整
101
- - Google Sheets での読み取り直後の整合性
102
- - Google Sheets をアプリケーションの主要データベースとして使う場合
103
-
104
- ## Hikoutei はあなたに合った抽象化か?
105
-
106
- Hikoutei は `google-spreadsheet` や `@googleapis/sheets` を置き換えるものでは
107
- ありません — その一段上に位置します。生のスプレッドシート操作だけが必要なら、
108
- API クライアントを直接使ってください。
109
-
110
- | 機能 | Hikoutei | google-spreadsheet | @googleapis/sheets |
111
- | --- | :-: | :-: | :-: |
112
- | 型付きエンティティモデル | ✅ | ❌ | ❌ |
113
- | 高速なローカルアプリケーション読み取り | ✅ | ❌ | ❌ |
114
- | Sheets への非同期投影 | ✅ | ❌ | ❌ |
115
- | 永続的な書き込み再試行と重複排除 | ✅ | ❌ | ❌ |
116
- | 競合を考慮したシート更新 | ✅ | ❌ | ❌ |
117
- | 行・セルの直接操作 | 限定的 | ✅ | ✅ |
118
- | Google Sheets API へのフルアクセス | Provider 経由 | 部分的 | ✅ |
119
-
120
- ## Google Sheets の設定
121
-
122
- Google Sheets の同期はサービス側の関心事です。アプリケーションは provider
123
- クライアントを import したり、`createTypedSheets()` にシートルートを渡したり、
124
- 書き込みごとに操作を選んだりしません — ルート API が受け取るのは `dbName` と
125
- `entities` だけです。同期ランタイムはサービスアカウントを使う単一の内部
126
- Google Sheets API provider を使用します — Apps Script のデプロイは不要です。
127
- 同期の自動開始は `HIKOUTEI_SYNC_SPREADSHEET_URL` と
128
- `GOOGLE_APPLICATION_CREDENTIALS` で選択され、設定する公開
129
- `googleSheetsApi` bootstrap オプションはありません。
130
-
131
- ### 環境変数による同期の自動開始
132
-
133
- スプレッドシートの URL を環境変数に設定すると、`createTypedSheets()` が内部で
134
- Sheets 同期を開始します — `flush()` はセットアップコードなしで outbox
135
- ワーカー経由で Google Sheets に流れます:
136
-
137
- ```sh
138
- HIKOUTEI_SYNC_SPREADSHEET_URL=https://docs.google.com/spreadsheets/d/<ID>/edit
139
- GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/service-account.json
117
+ const loaded = await em.findOne(User, { id: "u1" });
118
+ if (loaded !== null) {
119
+ em.remove(loaded);
120
+ await em.flush();
121
+ }
140
122
  ```
141
123
 
142
- ```ts
143
- const hikoutei = await createTypedSheets({ dbName: "./hikoutei.sqlite", entities: [User] });
144
- ```
124
+ 読み取り・トランザクション・演算子の詳細:
125
+ [クイックスタート](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/quick-start.md)。
145
126
 
146
- `HIKOUTEI_SYNC_SPREADSHEET_URL` がない場合、`createTypedSheets()` はローカル
147
- 専用 (SQLite) のままです。起動失敗は明確なメッセージで診断されます: URL の
148
- 不正、資格情報ファイルの欠落・不正、スプレッドシートに共有されていない
149
- サービスアカウント (共有すべきメールアドレスをエラーが教えてくれます)。
150
-
151
- ### 手動でのサービスアカウント設定
152
-
153
- 1. **サービスアカウントを作成する。** Cloud プロジェクトで Google Sheets API
154
- を有効化し、`https://www.googleapis.com/auth/spreadsheets` スコープの
155
- サービスアカウントを作成して、対象スプレッドシートをそのメールアドレスに
156
- **編集者(Editor)**として共有します。provider がタブを作成し、効果行と
157
- receipt レコードを書き、行アンカーを管理するため、閲覧者権限では不十分
158
- です。
159
- 2. **キーはサーバー側に置く。** サービスアカウントのキーパスはサーバーの
160
- `GOOGLE_APPLICATION_CREDENTIALS` に、スプレッドシート ID は追跡されない
161
- シークレットストアに置きます。キーをブラウザコードや Git に入れないで
162
- ください。
163
- 3. **アプリケーションを通常どおり実行する。** `GOOGLE_APPLICATION_CREDENTIALS`
164
- と `HIKOUTEI_SYNC_SPREADSHEET_URL` を設定した状態でアプリを起動すると、
165
- `createTypedSheets()` がそれを検出して内部 sync bootstrap を開始します —
166
- 登録済みタブのヘッダーを作成・検証した後、outbox 配信と User_Input
167
- ポーリングを開始します。渡す provider オプションも、手動で開始する内部
168
- bootstrap もありません。
169
-
170
- > **レガシースプレッドシートの注意。** 旧 Apps Script provider が developer
171
- > metadata の行アンカーでプロビジョニングしたスプレッドシートは移行されませ
172
- > ん。`User_Input` タブは `__hikoutei_row_id` システム列を必要とするため、
173
- > レガシータブは再プロビジョニングが必要です。
174
-
175
- Hikoutei は、永続的なローカル outbox・冪等な配信・競合を考慮した更新を使う
176
- ため、一時的な API 障害でコミット済みのアプリケーション書き込みが失われる
177
- ことはありません。provider は資格情報・スプレッドシート ID・URL・ペイロードを
178
- ログに残さず、Google の割り当て枠に収まるようリクエスト開始間隔を調整します。
179
- 詳細な状態機械と復旧ルールは[内部整合性モデル](website/guide/internal-consistency.md)を
180
- 参照してください。
181
-
182
- ライブの Google 呼び出しはオプトインであり、通常の検証経路はフェイク
183
- provider と SQLite フィクスチャです。詳細なセットアップとトラブルシューティン
184
- グは[クイックスタート](website/guide/quick-start.md)を参照してください。
127
+ 書き込みは即座にローカル SQLite にコミットされます — リクエストは Google を
128
+ 待ちません。シートで人が編集するとポーリングで戻ってきて、SQLite に取り込まれる
129
+ か競合として記録され、黙って上書きされることはありません。パイプライン全体
130
+ (outbox、配信、競合処理)は
131
+ [書き込みと同期フロー](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/sync-flow.md) を参照してください。
185
132
 
186
- ## インストール
187
-
188
- プロジェクト名と npm パッケージ名はどちらも `hikoutei` です。現在の組み込み
189
- SQLite provider は MikroORM を必要とします。
190
-
191
- ```sh
192
- npm install hikoutei @mikro-orm/core @mikro-orm/sql
193
- ```
194
-
195
- MikroORM は実装の詳細であり、Hikoutei の公開エンティティ API には現れません。
196
-
197
- ## ドキュメント
198
-
199
- - [クイックスタート](website/guide/quick-start.md) — インストール、ORM ライフサイクル、
200
- サービス側の同期設定
201
- - [アーキテクチャ](website/guide/architecture.md) — ローカルストアとシートビューの関係
202
- - [書き込みと同期フロー](website/guide/sync-flow.md) — 非同期配信と
203
- 復旧動作
204
- - [内部整合性モデル](website/guide/internal-consistency.md) — 永続的な outbox、
205
- 冪等な配信、競合を考慮した更新
206
- - [開発](website/guide/contributing.md) — ローカル開発とテストコマンド
207
- - [ベンチマークノート](website/guide/benchmarks.md) — 日付付きの測定と
208
- その限界
209
-
210
- ## 制限事項
211
-
212
- - Google Sheets には割り当て枠・レイテンシ・API レート制限があります。
213
- - シートの更新は非同期であり、アプリケーションはローカル状態を読むべきです。
214
- - SQLite はサービスのローカルのみであり、分散調整レイヤーではありません。
215
- - スキーマ変更・手動編集・競合更新には、依然としてアプリケーションの運用
216
- ポリシーが必要です。
217
-
218
- ## ローカルクエリ
219
-
220
- 読み取りは Hikoutei 独自の型付き演算子を使い、常に SQLite で実行されます。
221
-
222
- ```ts
223
- const [users, total] = await em.findAndCount(
224
- User,
225
- {
226
- name: { like: "Ada%" },
227
- age: { gte: 18, lt: 65 },
228
- active: { in: [true] },
229
- },
230
- {
231
- orderBy: { age: "desc", name: "asc" },
232
- limit: 20,
233
- offset: 0,
234
- },
235
- );
236
- ```
133
+ ## さらに読む
237
134
 
238
- `eq`、`ne`、`gt`、`gte`、`lt`、`lte`、`in`、`nin` は、宣言された
239
- スカラー型で有効な範囲で利用でき、`like` は文字列専用です。
240
- `{ active: true }` のような等価条件の省略記法も引き続き利用できます。
241
- `count()` はページネーション前のフィルター総数を返し、`findAndCount()` は
242
- 1 つの SQLite スナップショットからページと総数を読み取ります。明示的な
243
- 並び順には最後のタイブレーカーとして主キーが追加され、`orderBy` のない
244
- ページネーションは主キーの昇順を使用します。
245
-
246
- ## プロジェクトステータス
247
-
248
- Hikoutei は活発に開発中です。現在の EntityManager は、スカラーエンティティの
249
- ライフサイクル操作、型付きローカルフィルターと並び順、`limit` / `offset`
250
- ページネーション、`count()`、スナップショット整合性のある `findAndCount()`、
251
- コールバック形式の `transactional()` をサポートします。通常の読み取り元は
252
- 常に SQLite であり、Google Sheets ではありません。シート編集の取り込みと
253
- 競合表示はまだ発展途上です。マイナーバージョンのアップグレード前にリリース
254
- ノートを確認してください。
255
-
256
- ## ロードマップ
257
-
258
- 最初の EntityManager 段階である豊富なローカル読み取りは完了しました。残る段階は
259
- 以下の実装順序に従い、日付やリリース番号は約束しません。
260
-
261
- 1. **ライフサイクル安全な書き込み**
262
- - `upsert` と direct/bulk mutation 機能は、エンティティテーブル、
263
- canonical state、永続的な Sheet effect outbox を 1 つの SQLite
264
- トランザクションで処理する Hikoutei 独自の契約を通じてのみ追加します。
265
- - この原子的なライフサイクルを迂回し得る、生の `nativeInsert`、
266
- `nativeUpdate`、`nativeDelete`、または SQL パススルー API は約束しません。
267
- 2. **リレーションとロード**
268
- - many-to-one、one-to-many、`populate()` 機能を追加します。
269
- - 公開前に、リレーションの SQLite マッピング、Sheets プロジェクション
270
- 表現、スキーマ動作、競合セマンティクスを一体として設計します。
271
- 3. **スキーマ運用**
272
- - マイグレーションとスキーマドリフト管理を追加します。
273
- - 検証と運用フローを既存のセットアップツールと統合します。
274
-
275
- ### 同期と運用
276
-
277
- 以下の作業は EntityManager の各段階と並行して進めます。
278
-
279
- - Google Sheets からの意図的なユーザー編集の取り込みを完了する
280
- - 更新・削除の競合処理と表示を改善する
281
-
282
- 現在の作業は[オープンな Issues](https://github.com/ManddarinShop/Hikoutei/issues)を
283
- 参照してください。
135
+ - [クイックスタート](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/quick-start.md) — インストール、ORM
136
+ ライフサイクル、同期セットアップ
137
+ - [アーキテクチャ](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/architecture.md) — ローカルストアとシート
138
+ 画面の連携
139
+ - [書き込みと同期フロー](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/sync-flow.md) — 非同期配信とリカバリ
140
+ - [制限事項](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/limitations.md) — 他のツールを選ぶべきケース
141
+ - [プロジェクトステータスとロードマップ](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/status.md) — 完了済みと次の作業
284
142
 
285
143
  ## ライセンス
286
144
 
287
- Hikoutei は [MIT ライセンス](LICENSE)で公開されています。
145
+ Hikoutei は [MIT ライセンス](LICENSE) のもとで公開されています。
package/README.ko.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
+ <img src="assets/hikoutei-icon.png" alt="Hikoutei" width="220" />
6
+
5
7
  # Hikoutei
6
8
 
7
9
  **SQLite로 앱은 빠르게, Google Sheets로 업무 흐름은 눈에 보이게.**
@@ -11,7 +13,7 @@ Google Sheets 기반 MVP를 위한 타입 안전 리포지토리이자 안전한
11
13
  사람이 검토하고 가볍게 협업할 수 있도록 Google Sheets에 비동기로 투영됩니다.
12
14
 
13
15
  <a href="https://www.npmjs.com/package/hikoutei">npm</a> ·
14
- <a href="website/guide/quick-start.md">빠른 시작</a> ·
16
+ <a href="https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/quick-start.md">빠른 시작</a> ·
15
17
  <a href="https://github.com/ManddarinShop/Hikoutei/issues">이슈</a>
16
18
 
17
19
  [![npm version](https://img.shields.io/npm/v/hikoutei?style=flat-square)](https://www.npmjs.com/package/hikoutei)
@@ -32,43 +34,6 @@ Sheets는 검토, 운영, 가벼운 협업을 위한 화면으로 남습니다.
32
34
  > Google Sheets를 권위 있는 애플리케이션 데이터베이스로 취급하지 않습니다.
33
35
  > SQLite가 진실의 원천이고, Sheets는 사람을 위한 화면입니다.
34
36
 
35
- ## 빠른 시작
36
-
37
- 스칼라 엔티티를 정의하고 요청-로컬 매니저를 통해 로컬 SQLite authority를
38
- 사용합니다.
39
-
40
- ```ts
41
- import { createTypedSheets, defineTypedSheetsEntity } from "hikoutei";
42
-
43
- const User = defineTypedSheetsEntity({
44
- name: "User",
45
- tableName: "users",
46
- properties: {
47
- id: { type: "string", primary: true },
48
- name: { type: "string" },
49
- },
50
- });
51
-
52
- const hikoutei = await createTypedSheets({
53
- dbName: "./hikoutei.sqlite",
54
- entities: [User],
55
- });
56
-
57
- const em = hikoutei.em.fork();
58
- const user = em.create(User, { id: "u1", name: "Ada" });
59
- em.persist(user);
60
- await em.flush();
61
-
62
- user.name = "Ada Lovelace";
63
- await em.flush();
64
- ```
65
-
66
- **시트에는 무슨 일이 일어날까?** 쓰기는 즉시 로컬 SQLite에 커밋됩니다 —
67
- 애플리케이션 요청은 Google을 기다리지 않습니다. 동기화 서비스가 활성화되면
68
- Hikoutei는 나중에 엔티티를 등록된 Google Sheet에 백그라운드로 투영합니다.
69
- 시트에서 이루어진 사람의 수정은 관찰되고 검증되어 SQLite로 수용되거나
70
- 충돌로 기록되며, 절대 조용히 덮어쓰이지 않습니다.
71
-
72
37
  ## Hikoutei를 쓰는 이유
73
38
 
74
39
  - 시트 행을 수동으로 변환하는 대신 타입 지정 엔티티를 정의합니다.
@@ -77,29 +42,6 @@ Hikoutei는 나중에 엔티티를 등록된 Google Sheet에 백그라운드로
77
42
  - 예상치 못한 컬럼 변경과 중복 헤더를 감지합니다.
78
43
  - 충돌 중에 더 새로운 시트 수정을 덮어쓰지 않습니다.
79
44
 
80
- ## Hikoutei를 쓰기 좋은 때
81
-
82
- Hikoutei는 다음 상황에 잘 맞습니다.
83
-
84
- - 제품 워크플로의 일부로 스프레드시트가 있는 MVP와 프로토타입
85
- - 내부 도구와 저트래픽 관리 애플리케이션
86
- - 사람들이 Sheets를 쉽게 확인하면서 타입 지정된 애플리케이션 데이터를
87
- 유지하려는 팀
88
- - SQLite를 로컬에서 사용하고 비동기 시트 업데이트를 수용할 수 있는 서비스
89
-
90
- ## 다른 도구를 선택해야 할 때
91
-
92
- 다음이 필요하다면 일반 데이터베이스와 Google API를 직접 사용하세요.
93
-
94
- - 여러 행이나 서비스에 걸친 강력한 트랜잭션
95
- - 높은 쓰기 처리량 또는 많은 동시 작성자
96
- - 복잡한 쿼리, 조인, 리포팅 워크로드
97
- - 멀티 서버·멀티 리전 조정
98
- - Google Sheets에서의 즉시 읽기-쓰기 일관성
99
- - Google Sheets를 애플리케이션의 주 데이터베이스로 사용
100
-
101
- ## Hikoutei가 당신에게 맞는 추상화인가요?
102
-
103
45
  Hikoutei는 `google-spreadsheet`나 `@googleapis/sheets`를 대체하지 않습니다 —
104
46
  한 단계 위에 위치합니다. 원시 스프레드시트 접근만 필요하다면 API 클라이언트를
105
47
  직접 사용하세요.
@@ -114,164 +56,83 @@ Hikoutei는 `google-spreadsheet`나 `@googleapis/sheets`를 대체하지 않습
114
56
  | 행·셀 직접 조작 | 제한적 | ✅ | ✅ |
115
57
  | 전체 Google Sheets API 접근 | Provider 경유 | 부분적 | ✅ |
116
58
 
117
- ## Google Sheets 설정
118
-
119
- Google Sheets 동기화는 서비스 측 관심사입니다. 애플리케이션은 provider
120
- 클라이언트를 import하거나, Sheet 라우트를 `createTypedSheets()`에 넘기거나,
121
- 쓰기마다 연산을 선택하지 않습니다 — 루트 API는 `dbName`과 `entities`만
122
- 받습니다. 동기화 런타임은 서비스 계정을 사용하는 하나의 내부 Google Sheets
123
- API provider를 사용합니다 — Apps Script 배포가 없습니다. 동기화 자동 시작은
124
- `HIKOUTEI_SYNC_SPREADSHEET_URL`과 `GOOGLE_APPLICATION_CREDENTIALS`로
125
- 선택되며, 설정할 공개 `googleSheetsApi` bootstrap 옵션은 없습니다.
126
-
127
- ### 환경 변수 기반 동기화 자동 시작
128
-
129
- 스프레드시트 URL을 환경 변수로 설정하면 `createTypedSheets()`가 내부적으로
130
- Sheets 동기화를 시작합니다 — `flush()`는 설정 코드 없이 outbox worker를 통해
131
- Google Sheets로 흘러갑니다:
132
-
133
- ```sh
134
- HIKOUTEI_SYNC_SPREADSHEET_URL=https://docs.google.com/spreadsheets/d/<ID>/edit
135
- GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/service-account.json
136
- ```
137
-
138
- ```ts
139
- const hikoutei = await createTypedSheets({ dbName: "./hikoutei.sqlite", entities: [User] });
140
- ```
141
-
142
- `HIKOUTEI_SYNC_SPREADSHEET_URL`이 없으면 `createTypedSheets()`는 로컬 전용
143
- (SQLite)으로 유지됩니다. 시작 실패는 명확한 메시지로 진단됩니다: 잘못된 URL,
144
- 없거나 잘못된 자격 증명 파일, 스프레드시트에 공유되지 않은 서비스 계정
145
- (어떤 이메일을 공유해야 하는지 에러가 알려줍니다).
146
-
147
- ### 수동 서비스 계정 설정
148
-
149
- 1. **서비스 계정을 만듭니다.** Cloud 프로젝트에서 Google Sheets API를
150
- 활성화하고, `https://www.googleapis.com/auth/spreadsheets` 스코프의
151
- 서비스 계정을 만든 뒤 대상 스프레드시트를 해당 이메일에 **편집자(Editor)**로
152
- 공유합니다. provider가 탭을 만들고, 효과 행과 receipt 기록을 쓰고, 행
153
- anchor를 관리하므로 뷰어 권한으로는 부족합니다.
154
- 2. **키를 서버 측에 둡니다.** 서비스 계정 키 경로를 서버의
155
- `GOOGLE_APPLICATION_CREDENTIALS`에, 스프레드시트 ID는 추적되지 않는
156
- 비밀 저장소에 둡니다. 키를 브라우저 코드나 Git에 넣지 마세요.
157
- 3. **애플리케이션을 정상적으로 실행합니다.** `GOOGLE_APPLICATION_CREDENTIALS`와
158
- `HIKOUTEI_SYNC_SPREADSHEET_URL`을 설정한 상태로 앱을 시작하면
159
- `createTypedSheets()`가 이를 감지해 내부 sync bootstrap을 시작합니다 —
160
- 등록된 탭의 헤더를 만들고 검증한 뒤 outbox 전달과 User_Input 폴링을
161
- 시작합니다. 넘길 provider 옵션이나 직접 시작할 내부 bootstrap은 없습니다.
162
-
163
- > **레거시 스프레드시트 참고.** 이전 Apps Script provider가 developer
164
- > metadata 행 anchor로 프로비저닝한 스프레드시트는 마이그레이션되지
165
- > 않습니다. `User_Input` 탭은 이제 `__hikoutei_row_id` 시스템 컬럼이
166
- > 필요하므로 레거시 탭을 다시 프로비저닝해야 합니다.
167
-
168
- Hikoutei는 내구성 있는 로컬 outbox, 멱등 전달, 충돌을 인지하는 업데이트를
169
- 사용하므로 일시적인 API 실패가 커밋된 애플리케이션 쓰기를 잃게 하지 않습니다.
170
- provider는 자격 증명, 스프레드시트 ID, URL, 페이로드를 로그에 남기지 않으며,
171
- Google 할당량 창 안에 머물도록 요청 시작 간격을 조절합니다. 상세 상태 머신과
172
- 복구 규칙은 [내부 정합성 모델](website/guide/internal-consistency.md)을
173
- 참고하세요.
174
-
175
- 라이브 Google 호출은 opt-in이며, 일반적인 검증 경로는 fake provider와 SQLite
176
- fixture입니다. 자세한 설정과 문제 해결 단계는 [빠른 시작](website/guide/quick-start.md)을
177
- 참고하세요.
178
-
179
59
  ## 설치
180
60
 
181
- 프로젝트와 npm 패키지 이름은 모두 `hikoutei`입니다. 내장 SQLite provider는
182
- 현재 MikroORM을 필요로 합니다.
183
-
184
61
  ```sh
185
62
  npm install hikoutei @mikro-orm/core @mikro-orm/sql
186
63
  ```
187
64
 
188
- MikroORM은 구현 세부 사항이며 Hikoutei의 공개 엔티티 API에는 나타나지
189
- 않습니다.
65
+ 라이브러리 설치만 하면 Google Cloud에는 아무 것도 만들지 않습니다 — 기본은
66
+ 로컬 전용(SQLite)으로 동작합니다. 시트 동기화를 원할 때만 아래 setup을
67
+ 실행하세요.
190
68
 
191
- ## 문서
69
+ ## 설정 (Google Sheets 동기화)
192
70
 
193
- - [빠른 시작](website/guide/quick-start.md) — 설치, ORM 생명주기, 서비스 측 동기화 설정
194
- - [아키텍처](website/guide/architecture.md) — 로컬 저장소와 Sheet 화면이 맞물리는 방식
195
- - [쓰기 및 동기화 흐름](website/guide/sync-flow.md) — 비동기 전달과
196
- 복구 동작
197
- - [내부 정합성 모델](website/guide/internal-consistency.md) — 내구성 outbox,
198
- 멱등 전달, 충돌을 인지하는 업데이트
199
- - [개발](website/guide/contributing.md) — 로컬 개발 및 테스트 명령어
200
- - [벤치마크 노트](website/guide/benchmarks.md) — 날짜가 기록된 측정과
201
- 그 한계
71
+ 1회성 인터랙티브 작업입니다. gcloud CLI 설치 후:
202
72
 
203
- ## 한계
73
+ ```sh
74
+ npx hikoutei setup
75
+ ```
204
76
 
205
- - Google Sheets에는 quota, 지연, API 요청 제한이 있습니다.
206
- - 시트 업데이트는 비동기이며, 애플리케이션은 로컬 상태를 읽어야 합니다.
207
- - SQLite는 서비스 로컬 전용이며 분산 조정 계층이 아닙니다.
208
- - 스키마 변경, 수동 편집, 충돌 업데이트에는 여전히 애플리케이션의 운영 정책이
209
- 필요합니다.
77
+ Cloud 프로젝트, 서비스 계정, 키, 스프레드시트를 만들고 `.env`까지 써 줍니다.
78
+ `HIKOUTEI_SYNC_SPREADSHEET_URL`이 없으면 `createTypedSheets()`는 로컬 전용
79
+ (SQLite)으로 유지됩니다. 상세 설정, 크리덴셜 풀, 쿼터 가이드, 수동 설정:
80
+ [Google Sheets 설정](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/setup.md).
210
81
 
211
- ## 로컬 쿼리
82
+ ## 사용법
212
83
 
213
- 읽기는 Hikoutei가 정의한 타입 연산자를 사용하며 항상 SQLite에서 실행됩니다.
84
+ 스칼라 엔티티를 정의하고 요청-로컬 매니저를 통해 로컬 SQLite authority를
85
+ 사용합니다.
214
86
 
215
87
  ```ts
216
- const [users, total] = await em.findAndCount(
217
- User,
218
- {
219
- name: { like: "Ada%" },
220
- age: { gte: 18, lt: 65 },
221
- active: { in: [true] },
222
- },
223
- {
224
- orderBy: { age: "desc", name: "asc" },
225
- limit: 20,
226
- offset: 0,
227
- },
228
- );
229
- ```
230
-
231
- `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`은 선언된 스칼라 타입에
232
- 허용되는 범위에서 사용할 수 있고, `like`는 문자열 전용입니다. `{ active: true }`
233
- 같은 동등 조건 축약도 계속 지원합니다. `count()`는 페이지네이션 전 필터 전체 개수를
234
- 반환하고, `findAndCount()`는 한 SQLite 스냅샷에서 페이지와 전체 개수를 읽습니다.
235
- 명시적 정렬에는 마지막 동률 해소 기준으로 PK가 추가되며, `orderBy` 없는
236
- 페이지네이션은 PK 오름차순을 사용합니다.
88
+ import { createTypedSheets, defineTypedSheetsEntity } from "hikoutei";
237
89
 
238
- ## 프로젝트 상태
90
+ const User = defineTypedSheetsEntity({
91
+ name: "User",
92
+ tableName: "users",
93
+ properties: {
94
+ id: { type: "string", primary: true },
95
+ name: { type: "string" },
96
+ age: { type: "number" },
97
+ active: { type: "boolean" },
98
+ },
99
+ });
239
100
 
240
- Hikoutei는 활발히 개발 중입니다. 현재 EntityManager는 스칼라 엔티티 생명주기,
241
- 타입 로컬 필터와 정렬, `limit` / `offset` 페이지네이션, `count()`, 스냅샷이
242
- 일관된 `findAndCount()`, 콜백형 `transactional()`을 지원합니다. 일반 읽기는
243
- Google Sheets가 아니라 항상 SQLite에서 수행됩니다. 시트 편집 수집과 충돌 표시는
244
- 아직 발전 중입니다. 마이너 버전 업그레이드 전에 릴리스 노트를 확인하세요.
101
+ const hikoutei = await createTypedSheets({
102
+ dbName: "./hikoutei.sqlite",
103
+ entities: [User],
104
+ });
245
105
 
246
- ## 로드맵
106
+ const em = hikoutei.em.fork();
107
+ const user = em.create(User, { id: "u1", name: "Ada", age: 36, active: true });
108
+ em.persist(user);
109
+ await em.flush();
247
110
 
248
- 첫 EntityManager 단계인 풍부한 로컬 읽기는 완료됐습니다. 남은 단계는 아래 구현
249
- 순서를 따르며, 일정이나 릴리스 번호는 약속하지 않습니다.
111
+ user.name = "Ada Lovelace";
112
+ await em.flush();
250
113
 
251
- 1. **생명주기 안전 쓰기**
252
- - `upsert`와 direct/bulk mutation 기능은 엔티티 테이블, canonical state,
253
- 내구성 있는 Sheet effect outbox를 하나의 SQLite 트랜잭션에서 처리하는
254
- Hikoutei 정의 계약을 통해서만 추가합니다.
255
- - 이 원자적 생명주기를 우회할 수 있는 원시 `nativeInsert`, `nativeUpdate`,
256
- `nativeDelete` 또는 SQL 패스스루 API는 약속하지 않습니다.
257
- 2. **관계와 로딩**
258
- - many-to-one, one-to-many, `populate()` 기능을 추가합니다.
259
- - 공개 전에 관계의 SQLite 매핑, Sheets 프로젝션 표현, 스키마 동작, 충돌
260
- 의미론을 함께 설계합니다.
261
- 3. **스키마 운영**
262
- - 마이그레이션과 스키마 드리프트 관리를 추가합니다.
263
- - 검증 및 운영 흐름을 기존 설정 도구와 통합합니다.
114
+ const loaded = await em.findOne(User, { id: "u1" });
115
+ if (loaded !== null) {
116
+ em.remove(loaded);
117
+ await em.flush();
118
+ }
119
+ ```
264
120
 
265
- ### 동기화 및 운영
121
+ 더 많은 읽기·트랜잭션·연산자: [빠른 시작](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/quick-start.md).
266
122
 
267
- 다음 작업은 EntityManager 단계와 병행합니다.
123
+ 쓰기는 즉시 로컬 SQLite에 커밋됩니다 — 요청은 Google을 기다리지 않습니다.
124
+ 시트에서 사람이 편집하면 폴링으로 되돌아와 SQLite에 수용되거나 충돌로
125
+ 기록되며, 절대 조용히 덮어쓰이지 않습니다. 전체 파이프라인(outbox, 전달,
126
+ 충돌 처리)은 [쓰기 및 동기화 흐름](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/sync-flow.md)을 참고하세요.
268
127
 
269
- - Google Sheets에서 의도적인 사용자 편집 수집 완성
270
- - 업데이트·삭제 충돌 처리와 표시 개선
128
+ ## 더 보기
271
129
 
272
- 현재 작업은 [오픈 이슈](https://github.com/ManddarinShop/Hikoutei/issues)를
273
- 참고하세요.
130
+ - [빠른 시작](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/quick-start.md) — 설치, ORM 생명주기, 동기화 설정
131
+ - [아키텍처](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/architecture.md) — 로컬 저장소와 Sheet 화면이 맞물리는 방식
132
+ - [쓰기 및 동기화 흐름](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/sync-flow.md) — 비동기 전달과 복구 동작
133
+ - [한계](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/limitations.md) — 다른 도구를 선택해야 할 때
134
+ - [프로젝트 상태와 로드맵](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/status.md) — 완료된 것과 다음 작업
274
135
 
275
136
  ## 라이선스
276
137
 
277
- Hikoutei는 [MIT 라이선스](LICENSE)로 배포됩니다.
138
+ Hikoutei는 [MIT 라이선스](LICENSE)로 배포됩니다.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
+ <img src="assets/hikoutei-icon.png" alt="Hikoutei" width="220" />
6
+
5
7
  # Hikoutei
6
8
 
7
9
  **Keep your app fast with SQLite. Keep your workflow visible in Google Sheets.**
@@ -12,7 +14,7 @@ changes are asynchronously projected to Google Sheets for human review and
12
14
  lightweight collaboration.
13
15
 
14
16
  <a href="https://www.npmjs.com/package/hikoutei">npm</a> ·
15
- <a href="website/guide/quick-start.md">Quick start</a> ·
17
+ <a href="https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/quick-start.md">Quick start</a> ·
16
18
  <a href="https://github.com/ManddarinShop/Hikoutei/issues">Issues</a>
17
19
 
18
20
  [![npm version](https://img.shields.io/npm/v/hikoutei?style=flat-square)](https://www.npmjs.com/package/hikoutei)
@@ -34,46 +36,6 @@ collaboration.
34
36
  > and it does not treat Google Sheets as the authoritative application
35
37
  > database. SQLite is the source of truth; Sheets is the human-facing view.
36
38
 
37
- ## Quick start
38
-
39
- Define a scalar entity and use the local SQLite authority through a
40
- request-local manager.
41
-
42
- ```ts
43
- import { createTypedSheets, defineTypedSheetsEntity } from "hikoutei";
44
-
45
- const User = defineTypedSheetsEntity({
46
- name: "User",
47
- tableName: "users",
48
- properties: {
49
- id: { type: "string", primary: true },
50
- name: { type: "string" },
51
- age: { type: "number" },
52
- active: { type: "boolean" },
53
- },
54
- });
55
-
56
- const hikoutei = await createTypedSheets({
57
- dbName: "./hikoutei.sqlite",
58
- entities: [User],
59
- });
60
-
61
- const em = hikoutei.em.fork();
62
- const user = em.create(User, { id: "u1", name: "Ada", age: 36, active: true });
63
- em.persist(user);
64
- await em.flush();
65
-
66
- user.name = "Ada Lovelace";
67
- await em.flush();
68
- ```
69
-
70
- **What happens to the Sheet?** The write commits to local SQLite immediately —
71
- the application request never waits on Google. When the sync service is
72
- enabled, Hikoutei later projects the entity to the registered Google Sheet in
73
- the background. Human edits made in the sheet are observed, validated, and
74
- either accepted back into SQLite or recorded as conflicts, never silently
75
- overwritten.
76
-
77
39
  ## Why Hikoutei?
78
40
 
79
41
  - Define typed entities instead of manually converting Sheet rows.
@@ -82,29 +44,6 @@ overwritten.
82
44
  - Detect unexpected column changes and duplicate headers.
83
45
  - Avoid overwriting newer Sheet edits during conflicting updates.
84
46
 
85
- ## When to use Hikoutei
86
-
87
- Hikoutei is a good fit for:
88
-
89
- - MVPs and prototypes where a spreadsheet is part of the product workflow.
90
- - Internal tools and low-traffic administrative applications.
91
- - Teams that want typed application data while keeping Sheets easy for people
92
- to inspect.
93
- - Services that can use SQLite locally and accept asynchronous Sheet updates.
94
-
95
- ## When to choose something else
96
-
97
- Use a conventional database and direct Google APIs when you need:
98
-
99
- - Strong transactions across many rows or services.
100
- - High write throughput or many concurrent writers.
101
- - Complex queries, joins, or reporting workloads.
102
- - Multi-server or multi-region coordination.
103
- - Immediate read-after-write consistency in Google Sheets.
104
- - Google Sheets to be the primary database for the application.
105
-
106
- ## Is Hikoutei the right abstraction for you?
107
-
108
47
  Hikoutei does not replace `google-spreadsheet` or `@googleapis/sheets` — it
109
48
  sits one level above them. If you only need raw spreadsheet access, use the API
110
49
  client directly.
@@ -119,289 +58,83 @@ client directly.
119
58
  | Direct row and cell manipulation | Limited | ✅ | ✅ |
120
59
  | Full Google Sheets API access | Provider only | Partial | ✅ |
121
60
 
122
- ## Google Sheets setup
123
-
124
- Google Sheets synchronization is a service-side concern. Applications do not
125
- import a provider client, pass Sheet routes to `createTypedSheets()`, or choose
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.
134
-
135
- **Fastest path:** install the gcloud CLI, then run `npx hikoutei setup` from your
136
- project directory. On an interactive terminal it offers (press Enter) to
137
- start `gcloud auth login --enable-gdrive-access --force` for you when the
138
- active account is missing or lacks Drive access — you only complete the
139
- browser approval yourself. (In `--yes`, CI, or non-TTY sessions, run that
140
- login command yourself first.) Setup then creates the project, service
141
- account, and key, creates a spreadsheet owned by your account, shares it
142
- with the service account as an Editor, verifies service-account access, and
143
- writes `GOOGLE_APPLICATION_CREDENTIALS` plus `HIKOUTEI_SYNC_SPREADSHEET_URL`
144
- into your `.env`. The human access token is used in memory only and never
145
- stored. Automatic setup runs on macOS and Linux; on Windows a non-dry-run
146
- is refused before any mutation and manual setup is available. Interrupted runs resume from a local checkpoint
147
- (`.hikoutei-setup-state.json`); a spreadsheet create whose outcome is
148
- unknown is reconciled by its creation marker on the next run and setup
149
- never creates a second spreadsheet (inspect Drive and rerun if setup
150
- reports `sheet_create_uncertain`, and a create rejected up front with
151
- HTTP 400/403 plus a confirmed-zero marker lookup rolls back to `key_ready`
152
- so a corrected rerun starts a fresh marker). Sharing is write-ahead too:
153
- `spreadsheet_share_started` is persisted before the idempotent SA writer
154
- permission ensure and `spreadsheet_shared` after it, so a crash between
155
- the remote permission mutation and the checkpoint write resumes the
156
- ensure on the next run and never creates a second spreadsheet. The
157
- service-account key is
158
- created under a write-ahead contract too: the user-managed key list is
159
- recorded as a baseline before the single gcloud key create, and
160
- `key_create_started`/`key_ready` checkpoints let a crashed run recover a
161
- staged or installed key instead of creating a second one. Only the
162
- invocation that just persisted `key_create_started` may issue the one key
163
- create; resumed runs are reconcile-only and, when no credential and no
164
- post-baseline key are visible, poll the key list plus staged/final
165
- evidence for up to two minutes (2, 4, 8, 16, 30, 30, 30 s) before failing
166
- with `key_create_uncertain` — the create is never retried automatically.
167
- An unmatched user-managed key with no local credential is never deleted
168
- automatically — setup fails with `key_create_uncertain` and you inspect
169
- the key list in the Google Cloud console before rerunning (a
170
- verified-absent state requires removing the setup state file to reset the
171
- key checkpoint); reused keys are enforced to owner-only mode 600. An exclusive lock directory
172
- (`.hikoutei-setup-state.json.lock`) prevents concurrent runs and is never
173
- removed automatically: a crash leaves an empty lock directory behind, and
174
- removing it manually is required only when you are certain no setup is
175
- running. Starting fresh requires removing or moving both the checkpoint and
176
- the key file, or passing `--project` to recover an existing key —
177
- checkpointed or identity-matched cloud resources are reused, and setup
178
- never deletes cloud resources. The manual steps below remain available for
179
- advanced setups.
180
-
181
- **Setup progress.** `hikoutei setup` reports step-by-step progress to
182
- **stderr** across the ten setup phases (cloud auth, Drive access, project,
183
- APIs, service account, service-account key, spreadsheet, share,
184
- service-account access, output): an **overall bar** advances only when a
185
- phase actually completes — it is never an ETA and never guesses a
186
- percentage — and a **detail line** shows the bounded propagation checks
187
- (how many of the eight key/access checks have run) and the known 2, 4, 8,
188
- 16, 30, 30, 30 s waits, with a fixed `working…` label for unknown-duration
189
- steps. On an interactive terminal the four-line block redraws in place;
190
- in CI, non-TTY, or `NO_COLOR` sessions one static line is printed per
191
- phase/retry event with no control sequences. Progress pauses and the
192
- block is cleared during the interactive `gcloud auth login` handoff and
193
- resumes with the retry. Progress never prints credentials, tokens, keys,
194
- project ids, emails, paths, or raw command output, and it can never
195
- change the setup result or exit code; `--dry-run` prints the command
196
- plan only.
197
-
198
- **Keep setup artifacts out of Git.** `hikoutei setup` writes its defaults
199
- into the current directory: the service-account key
200
- (`hikoutei-service-account.json`, owner-only mode 600 — a secret), the
201
- resume checkpoint (`.hikoutei-setup-state.json` and its `.tmp`/unique-temp
202
- and `.lock` siblings), private key staging/cleanup directories
203
- (`.hikoutei-key-stage-*`, `.hikoutei-key-cleanup-*`), and
204
- `.hikoutei-env-*` temporary env writes. The repository's `.gitignore`
205
- already ignores these defaults, so a plain `git add .` does not pick them
206
- up. A `.gitignore` is **not a security boundary**, though: it only keeps
207
- untracked files out of `git add`, and it does not protect files that are
208
- already tracked (remove a mistakenly tracked key from history and rotate
209
- it — delete the user-managed key in the Cloud console and rerun setup).
210
- When you use a custom `--output` or keep the key or checkpoint at custom
211
- paths, add those exact paths to your application's ignore rules and never
212
- commit them.
213
-
214
- ### Env-driven sync auto-start
215
-
216
- Set the spreadsheet URL in the environment and `createTypedSheets()` starts
217
- the Sheets sync internally — `flush()` then flows to Google Sheets through
218
- the outbox worker with no per-call setup:
61
+ ## Installation
219
62
 
220
63
  ```sh
221
- HIKOUTEI_SYNC_SPREADSHEET_URL=https://docs.google.com/spreadsheets/d/<ID>/edit
222
- GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/service-account.json
223
- ```
224
-
225
- ```ts
226
- const hikoutei = await createTypedSheets({ dbName: "./hikoutei.sqlite", entities: [User] });
64
+ npm install hikoutei @mikro-orm/core @mikro-orm/sql
227
65
  ```
228
66
 
229
- Without `HIKOUTEI_SYNC_SPREADSHEET_URL`, `createTypedSheets()` stays
230
- local-only (SQLite). Startup failures are diagnosed with clear messages:
231
- invalid URL, missing/invalid credentials file, or a service account not
232
- shared on the spreadsheet (the error tells you which email to share).
233
-
234
- ### Manual service-account setup
235
-
236
- 1. **Create a service account.** Enable the Google Sheets API in a Cloud
237
- project, create a service account with the
238
- `https://www.googleapis.com/auth/spreadsheets` scope, and share the target
239
- spreadsheet with its email as an **Editor**. The provider creates tabs,
240
- writes effect rows and receipt records, and manages row anchors, so Viewer
241
- access is not enough.
242
- 2. **Keep the key server-side.** Put the service-account key path in
243
- `GOOGLE_APPLICATION_CREDENTIALS` on the server and the spreadsheet ID in an
244
- untracked secret store. Never put the key in browser code or Git: add the
245
- key path (and any custom `.env`/checkpoint paths) to the application's
246
- `.gitignore` — the defaults created by `hikoutei setup` are already
247
- ignored, but a `.gitignore` is not a security boundary and does not
248
- protect already-tracked files.
249
- 3. **Run the application normally.** Start the app with
250
- `GOOGLE_APPLICATION_CREDENTIALS` and `HIKOUTEI_SYNC_SPREADSHEET_URL` set;
251
- `createTypedSheets()` detects them and starts the internal sync bootstrap —
252
- it creates and verifies headers on the registered tabs, then starts outbox
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.
255
-
256
- > **Legacy spreadsheet note.** Spreadsheets provisioned by the old Apps Script
257
- > provider with developer-metadata row anchors are not migrated: `User_Input`
258
- > tabs now require the `__hikoutei_row_id` system column, so legacy tabs must
259
- > be re-provisioned. Current provisioning also adds one internal formula
260
- > column, `__hikoutei_row_check`, directly after each `User_Input` tab's
261
- > registered range: Hikoutei writes a change-detection formula per row, and
262
- > polling reads only that column to spot human edits (rows without it fall
263
- > back to full reads). Do not edit, sort, or delete the column or its
264
- > formulas.
265
-
266
- Hikoutei uses a durable local outbox, idempotent delivery, and conflict-aware
267
- updates so temporary API failures do not lose committed application writes. The
268
- provider never logs credentials, spreadsheet IDs, URLs, or payloads, and it
269
- spaces request starts to stay inside Google's quota windows. See the
270
- [internal consistency model](website/guide/internal-consistency.md) for the
271
- detailed state machine and recovery rules.
272
-
273
- Live Google calls are opt-in; fake providers and SQLite fixtures are the normal
274
- verification path. The detailed setup and troubleshooting steps are in the
275
- [Quick start](website/guide/quick-start.md) and [Google Sheets setup](website/guide/setup.md) —
276
- including the credential pool (`--sa-count`) and API quota guidance when
277
- polling-heavy workloads hit HTTP 429.
67
+ Installing the library does not touch Google Cloud — it runs local-only
68
+ (SQLite) by default. Run the setup command below only when you want Sheet
69
+ sync.
278
70
 
279
- ## Installation
71
+ ## Setup (Google Sheets sync)
280
72
 
281
- The project and npm package are both called `hikoutei`. This repo uses pnpm
282
- (packageManager `pnpm@11.1.2`):
73
+ One-time, interactive. Install the gcloud CLI, then:
283
74
 
284
75
  ```sh
285
- pnpm install --frozen-lockfile
76
+ npx hikoutei setup
286
77
  ```
287
78
 
288
- `@mikro-orm/core` and `@mikro-orm/sql` are optional peers (`optional: true`),
289
- lazy-loaded only when a runtime opens. `npm install` still works for consumers.
290
-
291
- ## Documentation
292
-
293
- - [Quick start](website/guide/quick-start.md) — installation, ORM lifecycle,
294
- and service-side sync setup.
295
- - [Architecture](website/guide/architecture.md) — how the local store and
296
- Sheet views fit together.
297
- - [Write and synchronization flow](website/guide/sync-flow.md) —
298
- asynchronous delivery and recovery behavior.
299
- - [Internal consistency model](website/guide/internal-consistency.md) —
300
- durable outbox, idempotent delivery, and conflict-aware updates.
301
- - [Contributing](website/guide/contributing.md) — local development and test
302
- commands.
303
- - [Soak testing](website/guide/soak-testing.md) — the long-duration soak
304
- runner: source build, six scalar tables, 6h preflight and 24h direct-live
305
- runs, log envs, redaction contract, resume/cleanup, and acceptance
306
- criteria.
307
- - [Benchmark notes](website/guide/benchmarks.md) — dated measurements and
308
- their limitations.
309
-
310
- ## Limitations
311
-
312
- - Google Sheets has quota, latency, and API rate limits. The outbound sync
313
- worker batches effects to the spreadsheet scope, so remote request volume
314
- does not grow with the number of tabs a dispatch touches.
315
- - Sheet updates are asynchronous; the application should read its local state.
316
- - SQLite is local to the service and is not a distributed coordination layer.
317
- - Schema changes, manual edits, and conflicting updates still need an
318
- operational policy from the application.
319
- - The EntityManager is an ORM-style facade over scalar entities, not a full
320
- ORM: entity definitions are scalar-only (`string`, `number`, `boolean`,
321
- `date`), and v1 permits uniqueness only on the primary/business key.
322
- - Relations, joins, `populate()`, migrations, cascades, bulk/ORM query
323
- builders, and raw SQL are unsupported in this milestone. Sheets is an async
324
- projection and human input surface, never a live query database.
325
-
326
- ## Local queries
327
-
328
- Reads use Hikoutei-owned typed operators and always execute against SQLite:
79
+ This creates the Cloud project, service account, key, and spreadsheet, and
80
+ writes `.env` for you. Without `HIKOUTEI_SYNC_SPREADSHEET_URL`,
81
+ `createTypedSheets()` stays local-only (SQLite). Details, credential pools,
82
+ quota guidance, and manual setup: [Google Sheets setup](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/setup.md).
83
+
84
+ ## Usage
85
+
86
+ Define a scalar entity and use the local SQLite authority through a
87
+ request-local manager.
329
88
 
330
89
  ```ts
331
- const [users, total] = await em.findAndCount(
332
- User,
333
- {
334
- name: { like: "Ada%" },
335
- age: { gte: 18, lt: 65 },
336
- active: { in: [true] },
337
- },
338
- {
339
- orderBy: { age: "desc", name: "asc" },
340
- limit: 20,
341
- offset: 0,
90
+ import { createTypedSheets, defineTypedSheetsEntity } from "hikoutei";
91
+
92
+ const User = defineTypedSheetsEntity({
93
+ name: "User",
94
+ tableName: "users",
95
+ properties: {
96
+ id: { type: "string", primary: true },
97
+ name: { type: "string" },
98
+ age: { type: "number" },
99
+ active: { type: "boolean" },
342
100
  },
343
- );
101
+ });
102
+
103
+ const hikoutei = await createTypedSheets({
104
+ dbName: "./hikoutei.sqlite",
105
+ entities: [User],
106
+ });
107
+
108
+ const em = hikoutei.em.fork();
109
+ const user = em.create(User, { id: "u1", name: "Ada", age: 36, active: true });
110
+ em.persist(user);
111
+ await em.flush();
112
+
113
+ user.name = "Ada Lovelace";
114
+ await em.flush();
115
+
116
+ const loaded = await em.findOne(User, { id: "u1" });
117
+ if (loaded !== null) {
118
+ em.remove(loaded);
119
+ await em.flush();
120
+ }
344
121
  ```
345
122
 
346
- `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, and `nin` are available where
347
- valid for the declared scalar type; `like` is string-only. Equality shorthand
348
- such as `{ active: true }` remains supported. `count()` returns the unpaged
349
- filter total, and `findAndCount()` returns the filtered page plus a total
350
- that ignores `limit`/`offset` — both read from one SQLite snapshot. When an
351
- explicit `orderBy` omits the primary key, Hikoutei appends it in ascending
352
- order as the final tie-breaker; when the primary key is explicitly ordered,
353
- its supplied position and direction are preserved. Pagination without
354
- `orderBy` uses primary-key ascending order.
355
-
356
- An `offset` alone (no `limit`) is a valid offset-only read, and an explicit
357
- `limit: 0` returns an empty page rather than being treated as "no limit".
358
- `findOne()` returns one entity or `null` and accepts ordering but no paging
359
- options. Malformed filters, operators, ordering, and paging options fail with
360
- a stable `HikouteiError`; branch on `error.code` through the exported
361
- `HIKOUTEI_ERROR_CODES` constants (`HIKOUTEI_ERROR_CODES.INVALID_QUERY` and
362
- `HIKOUTEI_ERROR_CODES.INVALID_SCALAR_VALUE`, which resolve to the lowercase
363
- runtime codes `invalid_query` and `invalid_scalar_value`) instead of guessing
364
- strings or parsing messages.
365
-
366
- ## Project status
367
-
368
- Hikoutei is in active development. The current EntityManager supports scalar
369
- entity lifecycle operations, typed local filters and ordering, `limit` /
370
- `offset` pagination, `count()`, snapshot-consistent `findAndCount()`, and
371
- callback-style `transactional()` work. Normal reads always come from SQLite,
372
- never Google Sheets. Sheet edit ingestion and conflict presentation are still
373
- evolving. Review release notes before upgrading minor versions.
374
-
375
- ## Roadmap
376
-
377
- The first EntityManager milestone, rich local reads, is complete. Remaining
378
- milestones follow the implementation order below; no milestone is tied to a
379
- date or release number.
380
-
381
- 1. **Lifecycle-safe writes**
382
- - Add `upsert` and direct/bulk mutation capabilities only through a
383
- Hikoutei-owned contract that preserves one SQLite transaction across the
384
- entity table, canonical state, and durable Sheet effect outbox.
385
- - Do not promise raw `nativeInsert`, `nativeUpdate`, `nativeDelete`, or SQL
386
- pass-through APIs that could bypass that atomic lifecycle.
387
- 2. **Relationships and loading**
388
- - Add many-to-one, one-to-many, and `populate()` capabilities.
389
- - Design SQLite relationship mapping, Sheets projection representation,
390
- schema behavior, and conflict semantics together before public release.
391
- 3. **Schema operations**
392
- - Add migration and schema drift management.
393
- - Integrate validation and operational workflows with the existing setup
394
- tooling.
395
-
396
- ### Synchronization and operations
397
-
398
- The following work continues in parallel with the EntityManager milestones:
399
-
400
- - Complete ingestion of intentional user edits from Google Sheets.
401
- - Improve update/delete conflict handling and presentation.
402
-
403
- See the [open issues](https://github.com/ManddarinShop/Hikoutei/issues)
404
- for current work.
123
+ More reads, transactions, and operators: [Quick start](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/quick-start.md).
124
+
125
+ Writes commit to local SQLite immediately — the request never waits on Google.
126
+ Human edits in the Sheet flow back through polling — accepted into SQLite or
127
+ recorded as conflicts, never silently overwritten. The full pipeline (outbox,
128
+ delivery, conflict handling) is covered in
129
+ [Write and synchronization flow](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/sync-flow.md).
130
+
131
+ ## Learn more
132
+
133
+ - [Quick start](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/quick-start.md) — installation, ORM lifecycle, sync setup.
134
+ - [Architecture](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/architecture.md) — local store and Sheet views.
135
+ - [Write and synchronization flow](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/sync-flow.md) — delivery and recovery.
136
+ - [Limitations](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/limitations.md) — when to choose something else.
137
+ - [Project status and roadmap](https://github.com/ManddarinShop/Hikoutei-Website-/blob/main/guide/status.md) — what is done and next.
405
138
 
406
139
  ## License
407
140
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hikoutei",
3
- "version": "0.10.18-dev",
3
+ "version": "0.10.20-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",