@ontrails/store 1.0.0-beta.14 → 1.0.0-beta.15

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 (92) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/README.md +126 -61
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/internal/signal-identity.d.ts +17 -0
  9. package/dist/internal/signal-identity.d.ts.map +1 -0
  10. package/dist/internal/signal-identity.js +81 -0
  11. package/dist/internal/signal-identity.js.map +1 -0
  12. package/dist/jsonfile/index.d.ts +3 -0
  13. package/dist/jsonfile/index.d.ts.map +1 -0
  14. package/dist/jsonfile/index.js +2 -0
  15. package/dist/jsonfile/index.js.map +1 -0
  16. package/dist/jsonfile/runtime.d.ts +19 -0
  17. package/dist/jsonfile/runtime.d.ts.map +1 -0
  18. package/dist/jsonfile/runtime.js +403 -0
  19. package/dist/jsonfile/runtime.js.map +1 -0
  20. package/dist/jsonfile/types.d.ts +40 -0
  21. package/dist/jsonfile/types.d.ts.map +1 -0
  22. package/dist/{drizzle → jsonfile}/types.js.map +1 -1
  23. package/dist/store.d.ts +8 -18
  24. package/dist/store.d.ts.map +1 -1
  25. package/dist/store.js +152 -94
  26. package/dist/store.js.map +1 -1
  27. package/dist/testing.d.ts +28 -0
  28. package/dist/testing.d.ts.map +1 -0
  29. package/dist/testing.js +90 -0
  30. package/dist/testing.js.map +1 -0
  31. package/dist/trails/crud.d.ts +38 -0
  32. package/dist/trails/crud.d.ts.map +1 -0
  33. package/dist/trails/crud.js +112 -0
  34. package/dist/trails/crud.js.map +1 -0
  35. package/dist/trails/index.d.ts +7 -0
  36. package/dist/trails/index.d.ts.map +1 -0
  37. package/dist/trails/index.js +4 -0
  38. package/dist/trails/index.js.map +1 -0
  39. package/dist/trails/reconcile.d.ts +36 -0
  40. package/dist/trails/reconcile.d.ts.map +1 -0
  41. package/dist/trails/reconcile.js +142 -0
  42. package/dist/trails/reconcile.js.map +1 -0
  43. package/dist/trails/sync.d.ts +25 -0
  44. package/dist/trails/sync.d.ts.map +1 -0
  45. package/dist/trails/sync.js +96 -0
  46. package/dist/trails/sync.js.map +1 -0
  47. package/dist/trails/utils.d.ts +51 -0
  48. package/dist/trails/utils.d.ts.map +1 -0
  49. package/dist/trails/utils.js +65 -0
  50. package/dist/trails/utils.js.map +1 -0
  51. package/dist/types.d.ts +205 -35
  52. package/dist/types.d.ts.map +1 -1
  53. package/package.json +6 -9
  54. package/src/__tests__/crud.test.ts +346 -0
  55. package/src/__tests__/jsonfile.test.ts +502 -0
  56. package/src/__tests__/store.test.ts +239 -33
  57. package/src/__tests__/sync-reconcile.test.ts +646 -0
  58. package/src/index.ts +12 -8
  59. package/src/internal/signal-identity.ts +162 -0
  60. package/src/jsonfile/index.ts +6 -0
  61. package/src/jsonfile/runtime.ts +702 -0
  62. package/src/jsonfile/types.ts +50 -0
  63. package/src/store.ts +296 -134
  64. package/src/testing.ts +175 -0
  65. package/src/trails/crud.ts +355 -0
  66. package/src/trails/index.ts +10 -0
  67. package/src/trails/reconcile.ts +281 -0
  68. package/src/trails/sync.ts +251 -0
  69. package/src/trails/utils.ts +96 -0
  70. package/src/types.ts +355 -63
  71. package/tsconfig.tests.json +10 -0
  72. package/tsconfig.tsbuildinfo +1 -1
  73. package/dist/drizzle/index.d.ts +0 -3
  74. package/dist/drizzle/index.d.ts.map +0 -1
  75. package/dist/drizzle/index.js +0 -2
  76. package/dist/drizzle/index.js.map +0 -1
  77. package/dist/drizzle/runtime.d.ts +0 -21
  78. package/dist/drizzle/runtime.d.ts.map +0 -1
  79. package/dist/drizzle/runtime.js +0 -458
  80. package/dist/drizzle/runtime.js.map +0 -1
  81. package/dist/drizzle/schema.d.ts +0 -15
  82. package/dist/drizzle/schema.d.ts.map +0 -1
  83. package/dist/drizzle/schema.js +0 -322
  84. package/dist/drizzle/schema.js.map +0 -1
  85. package/dist/drizzle/types.d.ts +0 -40
  86. package/dist/drizzle/types.d.ts.map +0 -1
  87. package/src/drizzle/__tests__/drizzle.test.ts +0 -469
  88. package/src/drizzle/index.ts +0 -17
  89. package/src/drizzle/runtime.ts +0 -853
  90. package/src/drizzle/schema.ts +0 -577
  91. package/src/drizzle/types.ts +0 -70
  92. /package/dist/{drizzle → jsonfile}/types.js +0 -0
