@ontrails/core 1.0.0-beta.39 → 1.0.0-beta.41

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.
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- import type { AnyContour } from '../contour.js';
3
+ import type { AnyEntity } from '../entity.js';
4
4
  import {
5
5
  DerivationError,
6
6
  InternalError,
@@ -25,96 +25,96 @@ export type DeriveTrailOperation =
25
25
  | 'delete'
26
26
  | 'list';
27
27
 
28
- type ContourInput<TContour extends AnyContour> = z.input<TContour>;
29
- type ContourOutput<TContour extends AnyContour> = z.output<TContour>;
30
- type ContourFieldKey<TContour extends AnyContour> = Extract<
31
- keyof ContourOutput<TContour>,
28
+ type EntityInput<TEntity extends AnyEntity> = z.input<TEntity>;
29
+ type EntityOutput<TEntity extends AnyEntity> = z.output<TEntity>;
30
+ type EntityFieldKey<TEntity extends AnyEntity> = Extract<
31
+ keyof EntityOutput<TEntity>,
32
32
  string
33
33
  >;
34
- type IdentityKey<TContour extends AnyContour> = Extract<
35
- TContour['identity'],
36
- keyof ContourInput<TContour> & string
34
+ type IdentityKey<TEntity extends AnyEntity> = Extract<
35
+ TEntity['identity'],
36
+ keyof EntityInput<TEntity> & string
37
37
  >;
38
38
 
39
39
  type GeneratedKey<
40
- TContour extends AnyContour,
41
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined,
42
- > = TGenerated extends readonly ContourFieldKey<TContour>[]
40
+ TEntity extends AnyEntity,
41
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined,
42
+ > = TGenerated extends readonly EntityFieldKey<TEntity>[]
43
43
  ? TGenerated[number]
44
44
  : never;
45
45
 
46
46
  type CreateInputOf<
47
- TContour extends AnyContour,
48
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined,
47
+ TEntity extends AnyEntity,
48
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined,
49
49
  > = Omit<
50
- ContourInput<TContour>,
51
- Extract<GeneratedKey<TContour, TGenerated>, keyof ContourInput<TContour>>
50
+ EntityInput<TEntity>,
51
+ Extract<GeneratedKey<TEntity, TGenerated>, keyof EntityInput<TEntity>>
52
52
  >;
53
53
 
54
- type ReadInputOf<TContour extends AnyContour> = Pick<
55
- ContourInput<TContour>,
56
- IdentityKey<TContour>
54
+ type ReadInputOf<TEntity extends AnyEntity> = Pick<
55
+ EntityInput<TEntity>,
56
+ IdentityKey<TEntity>
57
57
  >;
58
58
 
59
59
  type UpdateInputOf<
60
- TContour extends AnyContour,
61
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined,
62
- > = ReadInputOf<TContour> &
63
- Partial<Omit<CreateInputOf<TContour, TGenerated>, IdentityKey<TContour>>>;
60
+ TEntity extends AnyEntity,
61
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined,
62
+ > = ReadInputOf<TEntity> &
63
+ Partial<Omit<CreateInputOf<TEntity, TGenerated>, IdentityKey<TEntity>>>;
64
64
 
65
- type ListInputOf<TContour extends AnyContour> = Partial<ContourInput<TContour>>;
65
+ type ListInputOf<TEntity extends AnyEntity> = Partial<EntityInput<TEntity>>;
66
66
 
67
67
  /**
68
- * Input shape derived for one operation against one contour.
68
+ * Input shape derived for one operation against one entity.
69
69
  */
70
70
  export type DeriveTrailInput<
71
- TContour extends AnyContour,
71
+ TEntity extends AnyEntity,
72
72
  TOperation extends DeriveTrailOperation,
73
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined =
74
- | readonly ContourFieldKey<TContour>[]
73
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined =
74
+ | readonly EntityFieldKey<TEntity>[]
75
75
  | undefined,
76
76
  > = TOperation extends 'create'
77
- ? CreateInputOf<TContour, TGenerated>
77
+ ? CreateInputOf<TEntity, TGenerated>
78
78
  : TOperation extends 'read' | 'delete'
79
- ? ReadInputOf<TContour>
79
+ ? ReadInputOf<TEntity>
80
80
  : TOperation extends 'update'
81
- ? UpdateInputOf<TContour, TGenerated>
82
- : ListInputOf<TContour>;
81
+ ? UpdateInputOf<TEntity, TGenerated>
82
+ : ListInputOf<TEntity>;
83
83
 
84
84
  /**
85
- * Output shape derived for one operation against one contour.
85
+ * Output shape derived for one operation against one entity.
86
86
  */
87
87
  export type DeriveTrailOutput<
88
- TContour extends AnyContour,
88
+ TEntity extends AnyEntity,
89
89
  TOperation extends DeriveTrailOperation,
90
90
  > = TOperation extends 'delete'
91
91
  ? undefined
92
92
  : TOperation extends 'list'
93
- ? ContourOutput<TContour>[]
94
- : ContourOutput<TContour>;
93
+ ? EntityOutput<TEntity>[]
94
+ : EntityOutput<TEntity>;
95
95
 
96
96
  /**
97
97
  * Extra authored data accepted by `deriveTrail()` in addition to the
98
98
  * operation-derived contract pieces.
99
99
  *
100
- * `blaze` is optional for single-resource calls: when omitted, the helper
101
- * synthesizes a default blaze that delegates to the resource's accessor via
100
+ * `implementation` is optional for single-resource calls: when omitted, the helper
101
+ * synthesizes a default implementation that delegates to the resource's accessor via
102
102
  * the structural {@link StoreAccessorProtocol}. When multiple resources are
103
- * declared, an explicit `blaze` is required.
103
+ * declared, an explicit `implementation` is required.
104
104
  */
105
105
  export interface DeriveTrailSpec<
106
- TContour extends AnyContour,
106
+ TEntity extends AnyEntity,
107
107
  TOperation extends DeriveTrailOperation,
108
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined =
109
- | readonly ContourFieldKey<TContour>[]
108
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined =
109
+ | readonly EntityFieldKey<TEntity>[]
110
110
  | undefined,
111
111
  > extends Omit<
112
112
  TrailSpec<
113
- DeriveTrailInput<TContour, TOperation, TGenerated>,
114
- DeriveTrailOutput<TContour, TOperation>
113
+ DeriveTrailInput<TEntity, TOperation, TGenerated>,
114
+ DeriveTrailOutput<TEntity, TOperation>
115
115
  >,
116
- | 'blaze'
117
- | 'contours'
116
+ | 'implementation'
117
+ | 'entities'
118
118
  | 'examples'
119
119
  | 'input'
120
120
  | 'intent'
@@ -123,12 +123,12 @@ export interface DeriveTrailSpec<
123
123
  > {
124
124
  /**
125
125
  * Implementation of the trail. Optional for single-resource calls: when
126
- * omitted, the helper derives a default blaze from the resource accessor
126
+ * omitted, the helper derives a default implementation from the resource accessor
127
127
  * for standard CRUD operations.
128
128
  */
129
- readonly blaze?: Implementation<
130
- DeriveTrailInput<TContour, TOperation, TGenerated>,
131
- DeriveTrailOutput<TContour, TOperation>
129
+ readonly implementation?: Implementation<
130
+ DeriveTrailInput<TEntity, TOperation, TGenerated>,
131
+ DeriveTrailOutput<TEntity, TOperation>
132
132
  >;
133
133
  /**
134
134
  * Server-managed fields that should not be writable through derived create
@@ -137,8 +137,8 @@ export interface DeriveTrailSpec<
137
137
  readonly generated?: TGenerated;
138
138
  /**
139
139
  * Resource dependency declared on the derived trail. Pass a single
140
- * resource for default-blaze synthesis, or an array for multi-resource
141
- * trails that must provide an explicit `blaze`.
140
+ * resource for default-implementation synthesis, or an array for multi-resource
141
+ * trails that must provide an explicit `implementation`.
142
142
  */
143
143
  readonly resource: AnyResource | readonly AnyResource[];
144
144
  }
@@ -212,75 +212,75 @@ const normalizeResources = (
212
212
  ): readonly AnyResource[] =>
213
213
  Object.freeze(Array.isArray(resource) ? [...resource] : [resource]);
214
214
 
215
- const identityInputSchema = <TContour extends AnyContour>(
216
- contour: TContour
217
- ): z.ZodType<ReadInputOf<TContour>> =>
218
- pickFields(contour, [contour.identity]) as unknown as z.ZodType<
219
- ReadInputOf<TContour>
215
+ const identityInputSchema = <TEntity extends AnyEntity>(
216
+ entity: TEntity
217
+ ): z.ZodType<ReadInputOf<TEntity>> =>
218
+ pickFields(entity, [entity.identity]) as unknown as z.ZodType<
219
+ ReadInputOf<TEntity>
220
220
  >;
221
221
 
222
222
  const createInputSchema = <
223
- TContour extends AnyContour,
224
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined,
223
+ TEntity extends AnyEntity,
224
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined,
225
225
  >(
226
- contour: TContour,
226
+ entity: TEntity,
227
227
  generated: readonly string[]
228
- ): z.ZodType<CreateInputOf<TContour, TGenerated>> =>
229
- omitFields(contour, generated) as unknown as z.ZodType<
230
- CreateInputOf<TContour, TGenerated>
228
+ ): z.ZodType<CreateInputOf<TEntity, TGenerated>> =>
229
+ omitFields(entity, generated) as unknown as z.ZodType<
230
+ CreateInputOf<TEntity, TGenerated>
231
231
  >;
