hikoutei 0.5.22 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.ja.md +37 -18
  2. package/README.ko.md +36 -18
  3. package/README.md +73 -31
  4. package/dist/adapter/persistence/contracts/scalar.d.ts +62 -12
  5. package/dist/adapter/persistence/contracts/scalar.d.ts.map +1 -1
  6. package/dist/adapter/persistence/providers/mikro-orm/api/MikroOrmScalarPersistenceProvider.d.ts +5 -1
  7. package/dist/adapter/persistence/providers/mikro-orm/api/MikroOrmScalarPersistenceProvider.d.ts.map +1 -1
  8. package/dist/adapter/persistence/providers/mikro-orm/api/MikroOrmScalarPersistenceProvider.js +83 -7
  9. package/dist/adapter/persistence/providers/mikro-orm/api/MikroOrmScalarPersistenceProvider.js.map +1 -1
  10. package/dist/api/EntityManager.d.ts +6 -7
  11. package/dist/api/EntityManager.d.ts.map +1 -1
  12. package/dist/api/errors.d.ts +2 -0
  13. package/dist/api/errors.d.ts.map +1 -1
  14. package/dist/api/errors.js +2 -0
  15. package/dist/api/errors.js.map +1 -1
  16. package/dist/api/index.d.ts +1 -1
  17. package/dist/api/index.d.ts.map +1 -1
  18. package/dist/api/index.js.map +1 -1
  19. package/dist/api/internalEntityManager.d.ts.map +1 -1
  20. package/dist/api/internalEntityManager.js +54 -65
  21. package/dist/api/internalEntityManager.js.map +1 -1
  22. package/dist/api/query.d.ts +62 -0
  23. package/dist/api/query.d.ts.map +1 -0
  24. package/dist/api/query.js +18 -0
  25. package/dist/api/query.js.map +1 -0
  26. package/dist/api/queryNormalization.d.ts +7 -0
  27. package/dist/api/queryNormalization.d.ts.map +1 -0
  28. package/dist/api/queryNormalization.js +262 -0
  29. package/dist/api/queryNormalization.js.map +1 -0
  30. package/dist/internal/syncStatus.d.ts +111 -0
  31. package/dist/internal/syncStatus.d.ts.map +1 -0
  32. package/dist/internal/syncStatus.js +265 -0
  33. package/dist/internal/syncStatus.js.map +1 -0
  34. package/package.json +5 -1
package/README.ja.md CHANGED
@@ -215,41 +215,60 @@ MikroORM は実装の詳細であり、Hikoutei の公開エンティティ API
215
215
  - スキーマ変更・手動編集・競合更新には、依然としてアプリケーションの運用
216
216
  ポリシーが必要です。
