@ontrails/store 1.0.0-beta.32 → 1.0.0-beta.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/package.json +2 -2
- package/src/trails/crud.ts +67 -11
- package/src/trails/index.ts +1 -0
- package/src/trails/reconcile.ts +14 -1
- package/src/trails/sync.ts +20 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @ontrails/store
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.39
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`f7ec225`](https://github.com/outfitter-dev/trails/commit/f7ec225c01482f8fb55afd174add3d961a63171b): `sync()` gains the factory-contract options `crud()` and `reconcile()` received in TRL-1195: a `permit` option declared on the produced trail, and per-endpoint `contour` options on `SyncEndpoint` so a `crud()` bundle's table contour can be shared instead of colliding as a duplicate registration at `topo()`.
|
|
8
|
+
- [`5a38c73`](https://github.com/outfitter-dev/trails/commit/5a38c73092f81612769be4b44944d828c3436e07): Complete the store factory trail contracts (TRL-1195, absorbing TRL-1177 and TRL-1178). `crud()` gains `permit` (applied to every produced trail) and `permits` (per-operation overrides, so destroy trails satisfy permit governance) plus a `contour` option, and the returned tuple now exposes the table contour it registered as a `contour` property. `reconcile()` gains `permit` and accepts a shared `contour` instance, so crud + reconcile on one table register cleanly in a single `topo()` instead of colliding on a duplicate contour name. `TableContour` is exported from `@ontrails/store/trails`. Consuming apps no longer need to post-process factory trails to attach permits or strip contours.
|
|
9
|
+
|
|
10
|
+
## 1.0.0-beta.38
|
|
11
|
+
|
|
12
|
+
## 1.0.0-beta.37
|
|
13
|
+
|
|
14
|
+
## 1.0.0-beta.36
|
|
15
|
+
|
|
16
|
+
## 1.0.0-beta.35
|
|
17
|
+
|
|
18
|
+
## 1.0.0-beta.34
|
|
19
|
+
|
|
20
|
+
## 1.0.0-beta.33
|
|
21
|
+
|
|
3
22
|
## 1.0.0-beta.32
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/store",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.39",
|
|
4
4
|
"files": [
|
|
5
5
|
"src/**/*.ts",
|
|
6
6
|
"!src/**/__tests__/**",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
29
|
+
"@ontrails/core": "^1.0.0-beta.39"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"zod": "^4.3.5"
|
package/src/trails/crud.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Implementation,
|
|
3
|
+
PermitRequirement,
|
|
4
|
+
Resource,
|
|
5
|
+
Trail,
|
|
6
|
+
} from '@ontrails/core';
|
|
2
7
|
import { deriveTrail } from '@ontrails/core/trails';
|
|
3
8
|
import type {
|
|
4
9
|
DeriveTrailInput,
|
|
@@ -137,7 +142,15 @@ export type CrudTrails<TTable extends AnyStoreTable> = readonly [
|
|
|
137
142
|
update: UpdateTrailOf<TTable>,
|
|
138
143
|
remove: DeleteTrailOf<TTable>,
|
|
139
144
|
list: ListTrailOf<TTable>,
|
|
140
|
-
]
|
|
145
|
+
] & {
|
|
146
|
+
/**
|
|
147
|
+
* The table contour the factory registered on its trails. Pass it to
|
|
148
|
+
* `reconcile({ contour })` (or other factories over the same table) so
|
|
149
|
+
* the topo sees one shared contour instance instead of rejecting two
|
|
150
|
+
* same-named rebuilds as duplicates.
|
|
151
|
+
*/
|
|
152
|
+
readonly contour: TableContour<TTable>;
|
|
153
|
+
};
|
|
141
154
|
|
|
142
155
|
export interface CrudBlazeOverrides<TTable extends AnyStoreTable> {
|
|
143
156
|
readonly create?: Implementation<InsertOf<TTable>, EntityOf<TTable>>;
|
|
@@ -152,6 +165,24 @@ export interface CrudBlazeOverrides<TTable extends AnyStoreTable> {
|
|
|
152
165
|
|
|
153
166
|
export interface CrudOptions<TTable extends AnyStoreTable> {
|
|
154
167
|
readonly blaze?: CrudBlazeOverrides<TTable>;
|
|
168
|
+
/**
|
|
169
|
+
* Existing table contour to register on the produced trails. When
|
|
170
|
+
* omitted, the factory builds one from the table. Pass a shared
|
|
171
|
+
* instance when another factory (e.g. `reconcile()`) covers the same
|
|
172
|
+
* table so `topo()` sees a single contour registration.
|
|
173
|
+
*/
|
|
174
|
+
readonly contour?: TableContour<TTable>;
|
|
175
|
+
/**
|
|
176
|
+
* Permit requirement declared on every produced trail. Factory trails
|
|
177
|
+
* carry authored defaults like any hand-written trail; per-operation
|
|
178
|
+
* entries in `permits` override this baseline.
|
|
179
|
+
*/
|
|
180
|
+
readonly permit?: PermitRequirement;
|
|
181
|
+
/**
|
|
182
|
+
* Per-operation permit overrides. At minimum, destroy-intent trails
|
|
183
|
+
* (`delete`) need a declaration to satisfy permit governance.
|
|
184
|
+
*/
|
|
185
|
+
readonly permits?: Partial<Record<CrudOperation, PermitRequirement>>;
|
|
155
186
|
}
|
|
156
187
|
|
|
157
188
|
interface InternalCrudBlazeOverrides<TTable extends AnyStoreTable> {
|
|
@@ -179,6 +210,9 @@ interface InternalCrudBlazeOverrides<TTable extends AnyStoreTable> {
|
|
|
179
210
|
|
|
180
211
|
interface InternalCrudOptions<TTable extends AnyStoreTable> {
|
|
181
212
|
readonly blaze?: InternalCrudBlazeOverrides<TTable>;
|
|
213
|
+
readonly contour?: TableContour<TTable>;
|
|
214
|
+
readonly permit?: PermitRequirement;
|
|
215
|
+
readonly permits?: Partial<Record<CrudOperation, PermitRequirement>>;
|
|
182
216
|
}
|
|
183
217
|
|
|
184
218
|
const normalizeExampleForOutput = <TInput, TOutput>(
|
|
@@ -225,6 +259,7 @@ const finalizeTrail = <TInput, TOutput>(
|
|
|
225
259
|
readonly blaze?: Implementation<TInput, TOutput> | undefined;
|
|
226
260
|
readonly output?: z.ZodType<TOutput> | undefined;
|
|
227
261
|
readonly pattern?: string | undefined;
|
|
262
|
+
readonly permit?: PermitRequirement | undefined;
|
|
228
263
|
} = {}
|
|
229
264
|
): Trail<TInput, TOutput> =>
|
|
230
265
|
Object.freeze({
|
|
@@ -237,6 +272,7 @@ const finalizeTrail = <TInput, TOutput>(
|
|
|
237
272
|
output: options.output,
|
|
238
273
|
}),
|
|
239
274
|
...(options.pattern === undefined ? {} : { pattern: options.pattern }),
|
|
275
|
+
...(options.permit === undefined ? {} : { permit: options.permit }),
|
|
240
276
|
}) as Trail<TInput, TOutput>;
|
|
241
277
|
|
|
242
278
|
const deriveCrudBaseTrails = <
|
|
@@ -244,9 +280,9 @@ const deriveCrudBaseTrails = <
|
|
|
244
280
|
TConnection extends CrudConnection<TTable>,
|
|
245
281
|
>(
|
|
246
282
|
table: TTable,
|
|
247
|
-
resource: Resource<TConnection
|
|
283
|
+
resource: Resource<TConnection>,
|
|
284
|
+
entityContour: TableContour<TTable>
|
|
248
285
|
): InternalCrudBaseTrails<TTable> => {
|
|
249
|
-
const entityContour = createTableContour(table);
|
|
250
286
|
// Narrow the store's `readonly string[]` to the contour's typed field-key
|
|
251
287
|
// array so `deriveTrail`'s `TGenerated` generic picks up the precise
|
|
252
288
|
// key-of shape that `CreateInputOf<Contour, TGenerated>` expects. The
|
|
@@ -282,38 +318,51 @@ const deriveCrudBaseTrails = <
|
|
|
282
318
|
|
|
283
319
|
const buildCrudTrails = <TTable extends AnyStoreTable>(
|
|
284
320
|
baseTrails: InternalCrudBaseTrails<TTable>,
|
|
285
|
-
|
|
321
|
+
options: InternalCrudOptions<TTable>,
|
|
286
322
|
entityOutput: z.ZodType<DerivedOutput<TTable, 'create'>>,
|
|
287
323
|
listOutput: z.ZodType<DerivedOutput<TTable, 'list'>>
|
|
288
|
-
): InternalCrudTrails<TTable> =>
|
|
289
|
-
|
|
324
|
+
): InternalCrudTrails<TTable> => {
|
|
325
|
+
const overrides = options.blaze ?? {};
|
|
326
|
+
const permitFor = (operation: CrudOperation): PermitRequirement | undefined =>
|
|
327
|
+
options.permits?.[operation] ?? options.permit;
|
|
328
|
+
|
|
329
|
+
return Object.freeze([
|
|
290
330
|
finalizeTrail(baseTrails.createBase, {
|
|
291
331
|
...(overrides.create === undefined ? {} : { blaze: overrides.create }),
|
|
292
332
|
output: entityOutput,
|
|
293
333
|
pattern: 'crud',
|
|
334
|
+
permit: permitFor('create'),
|
|
294
335
|
}),
|
|
295
336
|
finalizeTrail(baseTrails.readBase, {
|
|
296
337
|
...(overrides.read === undefined ? {} : { blaze: overrides.read }),
|
|
297
338
|
output: entityOutput,
|
|
298
339
|
pattern: 'crud',
|
|
340
|
+
permit: permitFor('read'),
|
|
299
341
|
}),
|
|
300
342
|
finalizeTrail(baseTrails.updateBase, {
|
|
301
343
|
...(overrides.update === undefined ? {} : { blaze: overrides.update }),
|
|
302
344
|
output: entityOutput,
|
|
303
345
|
pattern: 'crud',
|
|
346
|
+
permit: permitFor('update'),
|
|
304
347
|
}),
|
|
305
348
|
overrides.delete === undefined
|
|
306
|
-
? finalizeTrail(baseTrails.deleteBase, {
|
|
349
|
+
? finalizeTrail(baseTrails.deleteBase, {
|
|
350
|
+
pattern: 'crud',
|
|
351
|
+
permit: permitFor('delete'),
|
|
352
|
+
})
|
|
307
353
|
: finalizeTrail(baseTrails.deleteBase, {
|
|
308
354
|
blaze: overrides.delete,
|
|
309
355
|
pattern: 'crud',
|
|
356
|
+
permit: permitFor('delete'),
|
|
310
357
|
}),
|
|
311
358
|
finalizeTrail(baseTrails.listBase, {
|
|
312
359
|
...(overrides.list === undefined ? {} : { blaze: overrides.list }),
|
|
313
360
|
output: listOutput,
|
|
314
361
|
pattern: 'crud',
|
|
362
|
+
permit: permitFor('list'),
|
|
315
363
|
}),
|
|
316
364
|
]) as InternalCrudTrails<TTable>;
|
|
365
|
+
};
|
|
317
366
|
|
|
318
367
|
/**
|
|
319
368
|
* Produce the standard CRUD trail tuple for one normalized store table.
|
|
@@ -340,8 +389,8 @@ export function crud<
|
|
|
340
389
|
resource: Resource<TConnection>,
|
|
341
390
|
options: InternalCrudOptions<TTable> = {}
|
|
342
391
|
) {
|
|
343
|
-
const
|
|
344
|
-
const baseTrails = deriveCrudBaseTrails(table, resource);
|
|
392
|
+
const entityContour = options.contour ?? createTableContour(table);
|
|
393
|
+
const baseTrails = deriveCrudBaseTrails(table, resource, entityContour);
|
|
345
394
|
// Narrow `table.schema` (typed `StoreObjectSchema`, which is
|
|
346
395
|
// `z.ZodObject<Record<string, z.ZodType>>`) to a ZodObject keyed by the
|
|
347
396
|
// concrete shape so its `z.output` unifies with the contour-derived
|
|
@@ -352,5 +401,12 @@ export function crud<
|
|
|
352
401
|
const listOutput: z.ZodType<DerivedOutput<TTable, 'list'>> =
|
|
353
402
|
entitySchema.array();
|
|
354
403
|
|
|
355
|
-
|
|
404
|
+
const trails = buildCrudTrails(baseTrails, options, entityOutput, listOutput);
|
|
405
|
+
// Expose the registered contour so other factories over the same table
|
|
406
|
+
// (reconcile, sync) can share the instance instead of rebuilding it.
|
|
407
|
+
return Object.freeze(
|
|
408
|
+
Object.assign([...trails], { contour: entityContour })
|
|
409
|
+
) as unknown as InternalCrudTrails<TTable> & {
|
|
410
|
+
readonly contour: TableContour<TTable>;
|
|
411
|
+
};
|
|
356
412
|
}
|
package/src/trails/index.ts
CHANGED
package/src/trails/reconcile.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ConflictError, Result, ValidationError, trail } from '@ontrails/core';
|
|
|
2
2
|
import type {
|
|
3
3
|
AnySignal,
|
|
4
4
|
Detour,
|
|
5
|
+
PermitRequirement,
|
|
5
6
|
Resource,
|
|
6
7
|
Trail,
|
|
7
8
|
TrailContext,
|
|
@@ -18,6 +19,7 @@ import type {
|
|
|
18
19
|
} from '../types.js';
|
|
19
20
|
import { versionFieldName } from '../store.js';
|
|
20
21
|
import { createTableContour, mapStoreTrailError } from './utils.js';
|
|
22
|
+
import type { TableContour } from './utils.js';
|
|
21
23
|
|
|
22
24
|
type ReconcileConnection<TTable extends AnyStoreTable> = Readonly<
|
|
23
25
|
Record<TTable['name'], StoreAccessor<TTable>>
|
|
@@ -39,9 +41,19 @@ export interface ReconcileOptions<
|
|
|
39
41
|
TTable extends AnyStoreTable,
|
|
40
42
|
TConnection extends ReconcileConnection<TTable>,
|
|
41
43
|
> {
|
|
44
|
+
/**
|
|
45
|
+
* Existing table contour to register on the reconcile trail. Pass the
|
|
46
|
+
* contour a `crud()` call over the same table exposes (its `contour`
|
|
47
|
+
* property) so `topo()` sees one shared instance instead of rejecting
|
|
48
|
+
* two same-named rebuilds as duplicates. When omitted, the factory
|
|
49
|
+
* builds its own.
|
|
50
|
+
*/
|
|
51
|
+
readonly contour?: TableContour<TTable>;
|
|
42
52
|
readonly description?: string;
|
|
43
53
|
readonly id?: string;
|
|
44
54
|
readonly on?: readonly (AnySignal | string)[];
|
|
55
|
+
/** Permit requirement declared on the reconcile trail. */
|
|
56
|
+
readonly permit?: PermitRequirement;
|
|
45
57
|
readonly resource: Resource<TConnection>;
|
|
46
58
|
readonly strategy?: ReconcileStrategy<TTable>;
|
|
47
59
|
readonly table: TTable;
|
|
@@ -260,7 +272,7 @@ export const reconcile = <
|
|
|
260
272
|
}
|
|
261
273
|
|
|
262
274
|
const id = options.id ?? `${options.table.name}.reconcile`;
|
|
263
|
-
const entityContour = createTableContour(options.table);
|
|
275
|
+
const entityContour = options.contour ?? createTableContour(options.table);
|
|
264
276
|
const strategy = options.strategy ?? 'last-write-wins';
|
|
265
277
|
|
|
266
278
|
return trail(id, {
|
|
@@ -276,6 +288,7 @@ export const reconcile = <
|
|
|
276
288
|
on: options.on,
|
|
277
289
|
output: options.table.schema as unknown as z.ZodType<EntityOf<TTable>>,
|
|
278
290
|
pattern: 'reconcile',
|
|
291
|
+
...(options.permit === undefined ? {} : { permit: options.permit }),
|
|
279
292
|
resources: [options.resource],
|
|
280
293
|
});
|
|
281
294
|
};
|
package/src/trails/sync.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InternalError, NotFoundError, Result, trail } from '@ontrails/core';
|
|
2
2
|
import type {
|
|
3
3
|
AnySignal,
|
|
4
|
+
PermitRequirement,
|
|
4
5
|
Resource,
|
|
5
6
|
Trail,
|
|
6
7
|
TrailContext,
|
|
@@ -17,6 +18,7 @@ import type {
|
|
|
17
18
|
UpsertOf,
|
|
18
19
|
} from '../types.js';
|
|
19
20
|
import { createTableContour, mapStoreTrailError } from './utils.js';
|
|
21
|
+
import type { TableContour } from './utils.js';
|
|
20
22
|
|
|
21
23
|
type IdentityInputOf<TTable extends AnyStoreTable> = Readonly<
|
|
22
24
|
Record<Extract<TTable['identity'], string>, StoreIdentifierOf<TTable>>
|
|
@@ -34,6 +36,14 @@ export interface SyncEndpoint<
|
|
|
34
36
|
TTable extends AnyStoreTable,
|
|
35
37
|
TConnection extends SourceConnection<TTable> | TargetConnection<TTable>,
|
|
36
38
|
> {
|
|
39
|
+
/**
|
|
40
|
+
* Existing table contour to register on the produced trail for this
|
|
41
|
+
* endpoint. Pass the contour a `crud()` bundle over the same table
|
|
42
|
+
* exposes (its `contour` property) so `topo()` sees one shared
|
|
43
|
+
* instance instead of rejecting two same-named rebuilds as
|
|
44
|
+
* duplicates. When omitted, the factory builds one from the table.
|
|
45
|
+
*/
|
|
46
|
+
readonly contour?: TableContour<TTable>;
|
|
37
47
|
readonly resource: Resource<TConnection>;
|
|
38
48
|
readonly table: TTable;
|
|
39
49
|
}
|
|
@@ -56,6 +66,11 @@ export interface SyncOptions<
|
|
|
56
66
|
readonly from: SyncEndpoint<TSourceTable, TSourceConnection>;
|
|
57
67
|
readonly id?: string;
|
|
58
68
|
readonly on?: readonly (AnySignal | string)[];
|
|
69
|
+
/**
|
|
70
|
+
* Permit requirement declared on the produced trail. Factory trails
|
|
71
|
+
* carry authored defaults like any hand-written trail.
|
|
72
|
+
*/
|
|
73
|
+
readonly permit?: PermitRequirement;
|
|
59
74
|
readonly to: SyncEndpoint<TTargetTable, TTargetConnection>;
|
|
60
75
|
readonly transform?: SyncTransform<TSourceTable, TTargetTable>;
|
|
61
76
|
}
|
|
@@ -175,8 +190,10 @@ export const sync = <
|
|
|
175
190
|
>
|
|
176
191
|
): Trail<IdentityInputOf<TSourceTable>, EntityOf<TTargetTable>> => {
|
|
177
192
|
const id = options.id ?? `${options.to.table.name}.sync`;
|
|
178
|
-
const sourceContour =
|
|
179
|
-
|
|
193
|
+
const sourceContour =
|
|
194
|
+
options.from.contour ?? createTableContour(options.from.table);
|
|
195
|
+
const targetContour =
|
|
196
|
+
options.to.contour ?? createTableContour(options.to.table);
|
|
180
197
|
|
|
181
198
|
return trail(id, {
|
|
182
199
|
// oxlint-disable-next-line max-statements -- sync blaze reads more clearly as one try/catch with schema validation, transform, and accessor call inline
|
|
@@ -246,6 +263,7 @@ export const sync = <
|
|
|
246
263
|
EntityOf<TTargetTable>
|
|
247
264
|
>,
|
|
248
265
|
pattern: 'sync',
|
|
266
|
+
...(options.permit === undefined ? {} : { permit: options.permit }),
|
|
249
267
|
resources: [options.from.resource, options.to.resource],
|
|
250
268
|
});
|
|
251
269
|
};
|