232
232
 
233
233
  const updateInputSchema = <
234
- TContour extends AnyContour,
235
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined,
234
+ TEntity extends AnyEntity,
235
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined,
236
236
  >(
237
- contour: TContour,
237
+ entity: TEntity,
238
238
  generated: readonly string[]
239
- ): z.ZodType<UpdateInputOf<TContour, TGenerated>> => {
240
- const mutableSchema = omitFields(contour, [...generated, contour.identity]);
241
- const identitySchema = asObjectSchema(identityInputSchema(contour));
239
+ ): z.ZodType<UpdateInputOf<TEntity, TGenerated>> => {
240
+ const mutableSchema = omitFields(entity, [...generated, entity.identity]);
241
+ const identitySchema = asObjectSchema(identityInputSchema(entity));
242
242
 
243
243
  return identitySchema.extend(
244
244
  toPartialSchema(mutableSchema).shape
245
- ) as unknown as z.ZodType<UpdateInputOf<TContour, TGenerated>>;
245
+ ) as unknown as z.ZodType<UpdateInputOf<TEntity, TGenerated>>;
246
246
  };
247
247
 
248
- const listInputSchema = <TContour extends AnyContour>(
249
- contour: TContour
250
- ): z.ZodType<ListInputOf<TContour>> =>
251
- toPartialSchema(contour) as unknown as z.ZodType<ListInputOf<TContour>>;
248
+ const listInputSchema = <TEntity extends AnyEntity>(
249
+ entity: TEntity
250
+ ): z.ZodType<ListInputOf<TEntity>> =>
251
+ toPartialSchema(entity) as unknown as z.ZodType<ListInputOf<TEntity>>;
252
252
 