@@ -1,3 +1,3 @@
1
1
  $ oxlint ./src
2
2
  Found 0 warnings and 0 errors.
3
- Finished in 27ms on 9 files with 93 rules using 24 threads.
3
+ Finished in 71ms on 17 files with 93 rules using 24 threads.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @ontrails/store
2
2
 
3
+ ## 1.0.0-beta.15
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [4ad6b25]
8
+ - @ontrails/core@1.0.0-beta.15
9
+
3
10
  ## 1.0.0-beta.14
4
11
 
5
12
  ### Minor Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Schema-derived persistence for Trails.
4
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.
5
+ The root package owns the connector-agnostic `store(...)` declaration. External connector packages such as `@ontrails/drizzle` bind that declaration to a concrete runtime, and first-party built-ins such as `@ontrails/store/jsonfile` live as opt-in subpaths on the same package.
6
6
 
7
7
  ## The two layers
8
8
 
@@ -14,13 +14,14 @@ import { store } from '@ontrails/store';
14
14
  export const db = store({
15
15
  gists: {
16
16
  schema: gistSchema,
17
- primaryKey: 'id',
17
+ identity: 'id',
18
18
  generated: ['id', 'createdAt', 'updatedAt'],
19
- indexes: ['owner', 'createdAt'],
19
+ indexed: ['owner', 'createdAt'],
20
+ versioned: true,
20
21
  },
21
22
  files: {
22
23
  schema: fileSchema,
23
- primaryKey: 'id',
24
+ identity: 'id',
24
25
  generated: ['id'],
25
26
  references: { gistId: 'gists' },
26
27
  },
@@ -33,8 +34,11 @@ This declaration is pure metadata:
33
34
  - insert schema
34
35
  - update schema
35
36
  - fixture schema
37
+ - derived change-signal handles (`table.signals.created|updated|removed`)
38
+ - identity field
36
39
  - generated-field metadata
37
- - indexes
40
+ - optional framework-managed version tracking
41
+ - indexed markers
38
42
  - references
39
43
 
40
44
  No database connection is opened here. The returned value is the durable authored source of truth.
@@ -43,12 +47,12 @@ No database connection is opened here. The returned value is the durable authore
43
47
 
44
48
  ```typescript
45
49
  import { store } from '@ontrails/store';
46
- import { connectDrizzle } from '@ontrails/store/drizzle';
50
+ import { connectDrizzle } from '@ontrails/drizzle';
47
51
 
48
52
  const definition = store({
49
53
  gists: {
50
54
  schema: gistSchema,
51
- primaryKey: 'id',
55
+ identity: 'id',
52
56
  generated: ['id', 'createdAt', 'updatedAt'],
53
57
  },
54
58
  });
@@ -59,11 +63,11 @@ export const db = connectDrizzle(definition, {
59
63
  });
60
64
  ```
61
65
 
62
- The bound store is a provision. Use it directly in trails:
66
+ The bound store is a resource. Use it directly in trails:
63
67
 
64
68
  ```typescript
65
69
  export const list = trail('gist.list', {
66
- provisions: [db],
70
+ resources: [db],
67
71
  intent: 'read',
68
72
  blaze: async (_input, ctx) => {
69
73
  const conn = db.from(ctx);
@@ -73,32 +77,76 @@ export const list = trail('gist.list', {
73
77
  });
74
78
  ```
75
79
 
80
+ ### Built-in local backend
81
+
82
+ For a zero-extra-package local backend, use the first-party JSON file binding:
83
+
84
+ ```typescript
85
+ import { store } from '@ontrails/store';
86
+ import { jsonFile } from '@ontrails/store/jsonfile';
87
+
88
+ const definition = store({
89
+ gists: {
90
+ schema: gistSchema,
91
+ identity: 'id',
92
+ generated: ['id', 'createdAt', 'updatedAt'],
93
+ },
94
+ });
95
+
96
+ export const db = jsonFile(definition, {
97
+ dir: './data',
98
+ });
99
+ ```
100
+
76
101
  ## Typed accessors
77
102
 
78
- Every table on a bound connection exposes typed CRUD accessors:
103
+ Every writable table on a bound connection exposes the connector-agnostic accessor contract:
79
104
 
80
105
  ```typescript
81
106
  const conn = db.from(ctx);
82
107
 
83
- const created = await conn.gists.insert({
84
- owner: 'matt',
108
+ const created = await conn.gists.upsert({
109
+ ownerId: 'matt',
85
110
  description: 'Hello, Trails',
86
111
  });
87
112
 
88
113
  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, {
114
+ const page = await conn.gists.list({ ownerId: 'matt' }, { limit: 20, offset: 0 });
115
+ const updated = await conn.gists.upsert({
91
116
  description: 'Updated description',
117
+ id: created.id,
118
+ ownerId: 'matt',
92
119
  });
93
120
  const removed = await conn.gists.remove(created.id);
94
121
  ```
95
122
 
96
123
  Types are derived from the Zod schema:
97
124
 
98
- - `insert()` uses the entity schema minus generated fields
99
- - `update()` uses the entity schema minus generated fields, then makes it partial
125
+ - `upsert()` uses the fixture/entity shape with generated fields optional
100
126
  - `get()` returns `Entity | null`
101
127
  - `list()` accepts typed partial filters and pagination options
128
+ - `versioned: true` adds a framework-managed `version` field to returned entities and lets `upsert()` accept an expected `version` for optimistic concurrency
129
+
130
+ Each normalized table also derives typed change signals from the same schema:
131
+
132
+ ```typescript
133
+ const createdHandle = definition.tables.gists.signals.created;
134
+ const updatedHandle = definition.tables.gists.signals.updated;
135
+ const removedHandle = definition.tables.gists.signals.removed;
136
+ ```
137
+
138
+ These pre-bind handles preserve payload shape, but the canonical signal id materializes only when a connector binds the store to a resource. The bound form is always `resource:table.event`:
139
+
140
+ ```typescript
141
+ const created = db.store.tables.gists.signals.created;
142
+
143
+ created.id;
144
+ // "db.main:gists.created"
145
+ ```
146
+
147
+ Writable bindings fire those canonical scoped signals automatically when you access the resource through `db.from(ctx)` inside a trail context.
148
+
149
+ Tabular connectors such as `@ontrails/drizzle` also expose `insert()` and `update()` as convenience methods when the backend natively distinguishes create and patch operations.
102
150
 
103
151
  ## Fixtures and mocks
104
152
 
@@ -108,44 +156,43 @@ Fixtures belong on the root definition:
108
156
  export const db = store({
109
157
  gists: {
110
158
  schema: gistSchema,
111
- primaryKey: 'id',
159
+ identity: 'id',
112
160
  generated: ['id', 'createdAt', 'updatedAt'],
113
161
  fixtures: [
114
- { id: 'g_1', owner: 'matt', description: 'Seed gist' },
162
+ { id: 'g_1', ownerId: 'matt', description: 'Seed gist' },
115
163
  ],
116
164
  },
117
165
  });
118
166
  ```
119
167
 
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.
168
+ When a connector binds the store, those fixtures feed the resource mock automatically. Connector options can also add or override seed data for tests.
121
169
 
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.
170
+ That means `testAll(app)` can auto-resolve connector-bound store resources without extra ceremony, as long as the resource is registered in the topo.
123
171
 
124
172
  ## Read-only bindings
125
173
 
126
- Use the Drizzle connector's read-only helpers when a trail should inspect persisted state without exposing writes:
174
+ Use the Drizzle connector's read-only binding when a trail should inspect persisted state without exposing writes:
127
175
 
128
176
  ```typescript
129
- import { connectReadOnlyDrizzle, readonlyStore } from '@ontrails/store/drizzle';
177
+ import { connectReadOnlyDrizzle } from '@ontrails/drizzle';
130
178
 
131
179
  const analytics = connectReadOnlyDrizzle(definition, {
132
180
  id: 'analytics.db',
133
181
  url: './data/analytics.sqlite',
134
182
  });
183
+ ```
135
184
 
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
- );
185
+ Read-only bindings expose `get()`, `list()`, and `query()`, but not `upsert()`, `remove()`, `insert()`, or `update()`.
186
+
187
+ ## Accessor contract testing
188
+
189
+ Connectors can reuse the shared writable-accessor contract tests from `@ontrails/store/testing`:
190
+
191
+ ```typescript
192
+ import { createStoreAccessorContractCases } from '@ontrails/store/testing';
146
193
  ```
147
194
 
148
- Read-only bindings expose `get()`, `list()`, and `query()`, but not `insert()`, `update()`, or `remove()`.
195
+ That helper provides reusable cases for the baseline `get()`, `list()`, `upsert()`, and `remove()` behavior so connector suites only need to wrap them with their normal `test(...)` calls and add backend-specific coverage on top.
149
196
 
150
197
  ## Drizzle escape hatch
151
198
 
@@ -163,51 +210,69 @@ const rows = await conn.query(({ drizzle, tables }) =>
163
210
 
164
211
  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
212
 
166
- ## Connector conveniences
213
+ ## Connector binding
167
214
 
168
- `@ontrails/store/drizzle` also exports one-line conveniences when you want declaration and binding together:
215
+ `@ontrails/drizzle` keeps the durable `store(...)` declaration in
216
+ `@ontrails/store` and binds it to a concrete runtime:
169
217
 
170
218
  ```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
- );
219
+ import { connectDrizzle, connectReadOnlyDrizzle } from '@ontrails/drizzle';
220
+ import { store } from '@ontrails/store';
183
221
 
184
- export const readonly = readonlyStore(
185
- {
186
- gists: {
187
- schema: gistSchema,
188
- primaryKey: 'id',
189
- generated: ['id', 'createdAt', 'updatedAt'],
190
- },
222
+ const definition = store({
223
+ gists: {
224
+ schema: gistSchema,
225
+ identity: 'id',
226
+ generated: ['id', 'createdAt', 'updatedAt'],
191
227
  },
192
- { url: './data/gists.sqlite' }
193
- );
228
+ });
229
+
230
+ export const writable = connectDrizzle(definition, { url: ':memory:' });
231
+
232
+ export const readonly = connectReadOnlyDrizzle(definition, {
233
+ url: './data/gists.sqlite',
234
+ });
194
235
  ```
195
236
 
196
- These are conveniences, not the architectural source of truth. The root package still owns the durable authored `store(...)` model.
237
+ The root package still owns the authored persistence model; connector packages
238
+ project that model into runnable resources.
197
239
 
198
240
  ## Schema export for external tooling
199
241
 
200
- If you need the raw derived Drizzle tables for tooling such as `drizzle-kit`, use `getSchema()`:
242
+ If you need the raw derived Drizzle tables for tooling such as `drizzle-kit`,
243
+ read them from the bound resource's `tables` field:
201
244
 
202
245
  ```typescript
203
- import { getSchema } from '@ontrails/store/drizzle';
246
+ import { connectDrizzle } from '@ontrails/drizzle';
247
+ import { store } from '@ontrails/store';
248
+
249
+ const definition = store({
250
+ gists: {
251
+ schema: gistSchema,
252
+ identity: 'id',
253
+ generated: ['id', 'createdAt', 'updatedAt'],
254
+ },
255
+ });
204
256
 
205
- const schema = getSchema(db);
257
+ const db = connectDrizzle(definition, { url: ':memory:' });
258
+ const schema = db.tables;
206
259
  ```
207
260
 
208
261
  ## Installation
209
262
 
210
263
  ```bash
211
264
  bun add @ontrails/store zod
212
- bun add drizzle-orm
213
265
  ```
266
+
267
+ Add Drizzle only when you want the external SQLite/ORM connector:
268
+
269
+ ```bash
270
+ bun add @ontrails/drizzle
271
+ ```
272
+
273
+ ## Migration
274
+
275
+ The Drizzle binding now lives in `@ontrails/drizzle`.
276
+
277
+ - Replace `import { ... } from '@ontrails/store/drizzle'` with `import { ... } from '@ontrails/drizzle'`
278
+ - Keep connector-agnostic store declarations on `@ontrails/store`
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { entitySchemaOf, fixtureSchemaOf, insertSchemaOf, store, updateSchemaOf, } from './store.js';
2
- export type { AnyStoreDefinition, AnyStoreTable, EntityOf, FiltersOf, FixtureInputOf, FixtureOf, FixturesOfInput, GeneratedFieldsOfInput, GeneratedKeysOf, IndexFieldsOfInput, InsertOf, PrimaryKeyOf, ReadOnlyStoreConnection, ReadOnlyStoreTableAccessor, ReferencesOfInput, StoreAccessMode, StoreConnection, StoreDefinition, StoreFieldKey, StoreFixtureInput, StoreFixtureRow, StoreIdentifierOf, StoreListOptions, StoreObjectSchema, StoreSearchDefinition, StoreTable, StoreTableAccessor, StoreTableInput, StoreTablesInput, UpdateOf, } from './types.js';
1
+ export { store, versionFieldName } from './store.js';
2
+ export type { AnyStoreDefinition, AnyStoreTable, EntityOf, FiltersOf, FixtureInputOf, FixtureOf, FixturesOfInput, GeneratedFieldsOfInput, GeneratedKeysOf, IdentityFieldOfInput, IdentityOf, IndexFieldsOfInput, IndexedFieldsOfInput, InsertOf, ReadOnlyStoreConnection, StoreAccessor, ReadOnlyStoreTableAccessor, ReferencesOfInput, StoreAccessMode, StoreConnection, StoreConnectorOptions, StoreDefinition, StoreFieldKey, StoreFixtureInput, StoreFixtureRow, StoreIdentifierOf, StoreKind, StoreListOptions, StoreMockSeed, StoreObjectSchema, StoreOptions, StoreSearchDefinition, StoreTable, StoreTableSignals, StoreTableAccessor, StoreTableConnection, StoreTableInput, StoreTablesInput, UpsertOf, UpdateOf, } from './types.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,cAAc,EACd,KAAK,EACL,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,kBAAkB,EAClB,aAAa,EACb,QAAQ,EACR,SAAS,EACT,cAAc,EACd,SAAS,EACT,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,kBAAkB,EAClB,QAAQ,EACR,YAAY,EACZ,uBAAuB,EACvB,0BAA0B,EAC1B,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,QAAQ,GACT,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACrD,YAAY,EACV,kBAAkB,EAClB,aAAa,EACb,QAAQ,EACR,SAAS,EACT,cAAc,EACd,SAAS,EACT,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,QAAQ,EACR,uBAAuB,EACvB,aAAa,EACb,0BAA0B,EAC1B,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,qBAAqB,EACrB,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACR,QAAQ,GACT,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { entitySchemaOf, fixtureSchemaOf, insertSchemaOf, store, updateSchemaOf, } from './store.js';
1
+ export { store, versionFieldName } from './store.js';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,cAAc,EACd,KAAK,EACL,cAAc,GACf,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { z } from 'zod';
2
+ import type { AnyStoreDefinition, StoreTableSignals } from '../types.js';
3
+ type StoreSignalEvent = 'created' | 'removed' | 'updated';
4
+ export declare const createStoreTableSignals: <TPayload>(tableName: string, payload: z.ZodType<TPayload>) => StoreTableSignals<TPayload>;
5
+ export declare const composeStoreSignalId: (scope: string, tableName: string, event: StoreSignalEvent) => string;
6
+ /**
7
+ * Verifies that a resource id is safe to compose into a scoped signal id.
8
+ *
9
+ * Scoped signal ids are matched by the `SCOPED_SIGNAL_ID` pattern in
10
+ * `@ontrails/core` (`^[^:\s]+:[^:.\s]+(?:\.[^:.\s]+)+$`). A resource id used
11
+ * as the scope segment must therefore be a non-empty string that contains
12
+ * neither `":"` nor any whitespace.
13
+ */
14
+ export declare const isValidResourceId: (resourceId: string) => boolean;
15
+ export declare const bindStoreDefinition: <TStore extends AnyStoreDefinition>(definition: TStore, scope: string) => TStore;
16
+ export type { StoreSignalEvent };
17
+ //# sourceMappingURL=signal-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal-identity.d.ts","sourceRoot":"","sources":["../../src/internal/signal-identity.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EACV,kBAAkB,EAElB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAMrB,KAAK,gBAAgB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAsC1D,eAAO,MAAM,uBAAuB,GAAI,QAAQ,EAC9C,WAAW,MAAM,EACjB,SAAS,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAC3B,iBAAiB,CAAC,QAAQ,CAKzB,CAAC;AAEL,eAAO,MAAM,oBAAoB,GAC/B,OAAO,MAAM,EACb,WAAW,MAAM,EACjB,OAAO,gBAAgB,KACtB,MAA0C,CAAC;AAkC9C;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,KAAG,OACuB,CAAC;AAU/E,eAAO,MAAM,mBAAmB,GAAI,MAAM,SAAS,kBAAkB,EACnE,YAAY,MAAM,EAClB,OAAO,MAAM,KACZ,MAiCF,CAAC;AAEF,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { signal, ValidationError } from '@ontrails/core';
2
+ import { attachLateBoundSignalRef, cloneSignalWithId, } from '@ontrails/core/internal/signal-ref';
3
+ const createStoreSignalDescription = (tableName, event) => {
4
+ switch (event) {
5
+ case 'created': {
6
+ return `Fired after a "${tableName}" entity is created.`;
7
+ }
8
+ case 'removed': {
9
+ return `Fired after a "${tableName}" entity is removed.`;
10
+ }
11
+ case 'updated': {
12
+ return `Fired after a "${tableName}" entity is updated.`;
13
+ }
14
+ default: {
15
+ throw new Error(`Unsupported store signal event: ${event}`);
16
+ }
17
+ }
18
+ };
19
+ const createStoreSignal = (tableName, event, payload) => attachLateBoundSignalRef(signal(`${tableName}.${event}`, {
20
+ description: createStoreSignalDescription(tableName, event),
21
+ payload,
22
+ }), {
23
+ kind: 'store-derived',
24
+ token: Bun.randomUUIDv7(),
25
+ });
26
+ export const createStoreTableSignals = (tableName, payload) => Object.freeze({
27
+ created: createStoreSignal(tableName, 'created', payload),
28
+ removed: createStoreSignal(tableName, 'removed', payload),
29
+ updated: createStoreSignal(tableName, 'updated', payload),
30
+ });
31
+ export const composeStoreSignalId = (scope, tableName, event) => `${scope}:${tableName}.${event}`;
32
+ const bindTableSignals = (scope, table) => Object.freeze({
33
+ created: cloneSignalWithId(table.signals.created, composeStoreSignalId(scope, table.name, 'created')),
34
+ removed: cloneSignalWithId(table.signals.removed, composeStoreSignalId(scope, table.name, 'removed')),
35
+ updated: cloneSignalWithId(table.signals.updated, composeStoreSignalId(scope, table.name, 'updated')),
36
+ });
37
+ const collectStoreSignals = (normalized, tableNames) => Object.freeze(tableNames.flatMap((name) => {
38
+ const table = normalized[name];
39
+ return table === undefined
40
+ ? []
41
+ : [table.signals.created, table.signals.updated, table.signals.removed];
42
+ }));
43
+ /**
44
+ * Verifies that a resource id is safe to compose into a scoped signal id.
45
+ *
46
+ * Scoped signal ids are matched by the `SCOPED_SIGNAL_ID` pattern in
47
+ * `@ontrails/core` (`^[^:\s]+:[^:.\s]+(?:\.[^:.\s]+)+$`). A resource id used
48
+ * as the scope segment must therefore be a non-empty string that contains
49
+ * neither `":"` nor any whitespace.
50
+ */
51
+ export const isValidResourceId = (resourceId) => resourceId.length > 0 && !resourceId.includes(':') && !/\s/.test(resourceId);
52
+ const assertValidScope = (scope) => {
53
+ if (!isValidResourceId(scope)) {
54
+ throw new ValidationError(`Store resource id "${scope}" is invalid: must be a non-empty string with no ":" characters and no whitespace.`);
55
+ }
56
+ };
57
+ export const bindStoreDefinition = (definition, scope) => {
58
+ assertValidScope(scope);
59
+ const tableNames = definition.tableNames;
60
+ const tables = {};
61
+ for (const tableName of tableNames) {
62
+ const table = definition.tables[tableName];
63
+ if (table === undefined) {
64
+ continue;
65
+ }
66
+ tables[tableName] = Object.freeze({
67
+ ...table,
68
+ signals: bindTableSignals(scope, table),
69
+ });
70
+ }
71
+ const get = 'get' in definition && typeof definition.get === 'function'
72
+ ? (name) => tables[name]
73
+ : undefined;
74
+ return Object.freeze({
75
+ ...definition,
76
+ ...(get ? { get } : {}),
77
+ signals: collectStoreSignals(tables, tableNames),
78
+ tables: Object.freeze(tables),
79
+ });
80
+ };
81
+ //# sourceMappingURL=signal-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal-identity.js","sourceRoot":"","sources":["../../src/internal/signal-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { connectJsonFile, jsonFile, jsonFile as store } from './runtime.js';
2
+ export type { JsonFileConnection, JsonFileStoreOptions, JsonFileStoreResource, } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/jsonfile/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,IAAI,KAAK,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { connectJsonFile, jsonFile, jsonFile as store } from './runtime.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/jsonfile/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,IAAI,KAAK,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { AnyStoreDefinition } from '../types.js';
2
+ import type { JsonFileConnection, JsonFileStoreOptions, JsonFileStoreResource } from './types.js';
3
+ /**
4
+ * Connect to a JSON-file-backed store.
5
+ *
6
+ * Creates one `<tableName>.json` file per table in the target directory.
7
+ * Each file holds a JSON array of entities. The entire dataset is loaded
8
+ * into memory on connect and flushed to disk on every write.
9
+ */
10
+ export declare const connectJsonFile: <TStore extends AnyStoreDefinition>(definition: TStore, options: JsonFileStoreOptions<TStore>) => Promise<JsonFileConnection<TStore["tables"]>>;
11
+ /**
12
+ * Create a JSON-file-backed store resource.
13
+ *
14
+ * Returns a `Resource` that can be registered in a topo and resolved from
15
+ * trail context via `db.from(ctx)`.
16
+ */
17
+ export declare const jsonFile: <TStore extends AnyStoreDefinition>(definition: TStore, options: JsonFileStoreOptions<TStore>) => JsonFileStoreResource<TStore["tables"]>;
18
+ export { jsonFile as store };
19
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/jsonfile/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EASnB,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAglBpB;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAU,MAAM,SAAS,kBAAkB,EACrE,YAAY,MAAM,EAClB,SAAS,oBAAoB,CAAC,MAAM,CAAC,KACpC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAmB9C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,SAAS,kBAAkB,EACxD,YAAY,MAAM,EAClB,SAAS,oBAAoB,CAAC,MAAM,CAAC,KACpC,qBAAqB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAgDxC,CAAC;AAEF,OAAO,EAAE,QAAQ,IAAI,KAAK,EAAE,CAAC"}