@ontrails/store 1.0.0-beta.14

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 (46) hide show
  1. package/.agents/notes/2026-04-04/handoff-202604032309-9e85a104.md +38 -0
  2. package/.turbo/turbo-build.log +1 -0
  3. package/.turbo/turbo-lint.log +3 -0
  4. package/.turbo/turbo-typecheck.log +1 -0
  5. package/CHANGELOG.md +12 -0
  6. package/README.md +213 -0
  7. package/dist/drizzle/index.d.ts +3 -0
  8. package/dist/drizzle/index.d.ts.map +1 -0
  9. package/dist/drizzle/index.js +2 -0
  10. package/dist/drizzle/index.js.map +1 -0
  11. package/dist/drizzle/runtime.d.ts +21 -0
  12. package/dist/drizzle/runtime.d.ts.map +1 -0
  13. package/dist/drizzle/runtime.js +458 -0
  14. package/dist/drizzle/runtime.js.map +1 -0
  15. package/dist/drizzle/schema.d.ts +15 -0
  16. package/dist/drizzle/schema.d.ts.map +1 -0
  17. package/dist/drizzle/schema.js +322 -0
  18. package/dist/drizzle/schema.js.map +1 -0
  19. package/dist/drizzle/types.d.ts +40 -0
  20. package/dist/drizzle/types.d.ts.map +1 -0
  21. package/dist/drizzle/types.js +2 -0
  22. package/dist/drizzle/types.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/store.d.ts +26 -0
  28. package/dist/store.d.ts.map +1 -0
  29. package/dist/store.js +192 -0
  30. package/dist/store.js.map +1 -0
  31. package/dist/types.d.ts +224 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +2 -0
  34. package/dist/types.js.map +1 -0
  35. package/package.json +29 -0
  36. package/src/__tests__/store.test.ts +333 -0
  37. package/src/drizzle/__tests__/drizzle.test.ts +469 -0
  38. package/src/drizzle/index.ts +17 -0
  39. package/src/drizzle/runtime.ts +853 -0
  40. package/src/drizzle/schema.ts +577 -0
  41. package/src/drizzle/types.ts +70 -0
  42. package/src/index.ts +39 -0
  43. package/src/store.ts +367 -0
  44. package/src/types.ts +361 -0
  45. package/tsconfig.json +9 -0
  46. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,38 @@