217
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
+ ```
237
+
238
+ `eq`、`ne`、`gt`、`gte`、`lt`、`lte`、`in`、`nin` は、宣言された
239
+ スカラー型で有効な範囲で利用でき、`like` は文字列専用です。
240
+ `{ active: true }` のような等価条件の省略記法も引き続き利用できます。
241
+ `count()` はページネーション前のフィルター総数を返し、`findAndCount()` は
242
+ 1 つの SQLite スナップショットからページと総数を読み取ります。明示的な
243
+ 並び順には最後のタイブレーカーとして主キーが追加され、`orderBy` のない
244
+ ページネーションは主キーの昇順を使用します。
245
+
218
246
  ## プロジェクトステータス
219
247
 
220
248
  Hikoutei は活発に開発中です。現在の EntityManager は、スカラーエンティティの
221
- ライフサイクル操作、等価条件を使う `find()` / `findOne()`、`find()`
222
- `limit` / `offset` ページネーション、コールバック形式の `transactional()` を
223
- サポートします。通常の読み取り元は常に SQLite であり、Google Sheets では
224
- ありません。
225
- `hikoutei setup` CLI がスプレッドシートとサービスアカウントをプロビジョニ
226
- ングし、direct Google Sheets API provider(環境変数による自動開始)が唯一の
227
- 同期経路です — Apps Script ゲートウェイはありません。シート編集の取り込みと
249
+ ライフサイクル操作、型付きローカルフィルターと並び順、`limit` / `offset`
250
+ ページネーション、`count()`、スナップショット整合性のある `findAndCount()`、
251
+ コールバック形式の `transactional()` をサポートします。通常の読み取り元は
252
+ 常に SQLite であり、Google Sheets ではありません。シート編集の取り込みと
228
253
  競合表示はまだ発展途上です。マイナーバージョンのアップグレード前にリリース
229
254
  ノートを確認してください。
230
255
 
231
256
  ## ロードマップ
232
257
 
233
- EntityManager のロードマップは、以下の実装順序に従います。段階の順序は確定
234
- していますが、日付やリリース番号は約束しません。
258
+ 最初の EntityManager 段階である豊富なローカル読み取りは完了しました。残る段階は
259
+ 以下の実装順序に従い、日付やリリース番号は約束しません。
235
260
 
236
- 1. **豊富なローカル読み取り**
237
- - Hikoutei 独自の型付きクエリ契約に、明示的な `eq`、`ne`、`gt`、
238
- `gte`、`lt`、`lte`、`in`、`nin`、`like` 条件と `orderBy`、
239
- `count()`、`findAndCount()` を追加します。
240
- - MikroORM のクエリ型を公開せず、これらを既存の `limit` / `offset`
241
- ページネーションと組み合わせます。
242
- 2. **ライフサイクル安全な書き込み**
261
+ 1. **ライフサイクル安全な書き込み**
243
262
  - `upsert` と direct/bulk mutation 機能は、エンティティテーブル、
244
263
  canonical state、永続的な Sheet effect outbox を 1 つの SQLite
245
264
  トランザクションで処理する Hikoutei 独自の契約を通じてのみ追加します。
246
265
  - この原子的なライフサイクルを迂回し得る、生の `nativeInsert`、
247
266
  `nativeUpdate`、`nativeDelete`、または SQL パススルー API は約束しません。
248
- 3. **リレーションとロード**
267
+ 2. **リレーションとロード**
249
268
  - many-to-one、one-to-many、`populate()` 機能を追加します。
250
269
  - 公開前に、リレーションの SQLite マッピング、Sheets プロジェクション
251
270
  表現、スキーマ動作、競合セマンティクスを一体として設計します。
252
- 4. **スキーマ運用**
271
+ 3. **スキーマ運用**
253
272
  - マイグレーションとスキーマドリフト管理を追加します。
254
273
  - 検証と運用フローを既存のセットアップツールと統合します。
255
274
 
package/README.ko.md CHANGED
@@ -208,39 +208,57 @@ MikroORM은 구현 세부 사항이며 Hikoutei의 공개 엔티티 API에는
208
208
  - 스키마 변경, 수동 편집, 충돌 업데이트에는 여전히 애플리케이션의 운영 정책이
209
209
  필요합니다.
210
210
 
211
+ ## 로컬 쿼리
212
+
213
+ 읽기는 Hikoutei가 정의한 타입 연산자를 사용하며 항상 SQLite에서 실행됩니다.
214
+
215
+ ```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 오름차순을 사용합니다.
237
+
211
238
  ## 프로젝트 상태
212
239
 
213
- Hikoutei는 활발히 개발 중입니다. 현재 EntityManager는 스칼라 엔티티의 생명주기
214
- 작업, 동등 조건을 사용하는 `find()` / `findOne()`, `find()`의 `limit` /
215
- `offset` 페이지네이션, 콜백형 `transactional()`을 지원합니다. 일반 읽기는
216
- Google Sheets가 아니라 항상 SQLite에서 수행됩니다. `hikoutei setup` CLI가
217
- 스프레드시트와 서비스 계정을 프로비저닝하며, direct Google Sheets API
218
- provider(환경 변수 자동 시작)가 유일한 sync 경로입니다 — Apps Script
219
- gateway가 없습니다. 시트 편집 수집과 충돌 표시는
240
+ Hikoutei는 활발히 개발 중입니다. 현재 EntityManager는 스칼라 엔티티 생명주기,
241
+ 타입 로컬 필터와 정렬, `limit` / `offset` 페이지네이션, `count()`, 스냅샷이
242
+ 일관된 `findAndCount()`, 콜백형 `transactional()`을 지원합니다. 일반 읽기는
243
+ Google Sheets가 아니라 항상 SQLite에서 수행됩니다. 시트 편집 수집과 충돌 표시는
220
244
  아직 발전 중입니다. 마이너 버전 업그레이드 전에 릴리스 노트를 확인하세요.
221
245
 
222
246
  ## 로드맵
223
247
 
224
- EntityManager 로드맵은 아래 구현 순서를 따릅니다. 단계 순서는 확정되어 있지만,
225
- 일정이나 릴리스 번호는 약속하지 않습니다.
248
+ EntityManager 단계인 풍부한 로컬 읽기는 완료됐습니다. 남은 단계는 아래 구현
249
+ 순서를 따르며, 일정이나 릴리스 번호는 약속하지 않습니다.
226
250
 
227
- 1. **풍부한 로컬 읽기**
228
- - Hikoutei가 정의한 타입 쿼리 계약에 명시적 `eq`, `ne`, `gt`, `gte`,
229
- `lt`, `lte`, `in`, `nin`, `like` 조건과 `orderBy`, `count()`,
230
- `findAndCount()`를 추가합니다.
231
- - MikroORM 쿼리 타입을 노출하지 않고 이 기능들을 기존 `limit` / `offset`
232
- 페이지네이션과 조합합니다.
233
- 2. **생명주기 안전 쓰기**
251
+ 1. **생명주기 안전 쓰기**
234
252
  - `upsert`와 direct/bulk mutation 기능은 엔티티 테이블, canonical state,