253
253
  const deriveInputSchema = <
254
- TContour extends AnyContour,
254
+ TEntity extends AnyEntity,
255
255
  TOperation extends DeriveTrailOperation,
256
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined,
256
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined,
257
257
  >(
258
- contour: TContour,
258
+ entity: TEntity,
259
259
  operation: TOperation,
260
260
  generated: readonly string[]
261
- ): z.ZodType<DeriveTrailInput<TContour, TOperation, TGenerated>> => {
261
+ ): z.ZodType<DeriveTrailInput<TEntity, TOperation, TGenerated>> => {
262
262
  switch (operation) {
263
263
  case 'create': {
264
- return createInputSchema<TContour, TGenerated>(
265
- contour,
264
+ return createInputSchema<TEntity, TGenerated>(
265
+ entity,
266
266
  generated
267
- ) as z.ZodType<DeriveTrailInput<TContour, TOperation, TGenerated>>;
267
+ ) as z.ZodType<DeriveTrailInput<TEntity, TOperation, TGenerated>>;
268
268
  }
269
269
  case 'read':
270
270
  case 'delete': {
271
- return identityInputSchema(contour) as z.ZodType<
272
- DeriveTrailInput<TContour, TOperation, TGenerated>
271
+ return identityInputSchema(entity) as z.ZodType<
272
+ DeriveTrailInput<TEntity, TOperation, TGenerated>
273
273
  >;
274
274
  }
275
275
  case 'update': {
276
- return updateInputSchema<TContour, TGenerated>(
277
- contour,
276
+ return updateInputSchema<TEntity, TGenerated>(
277
+ entity,
278
278
  generated
279
- ) as z.ZodType<DeriveTrailInput<TContour, TOperation, TGenerated>>;
279
+ ) as z.ZodType<DeriveTrailInput<TEntity, TOperation, TGenerated>>;
280
280
  }
281
281
  case 'list': {
282
- return listInputSchema(contour) as z.ZodType<
283
- DeriveTrailInput<TContour, TOperation, TGenerated>
282
+ return listInputSchema(entity) as z.ZodType<
283
+ DeriveTrailInput<TEntity, TOperation, TGenerated>
284
284
  >;
285
285
  }
286
286
  default: {
@@ -290,28 +290,28 @@ const deriveInputSchema = <
290
290
  };
291
291
 
292
292
  const deriveOutputSchema = <
293
- TContour extends AnyContour,
293
+ TEntity extends AnyEntity,
294
294
  TOperation extends DeriveTrailOperation,
295
295
  >(
296
- contour: TContour,
296
+ entity: TEntity,
297
297
  operation: TOperation
298
- ): z.ZodType<DeriveTrailOutput<TContour, TOperation>> => {
298
+ ): z.ZodType<DeriveTrailOutput<TEntity, TOperation>> => {
299
299
  switch (operation) {
300
300
  case 'delete': {
301
301
  return z.void() as unknown as z.ZodType<
302
- DeriveTrailOutput<TContour, TOperation>
302
+ DeriveTrailOutput<TEntity, TOperation>
303
303
  >;
304
304
  }
305
305
  case 'list': {
306
- return contour.array() as unknown as z.ZodType<
307
- DeriveTrailOutput<TContour, TOperation>
306
+ return entity.array() as unknown as z.ZodType<
307
+ DeriveTrailOutput<TEntity, TOperation>
308
308
  >;
309
309
  }
310
310
  case 'create':
311
311
  case 'read':
312
312
  case 'update': {
313
- return contour as unknown as z.ZodType<
314
- DeriveTrailOutput<TContour, TOperation>
313
+ return entity as unknown as z.ZodType<
314
+ DeriveTrailOutput<TEntity, TOperation>
315
315
  >;
316
316
  }
317
317
  default: {
@@ -344,37 +344,37 @@ const omitValueFields = (
344
344
  };
345
345
 
346
346
  const formatExampleName = (
347
- contour: AnyContour,
347
+ entity: AnyEntity,
348
348
  operation: DeriveTrailOperation,
349
349
  example: ExampleRecord,
350
350
  index: number
351
351
  ): string => {
352
- const identifier = example[contour.identity];
352
+ const identifier = example[entity.identity];
353
353
  const suffix =
354
354
  identifier === undefined ? String(index + 1) : String(identifier);
355
- return `${titleCase(operation)} ${contour.name} ${suffix}`;
355
+ return `${titleCase(operation)} ${entity.name} ${suffix}`;
356
356
  };
357
357
 
358
358
  /**
359
- * Derive a single trail example from a contour fixture.
359
+ * Derive a single trail example from a entity fixture.
360
360
  *
361
361
  * @remarks
362
362
  * For `list` operations, each derived example wraps a single fixture in an
363
363
  * array (`expected: [example]`) and uses the fixture's identity as input
364
364
  * filters. This means the expected output is always a one-element array,
365
365
  * which may not match the real accessor behavior when multiple fixtures
366
- * share the same filter. A custom `blaze` with hand-authored examples is
366
+ * share the same filter. A custom `implementation` with hand-authored examples is
367
367
  * required for multi-result list assertions.
368
368
  */
369
369
  const deriveExample = (
370
- contour: AnyContour,
370
+ entity: AnyEntity,
371
371
  operation: DeriveTrailOperation,
372
372
  example: ExampleRecord,
373
373
  index: number,
374
374
  generated: readonly string[]
375
375
  ): TrailExample<unknown, unknown> => {
376
- const name = formatExampleName(contour, operation, example, index);
377
- const identity = pickValueFields(example, [contour.identity]);
376
+ const name = formatExampleName(entity, operation, example, index);
377
+ const identity = pickValueFields(example, [entity.identity]);
378
378
 
379
379
  switch (operation) {
380
380
  case 'create': {
@@ -395,7 +395,7 @@ const deriveExample = (
395
395
  return {
396
396
  expected: example,
397
397
  input: {
398
- ...omitValueFields(example, [...generated, contour.identity]),
398
+ ...omitValueFields(example, [...generated, entity.identity]),
399
399
  ...identity,
400
400
  },
401
401
  name,
@@ -421,11 +421,11 @@ const deriveExample = (
421
421
  };
422
422
 
423
423
  const deriveExamples = (
424
- contour: AnyContour,
424
+ entity: AnyEntity,
425
425
  operation: DeriveTrailOperation,
426
426
  generated: readonly string[]
427
427
  ): readonly TrailExample<unknown, unknown>[] | undefined => {
428
- if (contour.examples === undefined || contour.examples.length === 0) {
428
+ if (entity.examples === undefined || entity.examples.length === 0) {
429
429
  return undefined;
430
430
  }
431
431
 
@@ -436,17 +436,17 @@ const deriveExamples = (
436
436
  if (operation === 'list') {
437
437
  return Object.freeze([
438
438
  {
439
- expected: contour.examples,
439
+ expected: entity.examples,
440
440
  input: {},
441
- name: `${contour.name} list example`,
441
+ name: `${entity.name} list example`,
442
442
  },
443
443
  ]);
444
444
  }
445
445
 
446
446
  return Object.freeze(
447
- contour.examples.map((example, index) =>
447
+ entity.examples.map((example, index) =>
448
448
  deriveExample(
449
- contour,
449
+ entity,
450
450
  operation,
451
451
  example as ExampleRecord,
452
452
  index,
@@ -457,7 +457,7 @@ const deriveExamples = (
457
457
  };
458
458
 
459
459
  // ---------------------------------------------------------------------------
460
- // Default-blaze synthesis
460
+ // Default-implementation synthesis
461
461
  // ---------------------------------------------------------------------------
462
462
 
463
463
  type GenericAccessor = StoreAccessorProtocol<
@@ -468,7 +468,7 @@ type GenericAccessor = StoreAccessorProtocol<
468
468
  >;
469
469
 
470
470
  const wrapUnexpected = (
471
- contourName: string,
471
+ entityName: string,
472
472
  operation: DeriveTrailOperation,
473
473
  error: unknown
474
474
  ): Error => {
@@ -477,18 +477,18 @@ const wrapUnexpected = (
477
477
  }
478
478
  const cause = error instanceof Error ? error : new Error(String(error));
479
479
  return new InternalError(
480
- `deriveTrail("${contourName}.${operation}") synthesized blaze failed: ${cause.message}`,
480
+ `deriveTrail("${entityName}.${operation}") synthesized implementation failed: ${cause.message}`,
481
481
  { cause }
482
482
  );
483
483
  };
484
484
 
485
- const notFoundError = (contourName: string, id: unknown): NotFoundError =>
485
+ const notFoundError = (entityName: string, id: unknown): NotFoundError =>
486
486
  new NotFoundError(
487
- `deriveTrail("${contourName}"): entity "${String(id)}" not found`
487
+ `deriveTrail("${entityName}"): entity "${String(id)}" not found`
488
488
  );
489
489
 
490
490
  const resolveAccessor = (
491
- contour: AnyContour,
491
+ entity: AnyEntity,
492
492
  operation: DeriveTrailOperation,
493
493
  resource: AnyResource,
494
494
  ctx: TrailContext
@@ -499,52 +499,52 @@ const resolveAccessor = (
499
499
  | undefined;
500
500
  if (connection === undefined || connection === null) {
501
501
  return new InternalError(
502
- `deriveTrail("${contour.name}.${operation}"): resource "${resource.id}" produced no connection`
502
+ `deriveTrail("${entity.name}.${operation}"): resource "${resource.id}" produced no connection`
503
503
  );
504
504
  }
505
- const accessor = connection[contour.name];
505
+ const accessor = connection[entity.name];
506
506
  if (accessor === undefined) {
507
507
  return new InternalError(
508
- `deriveTrail("${contour.name}.${operation}"): resource "${resource.id}" does not expose an accessor for "${contour.name}"`
508
+ `deriveTrail("${entity.name}.${operation}"): resource "${resource.id}" does not expose an accessor for "${entity.name}"`
509
509
  );
510
510
  }
511
511
  return accessor;
512
512
  } catch (error) {
513
- return wrapUnexpected(contour.name, operation, error);
513
+ return wrapUnexpected(entity.name, operation, error);
514
514
  }
515
515
  };
516
516
 
517
- const extractIdentity = (contour: AnyContour, input: unknown): unknown => {
517
+ const extractIdentity = (entity: AnyEntity, input: unknown): unknown => {
518
518
  const record = input as Record<string, unknown>;
519
- return record[contour.identity];
519
+ return record[entity.identity];
520
520
  };
521
521
 
522
522
  const callRead = async (
523
- contour: AnyContour,
523
+ entity: AnyEntity,
524
524
  accessor: GenericAccessor,
525
525
  input: unknown
526
526
  ): Promise<Result<unknown, Error>> => {
527
527
  if (typeof accessor.get !== 'function') {
528
528
  return Result.err(
529
529
  new InternalError(
530
- `deriveTrail("${contour.name}.read"): accessor is missing a \`get\` method`
530
+ `deriveTrail("${entity.name}.read"): accessor is missing a \`get\` method`
531
531
  )
532
532
  );
533
533
  }
534
534
  try {
535
- const id = extractIdentity(contour, input);
536
- const entity = await accessor.get(id);
537
- if (entity === null || entity === undefined) {
538
- return Result.err(notFoundError(contour.name, id));
535
+ const id = extractIdentity(entity, input);
536
+ const foundEntity = await accessor.get(id);
537
+ if (foundEntity === null || foundEntity === undefined) {
538
+ return Result.err(notFoundError(entity.name, id));
539
539
  }
540
- return Result.ok(entity);
540
+ return Result.ok(foundEntity);
541
541
  } catch (error) {
542
- return Result.err(wrapUnexpected(contour.name, 'read', error));
542
+ return Result.err(wrapUnexpected(entity.name, 'read', error));
543
543
  }
544
544
  };
545
545
 
546
546
  const callCreate = async (
547
- contour: AnyContour,
547
+ entity: AnyEntity,
548
548
  accessor: GenericAccessor,
549
549
  input: unknown,
550
550
  ctx: TrailContext
@@ -560,17 +560,17 @@ const callCreate = async (
560
560
  if (typeof accessor.upsert !== 'function') {
561
561
  return Result.err(
562
562
  new InternalError(
563
- `deriveTrail("${contour.name}.create"): accessor is missing both \`insert\` and \`upsert\``
563
+ `deriveTrail("${entity.name}.create"): accessor is missing both \`insert\` and \`upsert\``
564
564
  )
565
565
  );
566
566
  }
567
567
  ctx.logger?.debug(
568
- `deriveTrail("${contour.name}.create"): accessor has no \`insert\`; falling back to \`upsert\``
568
+ `deriveTrail("${entity.name}.create"): accessor has no \`insert\`; falling back to \`upsert\``
569
569
  );
570
570
  const created = await accessor.upsert(input);
571
571
  return Result.ok(created);
572
572
  } catch (error) {
573
- return Result.err(wrapUnexpected(contour.name, 'create', error));
573
+ return Result.err(wrapUnexpected(entity.name, 'create', error));
574
574
  }
575
575
  };
576
576
 
@@ -604,7 +604,7 @@ const stripGeneratedFields = (
604
604
  * then `upsert`.
605
605
  */
606
606
  const updateViaReadAndUpsert = async (
607
- contour: AnyContour,
607
+ entity: AnyEntity,
608
608
  accessor: GenericAccessor,
609
609
  id: unknown,
610
610
  patch: Record<string, unknown>,
@@ -613,40 +613,40 @@ const updateViaReadAndUpsert = async (
613
613
  if (typeof accessor.get !== 'function') {
614
614
  return Result.err(
615
615
  new InternalError(
616
- `deriveTrail("${contour.name}.update"): accessor is missing both \`update\` and \`get\``
616
+ `deriveTrail("${entity.name}.update"): accessor is missing both \`update\` and \`get\``
617
617
  )
618
618
  );
619
619
  }
620
620
  if (typeof accessor.upsert !== 'function') {
621
621
  return Result.err(
622
622
  new InternalError(
623
- `deriveTrail("${contour.name}.update"): accessor is missing both \`update\` and \`upsert\``
623
+ `deriveTrail("${entity.name}.update"): accessor is missing both \`update\` and \`upsert\``
624
624
  )
625
625
  );
626
626
  }
627
627
  const current = await accessor.get(id);
628
628
  if (current === null || current === undefined) {
629
- return Result.err(notFoundError(contour.name, id));
629
+ return Result.err(notFoundError(entity.name, id));
630
630
  }
631
631
  const merged = stripGeneratedFields(
632
632
  { ...(current as Record<string, unknown>), ...patch },
633
633
  generated,
634
- contour.identity
634
+ entity.identity
635
635
  );
636
636
  const updated = await accessor.upsert(merged);
637
637
  return Result.ok(updated);
638
638
  };
639
639
 
640
640
  const callUpdate = async (
641
- contour: AnyContour,
641
+ entity: AnyEntity,
642
642
  accessor: GenericAccessor,
643
643
  input: unknown,
644
644
  generated: readonly string[]
645
645
  ): Promise<Result<unknown, Error>> => {
646
- const id = extractIdentity(contour, input);
646
+ const id = extractIdentity(entity, input);
647
647
  const patch = Object.fromEntries(
648
648
  Object.entries(input as Record<string, unknown>).filter(
649
- ([field]) => field !== contour.identity
649
+ ([field]) => field !== entity.identity
650
650
  )
651
651
  );
652
652
 
@@ -654,60 +654,54 @@ const callUpdate = async (
654
654
  if (typeof accessor.update === 'function') {
655
655
  const updated = await accessor.update(id, patch);
656
656
  if (updated === null || updated === undefined) {
657
- return Result.err(notFoundError(contour.name, id));
657
+ return Result.err(notFoundError(entity.name, id));
658
658
  }
659
659
  return Result.ok(updated);
660
660
  }
661
- return await updateViaReadAndUpsert(
662
- contour,
663
- accessor,
664
- id,
665
- patch,
666
- generated
667
- );
661
+ return await updateViaReadAndUpsert(entity, accessor, id, patch, generated);
668
662
  } catch (error) {
669
- return Result.err(wrapUnexpected(contour.name, 'update', error));
663
+ return Result.err(wrapUnexpected(entity.name, 'update', error));
670
664
  }
671
665
  };
672
666
 
673
667
  const callDelete = async (
674
- contour: AnyContour,
668
+ entity: AnyEntity,
675
669
  accessor: GenericAccessor,
676
670
  input: unknown
677
671
  ): Promise<Result<undefined, Error>> => {
678
672
  if (typeof accessor.remove !== 'function') {
679
673
  return Result.err(
680
674
  new InternalError(
681
- `deriveTrail("${contour.name}.delete"): accessor is missing a \`remove\` method`
675
+ `deriveTrail("${entity.name}.delete"): accessor is missing a \`remove\` method`
682
676
  )
683
677
  );
684
678
  }
685
679
  try {
686
- const id = extractIdentity(contour, input);
680
+ const id = extractIdentity(entity, input);
687
681
  await accessor.remove(id);
688
682
  // `{ deleted: false }` is a no-op on an absent row, not an error —
689
683
  // matches the accessor's documented semantic.
690
684
  return Result.ok();
691
685
  } catch (error) {
692
- return Result.err(wrapUnexpected(contour.name, 'delete', error));
686
+ return Result.err(wrapUnexpected(entity.name, 'delete', error));
693
687
  }
694
688
  };
695
689
 
696
690
  /**
697
691
  * Default `list` synthesis passes the entire input as the filter bag. The
698
- * derived input type is `Partial<ContourInput>` which matches the accessor's
692
+ * derived input type is `Partial<EntityInput>` which matches the accessor's
699
693
  * filter shape field-for-field. Pagination controls are not derived — callers
700
- * that need pagination must provide an explicit blaze.
694
+ * that need pagination must provide an explicit implementation.
701
695
  */
702
696
  const callList = async (
703
- contour: AnyContour,
697
+ entity: AnyEntity,
704
698
  accessor: GenericAccessor,
705
699
  input: unknown
706
700
  ): Promise<Result<unknown[], Error>> => {
707
701
  if (typeof accessor.list !== 'function') {
708
702
  return Result.err(
709
703
  new InternalError(
710
- `deriveTrail("${contour.name}.list"): accessor is missing a \`list\` method`
704
+ `deriveTrail("${entity.name}.list"): accessor is missing a \`list\` method`
711
705
  )
712
706
  );
713
707
  }
@@ -715,43 +709,43 @@ const callList = async (
715
709
  const listed = await accessor.list(input);
716
710
  return Result.ok([...listed]);
717
711
  } catch (error) {
718
- return Result.err(wrapUnexpected(contour.name, 'list', error));
712
+ return Result.err(wrapUnexpected(entity.name, 'list', error));
719
713
  }
720
714
  };
721
715
 
722
- const synthesizeDefaultBlaze = <
723
- TContour extends AnyContour,
716
+ const synthesizeDefaultImplementation = <
717
+ TEntity extends AnyEntity,
724
718
  TOperation extends DeriveTrailOperation,
725
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined,
719
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined,
726
720
  >(
727
- contour: TContour,
721
+ entity: TEntity,
728
722
  operation: TOperation,
729
723
  resource: AnyResource,
730
724
  generated: readonly string[]
731
725
  ): Implementation<
732
- DeriveTrailInput<TContour, TOperation, TGenerated>,
733
- DeriveTrailOutput<TContour, TOperation>
726
+ DeriveTrailInput<TEntity, TOperation, TGenerated>,
727
+ DeriveTrailOutput<TEntity, TOperation>
734
728
  > => {
735
729
  const impl: Implementation<unknown, unknown> = (input, ctx) => {
736
- const accessor = resolveAccessor(contour, operation, resource, ctx);
730
+ const accessor = resolveAccessor(entity, operation, resource, ctx);
737
731
  if (accessor instanceof Error) {
738
732
  return Promise.resolve(Result.err(accessor));
739
733
  }
740
734
  switch (operation) {
741
735
  case 'create': {
742
- return callCreate(contour, accessor, input, ctx);
736
+ return callCreate(entity, accessor, input, ctx);
743
737
  }
744
738
  case 'read': {
745
- return callRead(contour, accessor, input);
739
+ return callRead(entity, accessor, input);
746
740
  }
747
741
  case 'update': {
748
- return callUpdate(contour, accessor, input, generated);
742
+ return callUpdate(entity, accessor, input, generated);
749
743
  }
750
744
  case 'delete': {
751
- return callDelete(contour, accessor, input);
745
+ return callDelete(entity, accessor, input);
752
746
  }
753
747
  case 'list': {
754
- return callList(contour, accessor, input);
748
+ return callList(entity, accessor, input);
755
749
  }
756
750
  default: {
757
751
  return unsupportedOperation(operation);
@@ -760,83 +754,82 @@ const synthesizeDefaultBlaze = <
760
754
  };
761
755
 
762
756
  return impl as Implementation<
763
- DeriveTrailInput<TContour, TOperation, TGenerated>,
764
- DeriveTrailOutput<TContour, TOperation>
757
+ DeriveTrailInput<TEntity, TOperation, TGenerated>,
758
+ DeriveTrailOutput<TEntity, TOperation>
765
759
  >;
766
760
  };
767
761
 
768
762
  /**
769
- * Mechanically project one CRUD-shaped trail from a contour declaration.
763
+ * Mechanically project one CRUD-shaped trail from a entity declaration.
770
764
  *
771
- * When `spec.blaze` is omitted and the call declares a single resource, the
772
- * helper derives a default blaze that dispatches to the resource accessor
765
+ * When `spec.implementation` is omitted and the call declares a single resource, the
766
+ * helper derives a default implementation that dispatches to the resource accessor
773
767
  * through the structural {@link StoreAccessorProtocol}. Multi-resource calls
774
- * must supply an explicit blaze and are rejected with {@link DerivationError}
768
+ * must supply an explicit implementation and are rejected with {@link DerivationError}
775
769
  * at construction time when they do not.
776
770
  */
777
771
  export const deriveTrail = <
778
- TContour extends AnyContour,
772
+ TEntity extends AnyEntity,
779
773
  TOperation extends DeriveTrailOperation,
780
- TGenerated extends readonly ContourFieldKey<TContour>[] | undefined =
781
- | readonly ContourFieldKey<TContour>[]
774
+ TGenerated extends readonly EntityFieldKey<TEntity>[] | undefined =
775
+ | readonly EntityFieldKey<TEntity>[]
782
776
  | undefined,
783
777
  >(
784
- contour: TContour,
778
+ entity: TEntity,
785
779
  operation: TOperation,
786
- spec: DeriveTrailSpec<TContour, TOperation, TGenerated>
780
+ spec: DeriveTrailSpec<TEntity, TOperation, TGenerated>
787
781
  ): Trail<
788
- DeriveTrailInput<TContour, TOperation, TGenerated>,
789
- DeriveTrailOutput<TContour, TOperation>
782
+ DeriveTrailInput<TEntity, TOperation, TGenerated>,
783
+ DeriveTrailOutput<TEntity, TOperation>
790
784
  > => {
791
785
  const resources = normalizeResources(spec.resource);
792
786
  const generated = uniqueStrings(
793
787
  spec.generated as readonly string[] | undefined
794
788
  );
795
789
 
796
- let blaze: Implementation<
797
- DeriveTrailInput<TContour, TOperation, TGenerated>,
798
- DeriveTrailOutput<TContour, TOperation>
790
+ let implementation: Implementation<
791
+ DeriveTrailInput<TEntity, TOperation, TGenerated>,
792
+ DeriveTrailOutput<TEntity, TOperation>
799
793
  >;
800
- if (typeof spec.blaze === 'function') {
801
- ({ blaze } = spec);
794
+ if (typeof spec.implementation === 'function') {
795
+ ({ implementation } = spec);
802
796
  } else if (resources.length === 1) {
803
- blaze = synthesizeDefaultBlaze<TContour, TOperation, TGenerated>(
804
- contour,
805
- operation,
806
- resources[0] as AnyResource,
807
- generated
808
- );
797
+ implementation = synthesizeDefaultImplementation<
798
+ TEntity,
799
+ TOperation,
800
+ TGenerated
801
+ >(entity, operation, resources[0] as AnyResource, generated);
809
802
  } else {
810
803
  throw new DerivationError(
811
- `deriveTrail("${contour.name}.${operation}") requires an explicit \`blaze\` when ${describeDeriveTrailResourceDeclaration(resources.length)} — default synthesis is single-resource only`
804
+ `deriveTrail("${entity.name}.${operation}") requires an explicit \`implementation\` when ${describeDeriveTrailResourceDeclaration(resources.length)} — default synthesis is single-resource only`
812
805
  );
813
806
  }
814
807
  const {
815
- blaze: _blaze,
808
+ implementation: _implementation,
816
809
  resource: _resource,
817
810
  generated: _generated,
818
811
  ...trailSpec
819
812
  } = spec;
820
813
  const derivedSpec = {
821
814
  ...trailSpec,
822
- blaze,
823
- contours: [contour],
824
- examples: deriveExamples(contour, operation, generated),
825
- input: deriveInputSchema<TContour, TOperation, TGenerated>(
826
- contour,
815
+ entities: [entity],
816
+ examples: deriveExamples(entity, operation, generated),
817
+ implementation,
818
+ input: deriveInputSchema<TEntity, TOperation, TGenerated>(
819
+ entity,
827
820
  operation,
828
821
  generated
829
822
  ),
830
823
  intent: operationIntent[operation],
831
- output: deriveOutputSchema(contour, operation),
824
+ output: deriveOutputSchema(entity, operation),
832
825
  resources,
833
826
  } as unknown as TrailSpec<
834
- DeriveTrailInput<TContour, TOperation, TGenerated>,
835
- DeriveTrailOutput<TContour, TOperation>
827
+ DeriveTrailInput<TEntity, TOperation, TGenerated>,
828
+ DeriveTrailOutput<TEntity, TOperation>
836
829
  >;
837
830
 
838
- return trail(`${contour.name}.${operation}`, derivedSpec) as unknown as Trail<
839
- DeriveTrailInput<TContour, TOperation, TGenerated>,
840
- DeriveTrailOutput<TContour, TOperation>
831
+ return trail(`${entity.name}.${operation}`, derivedSpec) as unknown as Trail<
832
+ DeriveTrailInput<TEntity, TOperation, TGenerated>,
833
+ DeriveTrailOutput<TEntity, TOperation>
841
834
  >;
842
835
  };