@ultimat3/testing 30.0.0 → 30.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,7 +34,7 @@ unitTest('the clock moves only when the test moves it', ({ clock }) => {
34
34
  | `seed-graph-error.ts` | `X_SEED_GRAPH_UNSATISFIED`, its fix naming the `defineFactory` to write; `X_SEED_GRAPH_ENTITY_UNKNOWN`, a plan and a writer built from two entity sets |
35
35
  | `seed-writer.ts` | `entitySeedWriter({ entities, client })` or `({ entities, driver? })` — the `SeedWriter` that writes a graph through each entity's own repository: Postgres or PGlite through a client, or a driver. `@ultimat3/entity` loads on the first write. `snapshot(rows)`, required on every `SeedWriter`, reads rows back as one digest each (`md5(t::text)` over a client), `null` for a row gone |
36
36
  | `exercise.ts` | `runExercisePass({ entities, writer, roles, seed?, overrides?, only? })` — every registered action, query and job once, each on its own committed tenant pair, one `ExerciseRecord` per primitive, the tenant proof's verdict on its `tenant`. What `x exercise` runs |
37
- | `define-exercise.ts` | `defineExercise({ <name>: { actor, input?, runs? } \| { off: '<why>' } }, { actor?: <entity> })` — an app's overrides (the second argument names the entity whose rows are the actors), `apps/web/exercise.ts`'s one export; a blank `off` is `X_CONTRACT_EXERCISE_UNDECLARED`, an unknown name `X_DECLARATION_UNKNOWN` when the pass runs |
37
+ | `define-exercise.ts` | `defineExercise({ <name>: { actor, input?, runs? } \| { off: '<why>' } }, { actor?: <entity>, factories?: { <entity name>: defineFactory(…) } })` — an app's overrides (the second argument names the entity whose rows are the actors, and the factories that build a table no generated row satisfies — a cross-column `satisfies` — keyed by entity name, unknown ones `X_DECLARATION_UNKNOWN`), `apps/web/exercise.ts`'s one export; a blank `off` is `X_CONTRACT_EXERCISE_UNDECLARED`, an unknown name `X_DECLARATION_UNKNOWN` when the pass runs |
38
38
  | `exercise-actor.ts` · `exercise-input.ts` · `exercise-report.ts` | the first `x policy explain` actor the policy allows, minted in the primitive's tenant · the input: override, then a seeded id by field stem, then the minimal draw (`@ultimat3/schema/sample`, loaded dynamically) · the room one run executes in (network sealed, mail captured, clock frozen) and its record, `overrideLine` the entry to paste |
39
39
  | `tenant-proof.ts` | internal (`CROSS_TENANT_SCOPE` is `@ultimat3/entity`'s): `proveTenant` — B with A's ids, A's own call, B again; each B leg between two snapshots of A's rows. `withoutCrossTenant` strips `tenancy:cross` (`CROSS_TENANT_SCOPE`) from B; `publicReason` decides `public` |
40
40
  | `tenant-canary.ts` | internal: `tenantTaint` — one tenant's rows, ids and marks (row canaries, key parts of `MIN_TAINT_ID_LENGTH` 8 or more no other tenant shares); `findTaint` — every path holding one |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/testing",
3
- "version": "30.0.0",
3
+ "version": "30.1.0",
4
4
  "description": "Test harness: cloned template DBs per worker, frozen clock, sealed network, 6 test types",
5
5
  "keywords": [
6
6
  "ultimate",
@@ -46,21 +46,21 @@
46
46
  "test": "bun test"
47
47
  },
48
48
  "dependencies": {
49
- "@ultimat3/action": "30.0.0",
50
- "@ultimat3/cache": "30.0.0",
51
- "@ultimat3/core": "30.0.0",
52
- "@ultimat3/db": "30.0.0",
53
- "@ultimat3/entity": "30.0.0",
54
- "@ultimat3/http": "30.0.0",
55
- "@ultimat3/i18n": "30.0.0",
56
- "@ultimat3/jobs": "30.0.0",
57
- "@ultimat3/mail": "30.0.0",
58
- "@ultimat3/policy": "30.0.0",
59
- "@ultimat3/pwa": "30.0.0",
60
- "@ultimat3/query": "30.0.0",
61
- "@ultimat3/realtime": "30.0.0",
62
- "@ultimat3/render": "30.0.0",
63
- "@ultimat3/schema": "30.0.0",
64
- "@ultimat3/time": "30.0.0"
49
+ "@ultimat3/action": "30.1.0",
50
+ "@ultimat3/cache": "30.1.0",
51
+ "@ultimat3/core": "30.1.0",
52
+ "@ultimat3/db": "30.1.0",
53
+ "@ultimat3/entity": "30.1.0",
54
+ "@ultimat3/http": "30.1.0",
55
+ "@ultimat3/i18n": "30.1.0",
56
+ "@ultimat3/jobs": "30.1.0",
57
+ "@ultimat3/mail": "30.1.0",
58
+ "@ultimat3/policy": "30.1.0",
59
+ "@ultimat3/pwa": "30.1.0",
60
+ "@ultimat3/query": "30.1.0",
61
+ "@ultimat3/realtime": "30.1.0",
62
+ "@ultimat3/render": "30.1.0",
63
+ "@ultimat3/schema": "30.1.0",
64
+ "@ultimat3/time": "30.1.0"
65
65
  }
66
66
  }
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { ExerciseOptions } from '@ultimat3/action';
7
7
  import { renderCauseValue, renderFixLiteral, UltimateError } from '@ultimat3/core';
8
+ import type { SeedFactory } from './seed-graph-plan';
8
9
 
9
10
  /** 39's option type, exactly: `{ actor, input?, runs? }` or `{ off: '<reason>' }`. */
10
11
  export type ExerciseOverride = ExerciseOptions;
@@ -15,6 +16,8 @@ export interface ExerciseOverrides {
15
16
  readonly entries: ReadonlyMap<string, ExerciseOverride>;
16
17
  /** The entity an actor is a row of, by name: its id is bound to a seeded row. */
17
18
  readonly actorEntity?: string;
19
+ /** By entity name: the pass's graph builds those rows with them (`seedGraph`'s `factories`). */
20
+ readonly factories?: Readonly<Record<string, SeedFactory>>;
18
21
  }
19
22
 
20
23
  /** What the whole pass needs to know of the app, beside the per-primitive entries. */
@@ -25,6 +28,12 @@ export interface ExerciseAppOptions {
25
28
  * writes a row that exists. Omitted, an actor's id is its role's name.
26
29
  */
27
30
  readonly actor?: { readonly $name: string };
31
+ /**
32
+ * By entity name, `seedGraph`'s own option: a table whose rows a generated value cannot satisfy
33
+ * (an `invariant` across columns) is built by the app's `defineFactory`. An unknown name is
34
+ * refused when the pass runs.
35
+ */
36
+ readonly factories?: Readonly<Record<string, SeedFactory>>;
28
37
  }
29
38
 
30
39
  /** `X_CONTRACT_EXERCISE_UNDECLARED`, 39's code for the same silence: an `off` with no reason. */
@@ -49,6 +58,7 @@ export function defineExercise(
49
58
  kind: 'exercise-overrides',
50
59
  entries,
51
60
  ...(app.actor === undefined ? {} : { actorEntity: app.actor.$name }),
61
+ ...(app.factories === undefined ? {} : { factories: app.factories }),
52
62
  });
53
63
  }