235
253
  내구성 있는 Sheet effect outbox를 하나의 SQLite 트랜잭션에서 처리하는
236
254
  Hikoutei 정의 계약을 통해서만 추가합니다.
237
255
  - 이 원자적 생명주기를 우회할 수 있는 원시 `nativeInsert`, `nativeUpdate`,
238
256
  `nativeDelete` 또는 SQL 패스스루 API는 약속하지 않습니다.
239
- 3. **관계와 로딩**
257
+ 2. **관계와 로딩**
240
258
  - many-to-one, one-to-many, `populate()` 기능을 추가합니다.
241
259
  - 공개 전에 관계의 SQLite 매핑, Sheets 프로젝션 표현, 스키마 동작, 충돌
242
260
  의미론을 함께 설계합니다.
243
- 4. **스키마 운영**
261
+ 3. **스키마 운영**
244
262
  - 마이그레이션과 스키마 드리프트 관리를 추가합니다.
245
263
  - 검증 및 운영 흐름을 기존 설정 도구와 통합합니다.
246
264
 
package/README.md CHANGED
@@ -12,7 +12,7 @@ changes are asynchronously projected to Google Sheets for human review and
12
12
  lightweight collaboration.
13
13
 
14
14
  <a href="https://www.npmjs.com/package/hikoutei">npm</a> ·
15
- <a href="docs/quick-start.md">Quick start</a> ·
15
+ <a href="website/guide/quick-start.md">Quick start</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)
@@ -48,6 +48,8 @@ const User = defineTypedSheetsEntity({
48
48
  properties: {
49
49
  id: { type: "string", primary: true },
50
50
  name: { type: "string" },
51
+ age: { type: "number" },
52
+ active: { type: "boolean" },
51
53
  },
52
54
  });
53
55
 
@@ -57,7 +59,7 @@ const hikoutei = await createTypedSheets({
57
59
  });
58
60
 
59
61
  const em = hikoutei.em.fork();
60
- const user = em.create(User, { id: "u1", name: "Ada" });
62
+ const user = em.create(User, { id: "u1", name: "Ada", age: 36, active: true });
61
63
  em.persist(user);
62
64
  await em.flush();
63
65
 
@@ -257,12 +259,12 @@ Hikoutei uses a durable local outbox, idempotent delivery, and conflict-aware
257
259
  updates so temporary API failures do not lose committed application writes. The
258
260
  provider never logs credentials, spreadsheet IDs, URLs, or payloads, and it
259
261
  spaces request starts to stay inside Google's quota windows. See the
260
- [internal consistency model](docs/internal-consistency-model.md) for the
262
+ [internal consistency model](website/guide/internal-consistency.md) for the
261
263
  detailed state machine and recovery rules.
262
264
 
263
265
  Live Google calls are opt-in; fake providers and SQLite fixtures are the normal
264
266
  verification path. The detailed setup and troubleshooting steps are in the
265
- [Quick start](docs/quick-start.md).
267
+ [Quick start](website/guide/quick-start.md).
266
268
 
267
269
  ## Installation
268
270
 
@@ -278,16 +280,18 @@ entity API.
278
280
 
279
281
  ## Documentation
280
282
 
281
- - [Quick start](docs/quick-start.md) — installation, ORM lifecycle, and service-side sync setup.
282
- - [Architecture](docs/architecture.md) — how the local store and Sheet views
283
- fit together.
284
- - [Write and synchronization flow](docs/write-and-synchronization-flow.md) —
283
+ - [Quick start](website/guide/quick-start.md) — installation, ORM lifecycle,
284
+ and service-side sync setup.
285
+ - [Architecture](website/guide/architecture.md) — how the local store and
286
+ Sheet views fit together.
287
+ - [Write and synchronization flow](website/guide/sync-flow.md) —
285
288
  asynchronous delivery and recovery behavior.
286
- - [Internal consistency model](docs/internal-consistency-model.md) — durable
287
- outbox, idempotent delivery, and conflict-aware updates.
288
- - [Development](docs/development.md) — local development and test commands.
289
- - [Benchmark notes](docs/sync-bulk-write-benchmark.md) — dated measurements
290
- and their limitations.
289
+ - [Internal consistency model](website/guide/internal-consistency.md) —
290
+ durable outbox, idempotent delivery, and conflict-aware updates.
291
+ - [Contributing](website/guide/contributing.md) — local development and test
292
+ commands.
293
+ - [Benchmark notes](website/guide/benchmarks.md) — dated measurements and
294
+ their limitations.
291
295
 