1
+ ---
2
+ created: 2026-04-04T03:09:15.424Z
3
+ type: handoff
4
+ session: 9e85a104-864f-4273-b68d-a4a95eb2d5fc
5
+ ---
6
+
7
+ # Handoff 2026-04-04 23:09
8
+
9
+ > Session `9e85a104` — PR feedback loop on 14-branch Graphite stack (#64-#77)
10
+
11
+ ## Done
12
+
13
+ - **Round 1**: Fixed 11 P1s across the stack — `persistEstablishedTopoSave` throw→Result, hash pipeline unification in drift.ts, store type safety (nullable defaults, PK leak, InsertOf/UpdateOf precision), drizzle error mapping, legacy tracker DB cleanup, transitive cache TSDoc, duplicate const before compile error
14
+ - **Round 2**: Fixed absorb regressions (variable reference splits across branches), plus new P1s — Commander undefined opts, toTrackStore close() no-op, verifyCurrentTopo hash pipeline, duplicate verifyCurrentTopo removal
15
+ - **Round 3**: Fixed remaining P1s — ADR depends_on slug→integer normalization, draft-promote preflight rename validation (duplicates + existing targets), NOT NULL on PK columns in generated SQL, InternalError passthrough, warden README method names (.trailhead→.blaze), vocabulary verb fix
16
+ - **Thread resolution**: 128 threads resolved across 3 rounds, re-reviews requested each time
17
+ - **Key learning**: `gt absorb` splits changes across branches unpredictably when a function definition and its callers span different branches — always walk up and typecheck each branch after absorb
18
+
19
+ ## State
20
+
21
+ - On branch `trl-129-topo-and-dev-surfaces` (#69) with an uncommitted fix (drift.ts resolveTrailsDir import + readTrailheadLock dir fix) — lint failed, needs investigation
22
+ - 4 new P1s surfaced in Round 4 (from `-p 2` run which includes P0-P2):
23
+ - **#69**: drift.ts passes workspace root instead of `.trails/` to readTrailheadLock (fix attempted, lint error pending)
24
+ - **#71**: v2→v3 migration gap in topo-saves.ts — crash on existing schema version 2 databases
25
+ - **#72**: createMockTopoStore returns data without seeded saves (diverges from real store)
26
+ - **#77**: Three broken API examples in warden README (wrong signatures, invalid lefthook key)
27
+ - P2s not yet addressed across #65, #68, #70, #72, #75
28
+
29
+ ## Next
30
+
31
+ - [ ] Debug lint failure on #69 drift.ts fix and commit
32
+ - [ ] Fix #71: Add v2→v3 migration guard in `ensureTopoHistorySchema` to create `topo_schemas`/`topo_exports` tables
33
+ - [ ] Fix #72: Add empty-save guard to `createMockTopoStore` `trails.list()`/`trails.get()`
34
+ - [ ] Fix #77: Correct `runWarden` and `checkDrift` signatures + lefthook key in warden README
35
+ - [ ] Walk up all branches verifying typecheck after each fix
36
+ - [ ] Submit stack, resolve threads, write loop record
37
+ - [ ] Then address P2s: gitignore duplicate-append (#68), stale TSDoc (#70), test harness fallback (#65), readonlyStore mock factory (#75)
38
+ - [ ] Consider: the `isDraftMarkedFile` vs `stripDraftFileMarkers` inconsistency on #67 is still open (P2)
@@ -0,0 +1 @@
1
+ $ tsc -b
@@ -0,0 +1,3 @@
1
+ $ oxlint ./src
2
+ Found 0 warnings and 0 errors.
3
+ Finished in 27ms on 9 files with 93 rules using 24 threads.
@@ -0,0 +1 @@
1
+ $ tsc --noEmit
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # @ontrails/store
2
+
3
+ ## 1.0.0-beta.14
4
+
5
+ ### Minor Changes
6
+
7
+ - 69057e9: Add hierarchical CLI command trees and structured input, enforce established-only topo exports across trailheads, move developer topo and tracker state onto shared `trails.db` with pins and maintenance flows, and ship schema-derived stores through `@ontrails/store` and its Drizzle runtime.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [69057e9]
12
+ - @ontrails/core@1.0.0-beta.14
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # @ontrails/store
2
+
3
+ Schema-derived persistence for Trails.
4
+
5
+ The root package owns the connector-agnostic `store(...)` declaration. Connector packages such as `@ontrails/store/drizzle` bind that declaration to a concrete runtime, just like a trailhead connector binds a topo to CLI, MCP, or HTTP.
6
+
7
+ ## The two layers
8
+
9
+ ### 1. Declare the store contract
10
+
11
+ ```typescript
12
+ import { store } from '@ontrails/store';
13
+
14
+ export const db = store({
15
+ gists: {
16
+ schema: gistSchema,
17
+ primaryKey: 'id',
18
+ generated: ['id', 'createdAt', 'updatedAt'],
19
+ indexes: ['owner', 'createdAt'],
20
+ },
21
+ files: {
22
+ schema: fileSchema,
23
+ primaryKey: 'id',
24
+ generated: ['id'],
25
+ references: { gistId: 'gists' },
26
+ },
27
+ });
28
+ ```
29
+
30
+ This declaration is pure metadata:
31
+
32
+ - full entity schema
33
+ - insert schema
34
+ - update schema
35
+ - fixture schema
36
+ - generated-field metadata
37
+ - indexes
38
+ - references
39
+
40
+ No database connection is opened here. The returned value is the durable authored source of truth.
41
+
42
+ ### 2. Bind it to a concrete runtime
43
+
44
+ ```typescript
45
+ import { store } from '@ontrails/store';
46
+ import { connectDrizzle } from '@ontrails/store/drizzle';
47
+
48
+ const definition = store({
49
+ gists: {
50
+ schema: gistSchema,
51
+ primaryKey: 'id',
52
+ generated: ['id', 'createdAt', 'updatedAt'],
53
+ },
54
+ });
55
+
56
+ export const db = connectDrizzle(definition, {
57
+ id: 'db.main',
58
+ url: ':memory:',
59
+ });
60
+ ```
61
+
62
+ The bound store is a provision. Use it directly in trails:
63
+
64
+ ```typescript
65
+ export const list = trail('gist.list', {
66
+ provisions: [db],
67
+ intent: 'read',
68
+ blaze: async (_input, ctx) => {
69
+ const conn = db.from(ctx);
70
+ const gists = await conn.gists.list();
71
+ return Result.ok(gists);
72
+ },
73
+ });
74
+ ```
75
+
76
+ ## Typed accessors
77
+
78
+ Every table on a bound connection exposes typed CRUD accessors:
79
+
80
+ ```typescript
81
+ const conn = db.from(ctx);
82
+
83
+ const created = await conn.gists.insert({
84
+ owner: 'matt',
85
+ description: 'Hello, Trails',
86
+ });
87
+
88
+ const found = await conn.gists.get(created.id);
89
+ const page = await conn.gists.list({ owner: 'matt' }, { limit: 20, offset: 0 });
90
+ const updated = await conn.gists.update(created.id, {
91
+ description: 'Updated description',
92
+ });
93
+ const removed = await conn.gists.remove(created.id);
94
+ ```
95
+
96
+ Types are derived from the Zod schema:
97
+
98
+ - `insert()` uses the entity schema minus generated fields
99
+ - `update()` uses the entity schema minus generated fields, then makes it partial
100
+ - `get()` returns `Entity | null`
101
+ - `list()` accepts typed partial filters and pagination options
102
+
103
+ ## Fixtures and mocks
104
+
105
+ Fixtures belong on the root definition:
106
+
107
+ ```typescript
108
+ export const db = store({
109
+ gists: {
110
+ schema: gistSchema,
111
+ primaryKey: 'id',
112
+ generated: ['id', 'createdAt', 'updatedAt'],
113
+ fixtures: [
114
+ { id: 'g_1', owner: 'matt', description: 'Seed gist' },
115
+ ],
116
+ },
117
+ });
118
+ ```
119
+
120
+ When a connector binds the store, those fixtures feed the provision mock automatically. Connector options can also add or override seed data for tests.
121
+
122
+ That means `testAll(app)` can auto-resolve connector-bound store provisions without extra ceremony, as long as the provision is registered in the topo.
123
+
124
+ ## Read-only bindings
125
+
126
+ Use the Drizzle connector's read-only helpers when a trail should inspect persisted state without exposing writes:
127
+
128
+ ```typescript
129
+ import { connectReadOnlyDrizzle, readonlyStore } from '@ontrails/store/drizzle';
130
+
131
+ const analytics = connectReadOnlyDrizzle(definition, {
132
+ id: 'analytics.db',
133
+ url: './data/analytics.sqlite',
134
+ });
135
+
136
+ const auditLog = readonlyStore(
137
+ {
138
+ entries: {
139
+ schema: auditEntrySchema,
140
+ primaryKey: 'id',
141
+ generated: ['id', 'createdAt'],
142
+ },
143
+ },
144
+ { id: 'audit.db', url: './data/audit.sqlite' }
145
+ );
146
+ ```
147
+
148
+ Read-only bindings expose `get()`, `list()`, and `query()`, but not `insert()`, `update()`, or `remove()`.
149
+
150
+ ## Drizzle escape hatch
151
+
152
+ Complex queries use the connector-native query builder through `query()`:
153
+
154
+ ```typescript
155
+ const conn = db.from(ctx);
156
+
157
+ const rows = await conn.query(({ drizzle, tables }) =>
158
+ drizzle
159
+ .select()
160
+ .from(tables.gists)
161
+ );
162
+ ```
163
+
164
+ This keeps the default happy path derived and typed, while still giving you full access to the underlying connector when the CRUD accessors are not enough.
165
+
166
+ ## Connector conveniences
167
+
168
+ `@ontrails/store/drizzle` also exports one-line conveniences when you want declaration and binding together:
169
+
170
+ ```typescript
171
+ import { store, readonlyStore } from '@ontrails/store/drizzle';
172
+
173
+ export const writable = store(
174
+ {
175
+ gists: {
176
+ schema: gistSchema,
177
+ primaryKey: 'id',
178
+ generated: ['id', 'createdAt', 'updatedAt'],
179
+ },
180
+ },
181
+ { url: ':memory:' }
182
+ );
183
+
184
+ export const readonly = readonlyStore(
185
+ {
186
+ gists: {
187
+ schema: gistSchema,
188
+ primaryKey: 'id',
189
+ generated: ['id', 'createdAt', 'updatedAt'],
190
+ },
191
+ },
192
+ { url: './data/gists.sqlite' }
193
+ );
194
+ ```
195
+
196
+ These are conveniences, not the architectural source of truth. The root package still owns the durable authored `store(...)` model.
197
+
198
+ ## Schema export for external tooling
199
+
200
+ If you need the raw derived Drizzle tables for tooling such as `drizzle-kit`, use `getSchema()`:
201
+
202
+ ```typescript
203
+ import { getSchema } from '@ontrails/store/drizzle';
204
+
205
+ const schema = getSchema(db);
206
+ ```
207
+
208
+ ## Installation
209
+
210
+ ```bash
211
+ bun add @ontrails/store zod
212
+ bun add drizzle-orm
213
+ ```
@@ -0,0 +1,3 @@
1
+ export { connectDrizzle, connectReadOnlyDrizzle, getSchema, readonlyStore, store, } from './runtime.js';
2
+ export type { ConnectDrizzleOptions, DrizzleMockSeed, DrizzleQueryContext, DrizzleStoreConnection, DrizzleStoreProvision, DrizzleStoreSchema, ReadOnlyDrizzleOptions, ReadOnlyDrizzleStoreConnection, } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/drizzle/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,SAAS,EACT,aAAa,EACb,KAAK,GACN,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { connectDrizzle, connectReadOnlyDrizzle, getSchema, readonlyStore, store, } from './runtime.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/drizzle/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,SAAS,EACT,aAAa,EACb,KAAK,GACN,MAAM,cAAc,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { store as defineStore } from '../store.js';
2
+ import type { AnyStoreDefinition, StoreAccessMode, StoreTablesInput } from '../types.js';
3
+ import type { ConnectDrizzleOptions, DrizzleStoreConnection, DrizzleStoreProvision, DrizzleStoreSchema, ReadOnlyDrizzleOptions, ReadOnlyDrizzleStoreConnection } from './types.js';
4
+ /**
5
+ * Bind a store definition to a Drizzle-backed SQLite provision.
6
+ *
7
+ * The returned provision manages its own connection lifecycle. The `mock()`
8
+ * factory creates an in-memory SQLite database seeded with fixtures — callers
9
+ * who obtain a mock connection are responsible for calling `closeConnection()`
10
+ * when done, or letting the connection be garbage-collected (the underlying
11
+ * `Database` client is tracked via `WeakMap`).
12
+ *
13
+ * Note: the `search` field on `StoreTableInput` is not yet interpreted by
14
+ * this connector — it is reserved for future full-text search support.
15
+ */
16
+ export declare const connectDrizzle: <const TStore extends AnyStoreDefinition>(definition: TStore, options: ConnectDrizzleOptions<TStore>) => DrizzleStoreProvision<TStore, DrizzleStoreConnection<TStore>, "readwrite">;
17
+ export declare const connectReadOnlyDrizzle: <const TStore extends AnyStoreDefinition>(definition: TStore, options: ReadOnlyDrizzleOptions) => DrizzleStoreProvision<TStore, ReadOnlyDrizzleStoreConnection<TStore>, "readonly">;
18
+ export declare const store: <const TTables extends StoreTablesInput>(tables: TTables, options: ConnectDrizzleOptions<ReturnType<typeof defineStore<TTables>>>) => DrizzleStoreProvision<ReturnType<typeof defineStore<TTables>>, DrizzleStoreConnection<ReturnType<typeof defineStore<TTables>>>, "readwrite">;
19
+ export declare const readonlyStore: <const TTables extends StoreTablesInput>(tables: TTables, options: ReadOnlyDrizzleOptions) => DrizzleStoreProvision<ReturnType<typeof defineStore<TTables>>, ReadOnlyDrizzleStoreConnection<ReturnType<typeof defineStore<TTables>>>, "readonly">;
20
+ export declare const getSchema: <TStore extends AnyStoreDefinition>(binding: Pick<DrizzleStoreProvision<TStore, unknown, StoreAccessMode>, "tables">) => DrizzleStoreSchema<TStore>;
21
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/drizzle/runtime.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EACV,kBAAkB,EAMlB,eAAe,EAGf,gBAAgB,EAEjB,MAAM,aAAa,CAAC;AAMrB,OAAO,KAAK,EACV,qBAAqB,EAErB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,8BAA8B,EAC/B,MAAM,YAAY,CAAC;AAmqBpB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,CAAC,MAAM,SAAS,kBAAkB,EACpE,YAAY,MAAM,EAClB,SAAS,qBAAqB,CAAC,MAAM,CAAC,KACrC,qBAAqB,CACtB,MAAM,EACN,sBAAsB,CAAC,MAAM,CAAC,EAC9B,WAAW,CAoDZ,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,KAAK,CAAC,MAAM,SAAS,kBAAkB,EAC5E,YAAY,MAAM,EAClB,SAAS,sBAAsB,KAC9B,qBAAqB,CACtB,MAAM,EACN,8BAA8B,CAAC,MAAM,CAAC,EACtC,UAAU,CAkCX,CAAC;AAEF,eAAO,MAAM,KAAK,GAAI,KAAK,CAAC,OAAO,SAAS,gBAAgB,EAC1D,QAAQ,OAAO,EACf,SAAS,qBAAqB,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,KACtE,qBAAqB,CACtB,UAAU,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,EACvC,sBAAsB,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAC/D,WAAW,CACoC,CAAC;AAElD,eAAO,MAAM,aAAa,GAAI,KAAK,CAAC,OAAO,SAAS,gBAAgB,EAClE,QAAQ,OAAO,EACf,SAAS,sBAAsB,KAC9B,qBAAqB,CACtB,UAAU,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,EACvC,8BAA8B,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EACvE,UAAU,CAC6C,CAAC;AAE1D,eAAO,MAAM,SAAS,GAAI,MAAM,SAAS,kBAAkB,EACzD,SAAS,IAAI,CACX,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,eAAe,CAAC,EACvD,QAAQ,CACT,KACA,kBAAkB,CAAC,MAAM,CAAmB,CAAC"}