54
64
 
package/src/exercise.ts CHANGED
@@ -418,6 +418,9 @@ export async function runExercisePass(
418
418
  graph = await seedPass(options.entities, options.writer, {
419
419
  primitives: live.length,
420
420
  seed: passSeed,
421
+ ...(options.overrides?.factories === undefined
422
+ ? {}
423
+ : { factories: options.overrides.factories }),
421
424
  });
422
425
  // In order, one at a time: the seams the room installs are the process's, not the primitive's.
423
426
  for (const subject of chosen) {
@@ -27,7 +27,7 @@ export class SeedGraphUnsatisfiedError extends UltimateError {
27
27
  super({
28
28
  code: 'X_SEED_GRAPH_UNSATISFIED',
29
29
  cause: `${input.entity}: ${input.what}`,
30
- fix: `defineFactory(<the ${input.entity} entity>, { defaults: (index, ids) => ({ ${body} }) }), then seedGraph(entities, { factories: { ${name}: thatFactory } })`,
30
+ fix: `defineFactory(<the ${input.entity} entity>, { defaults: (index, ids) => ({ ${body} }) }), then seedGraph(entities, { factories: { ${name}: thatFactory } }) — for x exercise, defineExercise({}, { factories: { ${name}: thatFactory } }) in apps/web/exercise.ts`,
31
31
  });
32
32
  }
33
33
  }
@@ -3,10 +3,10 @@
3
3
  // owns a fresh tenant pair of its own. One graph also means one set of unique keys: per-primitive
4
4
  // graphs re-inserted the shared rows and collided on their primary keys.
5
5
 
6
- import { isUltimateError, renderThrowable } from '@ultimat3/core';
6
+ import { isUltimateError, renderCauseValue, renderThrowable, UltimateError } from '@ultimat3/core';
7
7
  import { seedGraph } from './seed-graph';
8
8
  import { seedGraphUnsatisfied } from './seed-graph-error';
9
- import type { SeedGraph, SeedRow, SeedUpdate, SeedWriter } from './seed-graph-plan';
9
+ import type { SeedFactory, SeedGraph, SeedRow, SeedUpdate, SeedWriter } from './seed-graph-plan';
10
10
  import { insertSeedGraph } from './seed-graph-plan';
11
11
  import type { SeedEntity } from './seed-graph-shape';
12
12
 
@@ -50,6 +50,15 @@ export const tenantPair = (graph: SeedGraph, index: number): readonly [string, s
50
50
  graph.tenants[index * TENANTS_PER_PRIMITIVE + 1] as string,
51
51
  ];
52
52
 
53
+ /** A factory keyed by a name no seeded entity has would build nothing, and the red would stay. */
54
+ const unknownFactory = (names: readonly string[], known: ReadonlySet<string>): UltimateError =>
55
+ new UltimateError({
56
+ code: 'X_DECLARATION_UNKNOWN',
57
+ cause: `defineExercise() factories name ${names.map((name) => renderCauseValue(name)).join(', ')}, which no seeded entity is called (seeded: ${renderCauseValue([...known].sort().join(', '))})`,
58
+ fix: "defineExercise({}, { factories: { '<the name entity(...) was declared with>': thatFactory } }) # in apps/web/exercise.ts; the seeded names are in the cause",
59
+ meta: { unknown: names, registered: [...known].sort() },
60
+ });
61
+
53
62
  /**
54
63
  * Plan and write the pass's graph: shared rows once, `TENANTS_PER_PRIMITIVE` tenants per
55
64
  * primitive. Same seed, same rows. Before any primitive runs, so a failure refuses the pass.
@@ -57,11 +66,19 @@ export const tenantPair = (graph: SeedGraph, index: number): readonly [string, s
57
66
  export async function seedPass(
58
67
  entities: readonly SeedEntity[],
59
68
  writer: SeedWriter,
60
- options: { readonly primitives: number; readonly seed: number },
69
+ options: {
70
+ readonly primitives: number;
71
+ readonly seed: number;
72
+ readonly factories?: Readonly<Record<string, SeedFactory>>;
73
+ },
61
74
  ): Promise<SeedGraph> {
75
+ const known = new Set(entities.map((entity) => entity.$name));
76
+ const unknown = Object.keys(options.factories ?? {}).filter((name) => !known.has(name));
77
+ if (unknown.length > 0) throw unknownFactory(unknown, known);
62
78
  const graph = seedGraph(Object.fromEntries(entities.map((entity) => [entity.$name, entity])), {
63
79
  tenants: Math.max(1, options.primitives) * TENANTS_PER_PRIMITIVE,
64
80
  seed: options.seed,
81
+ ...(options.factories === undefined ? {} : { factories: options.factories }),
65
82
  });
66
83
  await insertSeedGraph(graph, coded(writer));
67
84
  return graph;