292
296
  ## Limitations
293
297
 
@@ -296,41 +300,79 @@ entity API.
296
300
  - SQLite is local to the service and is not a distributed coordination layer.
297
301
  - Schema changes, manual edits, and conflicting updates still need an
298
302
  operational policy from the application.
303
+ - The EntityManager is an ORM-style facade over scalar entities, not a full
304
+ ORM: entity definitions are scalar-only (`string`, `number`, `boolean`,
305
+ `date`), and v1 permits uniqueness only on the primary/business key.
306
+ - Relations, joins, `populate()`, migrations, cascades, bulk/ORM query
307
+ builders, and raw SQL are unsupported in this milestone. Sheets is an async
308
+ projection and human input surface, never a live query database.
309
+
310
+ ## Local queries
311
+
312
+ Reads use Hikoutei-owned typed operators and always execute against SQLite:
313
+
314
+ ```ts
315
+ const [users, total] = await em.findAndCount(
316
+ User,
317
+ {
318
+ name: { like: "Ada%" },
319
+ age: { gte: 18, lt: 65 },
320
+ active: { in: [true] },
321
+ },
322
+ {
323
+ orderBy: { age: "desc", name: "asc" },
324
+ limit: 20,
325
+ offset: 0,
326
+ },
327
+ );
328
+ ```
329
+
330
+ `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, and `nin` are available where
331
+ valid for the declared scalar type; `like` is string-only. Equality shorthand
332
+ such as `{ active: true }` remains supported. `count()` returns the unpaged
333
+ filter total, and `findAndCount()` returns the filtered page plus a total
334
+ that ignores `limit`/`offset` — both read from one SQLite snapshot. When an
335
+ explicit `orderBy` omits the primary key, Hikoutei appends it in ascending
336
+ order as the final tie-breaker; when the primary key is explicitly ordered,
337
+ its supplied position and direction are preserved. Pagination without
338
+ `orderBy` uses primary-key ascending order.
339
+
340
+ An `offset` alone (no `limit`) is a valid offset-only read, and an explicit
341
+ `limit: 0` returns an empty page rather than being treated as "no limit".
342
+ `findOne()` returns one entity or `null` and accepts ordering but no paging
343
+ options. Malformed filters, operators, ordering, and paging options fail with
344
+ a stable `HikouteiError`; branch on `error.code` through the exported
345
+ `HIKOUTEI_ERROR_CODES` constants (`HIKOUTEI_ERROR_CODES.INVALID_QUERY` and
346
+ `HIKOUTEI_ERROR_CODES.INVALID_SCALAR_VALUE`, which resolve to the lowercase
347
+ runtime codes `invalid_query` and `invalid_scalar_value`) instead of guessing
348
+ strings or parsing messages.
299
349
 
300
350
  ## Project status
301
351
 
302
352
  Hikoutei is in active development. The current EntityManager supports scalar
303
- entity lifecycle operations, equality-filtered `find()` / `findOne()`,
304
- `limit` / `offset` pagination on `find()`, and callback-style
305
- `transactional()` work.
306
- Normal reads always come from SQLite, never Google Sheets. The `hikoutei
307
- setup` CLI provisions the spreadsheet and service account, and the direct
308
- Google Sheets API provider (env-driven auto-start) is the only sync path — no
309
- Apps Script gateway. Sheet edit ingestion and conflict presentation are still
353
+ entity lifecycle operations, typed local filters and ordering, `limit` /
354
+ `offset` pagination, `count()`, snapshot-consistent `findAndCount()`, and
355
+ callback-style `transactional()` work. Normal reads always come from SQLite,
356
+ never Google Sheets. Sheet edit ingestion and conflict presentation are still
310
357
  evolving. Review release notes before upgrading minor versions.
311
358
 
312
359
  ## Roadmap
313
360
 
314
- The EntityManager roadmap follows the implementation order below. The sequence
315
- is committed, but no milestone is tied to a date or release number.
361
+ The first EntityManager milestone, rich local reads, is complete. Remaining
362
+ milestones follow the implementation order below; no milestone is tied to a
363
+ date or release number.
316
364
 
317
- 1. **Rich local reads**
318
- - Add a Hikoutei-owned typed query contract for explicit `eq`, `ne`, `gt`,
319
- `gte`, `lt`, `lte`, `in`, `nin`, and `like` conditions, plus `orderBy`,
320
- `count()`, and `findAndCount()`.
321
- - Compose these capabilities with the existing `limit` / `offset`
322
- pagination without exposing MikroORM query types.
323
- 2. **Lifecycle-safe writes**
365
+ 1. **Lifecycle-safe writes**
324
366
  - Add `upsert` and direct/bulk mutation capabilities only through a
325
367
  Hikoutei-owned contract that preserves one SQLite transaction across the
326
368
  entity table, canonical state, and durable Sheet effect outbox.
327
369
  - Do not promise raw `nativeInsert`, `nativeUpdate`, `nativeDelete`, or SQL
328
370
  pass-through APIs that could bypass that atomic lifecycle.
329
- 3. **Relationships and loading**
371
+ 2. **Relationships and loading**
330
372
  - Add many-to-one, one-to-many, and `populate()` capabilities.
331
373
  - Design SQLite relationship mapping, Sheets projection representation,
332
374
  schema behavior, and conflict semantics together before public release.
333
- 4. **Schema operations**
375
+ 3. **Schema operations**
334
376
  - Add migration and schema drift management.
335
377
  - Integrate validation and operational workflows with the existing setup
336
378
  tooling.
@@ -84,27 +84,79 @@ export interface ScalarEntityFlushContext {
84
84
  export interface ScalarEntityFlushCoordinator {
85
85
  onFlush(context: ScalarEntityFlushContext): Promise<void>;
86
86
  }
87
- /** Equality filter and paging for a scalar entity read. */
88
- export interface ScalarEntityQuery {
87
+ /** Non-null scalar comparison represented independently from any ORM syntax. */
88
+ export interface ScalarEntityComparisonPredicate {
89
+ readonly kind: "comparison";
90
+ readonly field: string;
91
+ readonly operator: "eq" | "ne" | "gt" | "gte" | "lt" | "lte";
92
+ readonly value: Exclude<ScalarEntityValue, null>;
93
+ }
94
+ /** Non-empty set membership predicate after public-array validation. */
95
+ export interface ScalarEntitySetPredicate {
96
+ readonly kind: "set";
97
+ readonly field: string;
98
+ readonly operator: "in" | "nin";
99
+ readonly values: readonly Exclude<ScalarEntityValue, null>[];
100
+ }
101
+ /** SQLite LIKE predicate over one validated string field and pattern. */
102
+ export interface ScalarEntityLikePredicate {
103
+ readonly kind: "like";
104
+ readonly field: string;
105
+ readonly pattern: string;
106
+ }
107
+ /** Explicit nullable-field predicate that avoids SQL three-valued ambiguity. */
108
+ export interface ScalarEntityNullPredicate {
109
+ readonly kind: "null";
110
+ readonly field: string;
111
+ readonly operator: "is_null" | "is_not_null";
112
+ }
113
+ /** Query constant produced by empty filters and empty membership sets. */
114
+ export interface ScalarEntityConstantPredicate {
115
+ readonly kind: "constant";
116
+ readonly value: boolean;
117
+ }
118
+ /** Internal logical group used only after public field filters are validated. */
119
+ export interface ScalarEntityPredicateGroup {
120
+ readonly kind: "all" | "any";
121
+ readonly predicates: readonly ScalarEntityPredicate[];
122
+ }
123
+ /** Provider-neutral query predicate promoted from the public filter boundary. */
124
+ export type ScalarEntityPredicate = ScalarEntityComparisonPredicate | ScalarEntitySetPredicate | ScalarEntityLikePredicate | ScalarEntityNullPredicate | ScalarEntityConstantPredicate | ScalarEntityPredicateGroup;
125
+ /** One normalized sort key in deterministic precedence order. */
126
+ export interface ScalarEntityOrder {
127
+ readonly field: string;
128
+ readonly direction: "asc" | "desc";
129
+ }
130
+ /** Validated filter shared by collection reads and count queries. */
131
+ export interface ScalarEntityCountQuery {
89
132
  readonly tableName: string;
90
133
  readonly primaryKeyColumn: string;
91
- /** Equality filter on one or more declared columns. */
92
- readonly where: Readonly<Record<string, ScalarEntityValue>>;
134
+ readonly predicate: ScalarEntityPredicate;
135
+ }
136
+ /** Validated filter, ordering, and paging for one scalar entity read. */
137
+ export interface ScalarEntityQuery extends ScalarEntityCountQuery {
138
+ readonly orderBy: readonly ScalarEntityOrder[];
93
139
  readonly limit?: number;
94
140
  readonly offset?: number;
95
141
  }
142
+ /** One scalar entity row returned by a read. */
143
+ export type ScalarEntityRow = Readonly<Record<string, ScalarEntityValue>>;
144
+ /** Read operations available both outside and inside provider transactions. */
145
+ export interface ScalarEntityReader {
146
+ read(query: ScalarEntityQuery): Promise<readonly ScalarEntityRow[]>;
147
+ /** Returns the unpaged number of rows matching the query predicate. */
148
+ count(query: ScalarEntityCountQuery): Promise<number>;
149
+ }
96
150
  /**
97
151
  * Write surface available inside one atomic transaction.
98
152
  *
99
153
  * Inserts, updates, and deletes run against the active transaction so a single
100
154
  * `flush()` either commits every entity row or rolls them all back.
101
155
  */
102
- export interface ScalarEntityTransaction {
156
+ export interface ScalarEntityTransaction extends ScalarEntityReader {
103
157
  insert(row: ScalarEntityInsert): Promise<void>;
104
158
  update(row: ScalarEntityUpdate): Promise<void>;
105
159
  delete(row: ScalarEntityDelete): Promise<void>;
106
- /** Reads rows through the active transaction's view of the table. */
107
- read(query: ScalarEntityQuery): Promise<readonly ScalarEntityRow[]>;
108
160
  /**
109
161
  * Flushes provider-scheduled entity, canonical-state, and outbox work before
110
162
  * the common UoW advances snapshots. The enclosing transaction must still be
@@ -112,8 +164,6 @@ export interface ScalarEntityTransaction {
112
164
  */
113
165
  flush(): Promise<void>;
114
166
  }
115
- /** One scalar entity row returned by a read. */
116
- export type ScalarEntityRow = Readonly<Record<string, ScalarEntityValue>>;
117
167
  /**
118
168
  * Replaceable local persistence engine behind the public EntityManager.
119
169
  *
@@ -122,11 +172,11 @@ export type ScalarEntityRow = Readonly<Record<string, ScalarEntityValue>>;
122
172
  * callback within one SQLite transaction and roll the whole callback back when
123
173
  * it rejects.
124
174
  */
125
- export interface ScalarEntityPersistenceProvider {
175
+ export interface ScalarEntityPersistenceProvider extends ScalarEntityReader {
126
176
  /** Runs one atomic transaction and rolls back every write on rejection. */
127
177
  beginTransaction<Result>(work: (transaction: ScalarEntityTransaction) => Promise<Result>): Promise<Result>;
128
- /** Reads rows outside any transaction using a fresh persistence context. */
129
- read(query: ScalarEntityQuery): Promise<readonly ScalarEntityRow[]>;
178
+ /** Runs read operations against one consistent persistence snapshot. */
179
+ readSnapshot<Result>(work: (reader: ScalarEntityReader) => Promise<Result>): Promise<Result>;
130
180
  /** Releases resources owned by the underlying SQLite connection. */
131
181
  close(): Promise<void>;
132
182
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scalar.d.ts","sourceRoot":"","sources":["../../../../src/adapter/persistence/contracts/scalar.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scalar.d.ts","sourceRoot":"","sources":["../../../../src/adapter/persistence/contracts/scalar.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  * entity statements in the same SQLite transaction.
8
8
  */
9
9
  import type { ResolvedHikouteiEntityDescriptor } from "../../../../../api/entity.js";
10
- import type { ScalarEntityFlushCoordinator, ScalarEntityPersistenceProvider, ScalarEntityQuery, ScalarEntityRow, ScalarEntityTransaction } from "../../../contracts/scalar.js";
10
+ import type { ScalarEntityCountQuery, ScalarEntityFlushCoordinator, ScalarEntityPersistenceProvider, ScalarEntityQuery, ScalarEntityReader, ScalarEntityRow, ScalarEntityTransaction } from "../../../contracts/scalar.js";
11
11
  import type { MikroOrmSqliteAdapter } from "../storage/MikroOrmSqliteAdapter.js";
12
12
  import type { MappedEntityReference } from "../../../../../application/orm/mapping/contracts.js";
13
13
  /** Internal mapping from a public descriptor to its generated MikroORM entity. */
@@ -29,6 +29,10 @@ export declare class MikroOrmScalarPersistenceProvider implements ScalarEntityPe
29
29
  constructor(storage: MikroOrmSqliteAdapter, bindings: readonly MikroOrmScalarEntityBinding[], flushCoordinator?: ScalarEntityFlushCoordinator | undefined);
30
30
  /** Reads through a clean manager without flushing another Unit of Work. */
31
31
  read(query: ScalarEntityQuery): Promise<readonly ScalarEntityRow[]>;
32
+ /** Counts rows through a clean manager without materializing entities. */
33
+ count(query: ScalarEntityCountQuery): Promise<number>;
34
+ /** Runs sequential reads through one consistent SQLite read transaction. */
35
+ readSnapshot<Result>(work: (reader: ScalarEntityReader) => Promise<Result>): Promise<Result>;
32
36
  /** Runs the common UoW plan and mapped work in one SQLite transaction. */
33
37
  beginTransaction<Result>(work: (transaction: ScalarEntityTransaction) => Promise<Result>): Promise<Result>;
34
38
  /** Closes the one SQLite connection owned by this provider. */
@@ -1 +1 @@
1
- {"version":3,"file":"MikroOrmScalarPersistenceProvider.d.ts","sourceRoot":"","sources":["../../../../../../src/adapter/persistence/providers/mikro-orm/api/MikroOrmScalarPersistenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,8BAA8B,CAAC;AAErF,OAAO,KAAK,EAGV,4BAA4B,EAE5B,+BAA+B,EAC/B,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EAGxB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,qCAAqC,CAAC;AAG7C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qDAAqD,CAAC;AAEjG,kFAAkF;AAClF,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,UAAU,EAAE,gCAAgC,CAAC;IACtD,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACjE;AAED;;;;GAIG;AACH,qBAAa,iCAAkC,YAAW,+BAA+B;IAKrF,OAAO,CAAC,QAAQ,CAAC,OAAO;IAExB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IANpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmD;IAC5E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmD;gBAGhE,OAAO,EAAE,qBAAqB,EAC/C,QAAQ,EAAE,SAAS,2BAA2B,EAAE,EAC/B,gBAAgB,CAAC,EAAE,4BAA4B,YAAA;IAMlE,2EAA2E;IACrE,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;IAKzE,0EAA0E;IACpE,gBAAgB,CAAC,MAAM,EAC3B,IAAI,EAAE,CAAC,WAAW,EAAE,uBAAuB,KAAK,OAAO,CAAC,MAAM,CAAC,GAC9D,OAAO,CAAC,MAAM,CAAC;IAalB,+DAA+D;IACzD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,CAAC,qBAAqB;CAK9B"}
1
+ {"version":3,"file":"MikroOrmScalarPersistenceProvider.d.ts","sourceRoot":"","sources":["../../../../../../src/adapter/persistence/providers/mikro-orm/api/MikroOrmScalarPersistenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,8BAA8B,CAAC;AAErF,OAAO,KAAK,EACV,sBAAsB,EAGtB,4BAA4B,EAE5B,+BAA+B,EAE/B,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,uBAAuB,EAGxB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,qCAAqC,CAAC;AAG7C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qDAAqD,CAAC;AAEjG,kFAAkF;AAClF,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,UAAU,EAAE,gCAAgC,CAAC;IACtD,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACjE;AAED;;;;GAIG;AACH,qBAAa,iCAAkC,YAAW,+BAA+B;IAKrF,OAAO,CAAC,QAAQ,CAAC,OAAO;IAExB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IANpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmD;IAC5E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmD;gBAGhE,OAAO,EAAE,qBAAqB,EAC/C,QAAQ,EAAE,SAAS,2BAA2B,EAAE,EAC/B,gBAAgB,CAAC,EAAE,4BAA4B,YAAA;IAMlE,2EAA2E;IACrE,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;IAKzE,0EAA0E;IACpE,KAAK,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAK3D,4EAA4E;IACtE,YAAY,CAAC,MAAM,EACvB,IAAI,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,MAAM,CAAC,GACpD,OAAO,CAAC,MAAM,CAAC;IAUlB,0EAA0E;IACpE,gBAAgB,CAAC,MAAM,EAC3B,IAAI,EAAE,CAAC,WAAW,EAAE,uBAAuB,KAAK,OAAO,CAAC,MAAM,CAAC,GAC9D,OAAO,CAAC,MAAM,CAAC;IAalB,+DAA+D;IACzD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,CAAC,qBAAqB;CAK9B"}
@@ -30,6 +30,17 @@ export class MikroOrmScalarPersistenceProvider {
30
30
  const manager = this.storage.forkEntityManager();
31
31
  return readRows(manager, this.requireBindingByTable(query.tableName), query);
32
32
  }
33
+ /** Counts rows through a clean manager without materializing entities. */
34
+ async count(query) {
35
+ const manager = this.storage.forkEntityManager();
36
+ return countRows(manager, this.requireBindingByTable(query.tableName), query);
37
+ }
38
+ /** Runs sequential reads through one consistent SQLite read transaction. */
39
+ async readSnapshot(work) {
40
+ return this.storage.transactional(async ({ entityManager }) => {
41
+ return work(new MikroOrmScalarReader(entityManager, this.bindings, this.bindingsByTable));
42
+ });
43
+ }
33
44
  /** Runs the common UoW plan and mapped work in one SQLite transaction. */
34
45
  async beginTransaction(work) {
35
46
  return this.storage.transactional(async ({ entityManager }) => {
@@ -61,6 +72,9 @@ class MikroOrmScalarReader {
61
72
  async read(query) {
62
73
  return readRows(this.entityManager, this.requireBindingByTable(query.tableName), query);
63
74
  }
75
+ async count(query) {
76
+ return countRows(this.entityManager, this.requireBindingByTable(query.tableName), query);
77
+ }
64
78
  requireBindingByTable(tableName) {
65
79
  const binding = this.bindingsByTable.get(tableName);
66
80
  if (binding !== undefined)
@@ -149,7 +163,7 @@ function createManagedEntity(entityManager, binding, values) {
149
163
  async function readRows(entityManager, binding, query) {
150
164
  const result = await Reflect.apply(entityManager.find, entityManager, [
151
165
  binding.entity,
152
- toMikroOrmFilter(binding.descriptor, query.where),
166
+ toMikroOrmFilter(toInternalPredicate(binding.descriptor, query.predicate), query.primaryKeyColumn),
153
167
  toMikroOrmQueryOptions(query),
154
168
  ]);
155
169
  if (!Array.isArray(result) || !result.every(isRecord)) {
@@ -157,17 +171,79 @@ async function readRows(entityManager, binding, query) {
157
171
  }
158
172
  return result.map((entity) => fromInternalEntity(binding.descriptor, entity));
159
173
  }
174
+ async function countRows(entityManager, binding, query) {
175
+ const result = await Reflect.apply(entityManager.count, entityManager, [
176
+ binding.entity,
177
+ toMikroOrmFilter(toInternalPredicate(binding.descriptor, query.predicate), query.primaryKeyColumn),
178
+ ]);
179
+ if (typeof result !== "number" || !Number.isSafeInteger(result) || result < 0) {
180
+ throw new HikouteiError(HIKOUTEI_ERROR_CODES.INVALID_SCALAR_VALUE, "MikroORM count result must be a non-negative safe integer.");
181
+ }
182
+ return result;
183
+ }
160
184
  function toMikroOrmQueryOptions(query) {
185
+ const orderBy = query.orderBy.map((order) => ({ [order.field]: order.direction }));
186
+ // SQLite rejects a bare `OFFSET` (it requires a preceding `LIMIT`), so an
187
+ // offset-only public query must use the SQLite idiom for "all rows after a
188
+ // skip": a negative `LIMIT`, which SQLite treats as no upper bound. The
189
+ // public option contract still allows `{ offset }` independently of
190
+ // `{ limit }`; this `LIMIT -1` stays inside the MikroORM adapter boundary and
191
+ // is never exposed as raw SQL to application code. `limit` is `0`-safe via
192
+ // nullish coalescing: an explicit `limit: 0` is preserved unchanged.
193
+ const limit = query.limit ?? (query.offset === undefined ? undefined : -1);
161
194
  return {
162
- ...(query.limit === undefined ? {} : { limit: query.limit }),
195
+ ...(orderBy.length === 0 ? {} : { orderBy }),
196
+ ...(limit === undefined ? {} : { limit }),
163
197
  ...(query.offset === undefined ? {} : { offset: query.offset }),
164
198
  };
165
199
  }
166
- function toMikroOrmFilter(descriptor, where) {
167
- return Object.fromEntries(Object.entries(where).map(([property, value]) => [
168
- property,
169
- toInternalValue(descriptor, property, value),
170
- ]));
200
+ function toInternalPredicate(descriptor, predicate) {
201
+ switch (predicate.kind) {
202
+ case "comparison":
203
+ return {
204
+ ...predicate,
205
+ value: toInternalValue(descriptor, predicate.field, predicate.value),
206
+ };
207
+ case "set":
208
+ return {
209
+ ...predicate,
210
+ values: predicate.values.map((value) => toInternalValue(descriptor, predicate.field, value)),
211
+ };
212
+ case "all":
213
+ case "any":
214
+ return {
215
+ ...predicate,
216
+ predicates: predicate.predicates.map((child) => toInternalPredicate(descriptor, child)),
217
+ };
218
+ case "like":
219
+ case "null":
220
+ case "constant":
221
+ return predicate;
222
+ }
223
+ }
224
+ function toMikroOrmFilter(predicate, primaryKeyColumn) {
225
+ switch (predicate.kind) {
226
+ case "comparison":
227
+ return { [predicate.field]: { [`$${predicate.operator}`]: predicate.value } };
228
+ case "set":
229
+ return { [predicate.field]: { [`$${predicate.operator}`]: [...predicate.values] } };
230
+ case "like":
231
+ return { [predicate.field]: { $like: predicate.pattern } };
232
+ case "null":
233
+ return predicate.operator === "is_null"
234
+ ? { [predicate.field]: null }
235
+ : { [predicate.field]: { $ne: null } };
236
+ case "constant":
237
+ return predicate.value ? {} : { [primaryKeyColumn]: { $in: [] } };
238
+ case "all":
239
+ return {
240
+ $and: predicate.predicates.map((child) => toMikroOrmFilter(child, primaryKeyColumn)),
241
+ };
242
+ case "any":
243
+ return {
244
+ $or: predicate.predicates.map((child) => toMikroOrmFilter(child, primaryKeyColumn)),
245
+ };
246
+ }
171
247
  }
172
248
  function toInternalData(descriptor, values) {
173
249
  return Object.fromEntries(Object.entries(values).map(([property, value]) => [