@astrale-os/sdk 0.6.0-beta.42 → 0.6.0-beta.44

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 (125) hide show
  1. package/README.md +7 -7
  2. package/dist/application/action/authoring.d.ts +1 -1
  3. package/dist/application/action/context.d.ts +9 -8
  4. package/dist/application/error/error.d.ts +7 -7
  5. package/dist/application/error/error.js +12 -12
  6. package/dist/application/error/index.d.ts +1 -1
  7. package/dist/application/error/index.js +1 -1
  8. package/dist/application/mutation/authoring/builder.d.ts +12 -12
  9. package/dist/application/mutation/authoring/builder.js +3 -3
  10. package/dist/application/mutation/authoring/guard.d.ts +2 -2
  11. package/dist/application/mutation/authoring/index.d.ts +1 -1
  12. package/dist/application/mutation/define.d.ts +8 -8
  13. package/dist/application/mutation/define.js +12 -12
  14. package/dist/application/mutation/execution/execute.d.ts +2 -2
  15. package/dist/application/mutation/execution/execute.js +10 -10
  16. package/dist/application/mutation/execution/executor.d.ts +2 -2
  17. package/dist/application/mutation/execution/executor.js +2 -2
  18. package/dist/application/mutation/index.d.ts +2 -2
  19. package/dist/application/mutation/legacy/v6/define.d.ts +1 -1
  20. package/dist/application/mutation/legacy/v6/mutation.d.ts +1 -1
  21. package/dist/application/mutation/mutation.d.ts +12 -12
  22. package/dist/application/query/composite/builder.js +1 -1
  23. package/dist/application/query/define.d.ts +1 -1
  24. package/dist/application/query/define.js +3 -3
  25. package/dist/application/query/defined/define.d.ts +45 -0
  26. package/dist/application/query/defined/define.js +96 -0
  27. package/dist/application/query/defined/query.d.ts +135 -0
  28. package/dist/application/query/definition.d.ts +5 -5
  29. package/dist/application/query/definition.js +4 -4
  30. package/dist/application/query/execution/bounds.d.ts +7 -5
  31. package/dist/application/query/execution/bounds.js +4 -2
  32. package/dist/application/query/execution/client.d.ts +20 -0
  33. package/dist/application/query/execution/client.js +1 -0
  34. package/dist/application/query/execution/crossings.d.ts +3 -3
  35. package/dist/application/query/execution/crossings.js +55 -32
  36. package/dist/application/query/execution/error.d.ts +8 -8
  37. package/dist/application/query/execution/error.js +5 -5
  38. package/dist/application/query/execution/execute.d.ts +4 -4
  39. package/dist/application/query/execution/executor.d.ts +2 -2
  40. package/dist/application/query/execution/rows.d.ts +5 -5
  41. package/dist/application/query/execution/rows.js +42 -30
  42. package/dist/application/query/execution/run.d.ts +75 -0
  43. package/dist/application/query/execution/{recipe.js → run.js} +116 -68
  44. package/dist/application/query/execution/split.d.ts +3 -3
  45. package/dist/application/query/execution/split.js +1 -1
  46. package/dist/application/query/handles/build.d.ts +1 -1
  47. package/dist/application/query/handles/domain.d.ts +1 -1
  48. package/dist/application/query/handles/handles.d.ts +14 -12
  49. package/dist/application/query/handles/plan.d.ts +10 -9
  50. package/dist/application/query/handles/plan.js +1 -1
  51. package/dist/application/query/handles/reads.js +7 -7
  52. package/dist/application/query/handles/rows.d.ts +1 -1
  53. package/dist/application/query/handles/terms.d.ts +9 -1
  54. package/dist/application/query/handles/terms.js +20 -3
  55. package/dist/application/query/index.d.ts +8 -5
  56. package/dist/application/query/index.js +3 -3
  57. package/dist/application/query/language/index.d.ts +1 -1
  58. package/dist/application/query/language/query.d.ts +5 -5
  59. package/dist/application/query/legacy/v6/collection/union.d.ts +1 -1
  60. package/dist/application/query/legacy/v6/collection/union.js +3 -3
  61. package/dist/application/query/legacy/v6/define.d.ts +1 -1
  62. package/dist/application/query/legacy/v6/define.js +3 -3
  63. package/dist/application/query/legacy/v6/execution/execute.d.ts +6 -4
  64. package/dist/application/query/legacy/v6/index.d.ts +1 -1
  65. package/dist/application/query/legacy/v6/index.js +1 -1
  66. package/dist/application/query/query.d.ts +3 -3
  67. package/dist/application/routes/admission.d.ts +1 -1
  68. package/dist/application/routes/admission.js +2 -2
  69. package/dist/application/routes/index.d.ts +3 -2
  70. package/dist/application/routes/route.d.ts +1 -1
  71. package/dist/application/runtime/declarations.d.ts +6 -6
  72. package/dist/application/runtime/define.d.ts +5 -5
  73. package/dist/application/workflow/durable.d.ts +9 -9
  74. package/dist/deployment/build/build.d.ts +8 -1
  75. package/dist/deployment/build/material.js +3 -0
  76. package/dist/execution/invocation/capabilities/bind.d.ts +1 -1
  77. package/dist/execution/invocation/capabilities/bind.js +8 -8
  78. package/dist/execution/invocation/dispatch/action.js +2 -2
  79. package/dist/execution/invocation/dispatch/workflow.js +2 -2
  80. package/dist/execution/invocation/result/index.d.ts +1 -1
  81. package/dist/execution/invocation/result/index.js +1 -1
  82. package/dist/execution/invocation/result/undeclared.d.ts +7 -0
  83. package/dist/execution/invocation/result/undeclared.js +18 -0
  84. package/dist/execution/workflows/failure.d.ts +7 -7
  85. package/dist/execution/workflows/failure.js +19 -18
  86. package/dist/execution/workflows/receiver.d.ts +1 -1
  87. package/dist/execution/workflows/run.js +2 -2
  88. package/dist/execution/workflows/steps.d.ts +1 -1
  89. package/dist/platform/schema/class-authoring.d.ts +2 -8
  90. package/dist/platform/schema/index.d.ts +1 -1
  91. package/dist/platform/schema/kernel-compiled.d.ts +1 -1
  92. package/dist/platform/schema/kernel-compiled.js +2 -2
  93. package/dist/platform/schema/kernel.d.ts +4 -2
  94. package/dist/testing/index.d.ts +2 -2
  95. package/dist/testing/index.js +1 -1
  96. package/dist/testing/{recipe.d.ts → query.d.ts} +6 -6
  97. package/dist/testing/{recipe.js → query.js} +16 -11
  98. package/dist/tooling/cli/orchestrate.js +7 -6
  99. package/dist/tooling/linter/adapters/typescript/{recipe-definitions.d.ts → definition-calls.d.ts} +10 -10
  100. package/dist/tooling/linter/adapters/typescript/{recipe-definitions.js → definition-calls.js} +10 -10
  101. package/dist/tooling/linter/adapters/typescript/index.d.ts +1 -1
  102. package/dist/tooling/linter/adapters/typescript/index.js +1 -1
  103. package/dist/tooling/linter/adapters/typescript/query-observation.js +6 -6
  104. package/dist/tooling/linter/implementations/source/definitions.d.ts +2 -0
  105. package/dist/tooling/linter/implementations/source/{recipes.js → definitions.js} +60 -44
  106. package/dist/tooling/linter/implementations/source/global.js +8 -6
  107. package/dist/tooling/linter/implementations/source/mutations.js +5 -5
  108. package/dist/tooling/linter/implementations/source/shared.d.ts +2 -2
  109. package/dist/tooling/linter/implementations/source/shared.js +7 -7
  110. package/dist/tooling/linter/implementations/source/verifiers.js +2 -2
  111. package/dist/tooling/linter/policy/generated.js +3 -3
  112. package/dist/tooling/linter/requirements/registry.js +4 -4
  113. package/docs/durable-workflows.md +8 -8
  114. package/docs/handler-dependencies.md +1 -1
  115. package/docs/legacy/query-v6.md +7 -7
  116. package/docs/queries.md +60 -56
  117. package/package.json +6 -6
  118. package/dist/application/query/execution/recipe.d.ts +0 -70
  119. package/dist/application/query/recipe/define.d.ts +0 -45
  120. package/dist/application/query/recipe/define.js +0 -102
  121. package/dist/application/query/recipe/recipe.d.ts +0 -134
  122. package/dist/execution/invocation/result/recipe.d.ts +0 -6
  123. package/dist/execution/invocation/result/recipe.js +0 -17
  124. package/dist/tooling/linter/implementations/source/recipes.d.ts +0 -2
  125. /package/dist/application/query/{recipe/recipe.js → defined/query.js} +0 -0
@@ -11,7 +11,7 @@ interface CompositeQueryProjector<Schema extends schema.DomainSchema> {
11
11
  <Input, Output>(project: (domain: ResolvedDomainOf<Schema>) => Omit<RawCompositeQuery<Input, Output, ResolvedDomainOf<Schema>>, 'kind'>): CompositeQueryDefinition<ResolvedDomainOf<Schema>, Input, Output>;
12
12
  <Input, Composed, Output, Plan extends QueryPlan<Composed> = QueryPlan<Composed>>(project: (domain: ResolvedDomainOf<Schema>) => AuthoredInferredComposite<Input, Composed, Plan, Output, ResolvedDomainOf<Schema>>): CompositeQueryDefinition<ResolvedDomainOf<Schema>, Input, Output>;
13
13
  }
14
- /** Define one inert composite Query recipe projected from its exact loaded Domain. */
14
+ /** Define one inert composite Query projected from its exact loaded Domain. */
15
15
  export declare function defineCompositeQuery<Schema extends schema.DomainSchema>(): CompositeQueryProjector<Schema>;
16
16
  export declare function realizeQuery<DomainValue extends Domain, Input, Output>(query: QueryDefinition<DomainValue, Input, Output>, domain: DomainValue): RealizedQuery<Input, Output>;
17
17
  export {};
@@ -1,10 +1,10 @@
1
1
  import { isDomain } from '@astrale-os/kernel-dsl/v1/domain';
2
- import { definitionBase, isPromiseLike, projectorOf, recipe } from './definition.js';
2
+ import { definitionBase, isPromiseLike, projectorOf, inertQuery } from './definition.js';
3
3
  import { admitSingle } from './legacy/v6/define.js';
4
4
  const realized = new WeakMap();
5
- /** Define one inert composite Query recipe projected from its exact loaded Domain. */
5
+ /** Define one inert composite Query projected from its exact loaded Domain. */
6
6
  export function defineCompositeQuery() {
7
- return ((project) => recipe('composite-query', project));
7
+ return ((project) => inertQuery('composite-query', project));
8
8
  }
9
9
  export function realizeQuery(query, domain) {
10
10
  const project = projectorOf(query);
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `defineQuery<Schema>()`: one name for both forms. A string id first defines a Query v7; a
3
+ * function first defines a V6 Query, which stays in `legacy/v6`. A Query v7 is inert: its id and
4
+ * the errors it declares are public, and what it reads is kept for the runtime (WP-S3).
5
+ */
6
+ import type { schema } from '../../../platform/schema/index.js';
7
+ import type { SingleQueryProjector } from '../legacy/v6/define.js';
8
+ import type { DefinedQuery, DefinedQueryFactory } from './query.js';
9
+ /** A Query or Mutation id: lowercase segments of letters, digits and hyphens, joined by dots. */
10
+ export declare const DEFINITION_ID: RegExp;
11
+ export declare const MAXIMUM_DEFINITION_ID = 128;
12
+ type Callback = (...parameters: never[]) => unknown;
13
+ /** What a Query reads and answers: its `build`, of `(input, d, steps)`, and its `output`. */
14
+ export interface QuerySpec {
15
+ readonly build: Callback;
16
+ readonly output?: Callback;
17
+ }
18
+ /** The build and the output of a Query v7; `undefined` for anything else. */
19
+ export declare function querySpecOf(query: unknown): QuerySpec | undefined;
20
+ /** A definition of the Domain's namespace, a Query's or a Mutation's: its id, and its errors. */
21
+ export type NamedDefinition = Pick<DefinedQuery, 'id' | 'errors'>;
22
+ /**
23
+ * Keep the errors a definition declares, a Query's or a Mutation's, and collect it into the
24
+ * collections open: a defined Mutation shares its Domain's Query ids and error definitions.
25
+ */
26
+ export declare function collectDefinition(query: NamedDefinition): void;
27
+ /**
28
+ * Collect into `queries` each Query or Mutation defined while `load` runs: a build collects the
29
+ * definitions that loading its Domain defines, and checks them with `admitDefinitions`. A module an
30
+ * earlier load of the same process evaluated defines nothing again, so its definitions were checked
31
+ * with that load.
32
+ */
33
+ export declare function collectDefinitions<T>(queries: NamedDefinition[], load: () => Promise<T>): Promise<T>;
34
+ /**
35
+ * Refuse two definitions of one Domain under one id: a refusal and a call name a Query or Mutation
36
+ * by its id. Refuse two that declare one code with different definitions: a Function that spreads
37
+ * both errors keeps one of them, and would redact the other definition's error.
38
+ */
39
+ export declare function admitDefinitions(queries: readonly NamedDefinition[]): void;
40
+ /**
41
+ * Define a Query: `defineQuery<Schema>()('services.service.owner', { build, output?, errors? })`.
42
+ * The V6 form, `defineQuery<Schema>()((domain) => ({ id, build, project? }))`, stays.
43
+ */
44
+ export declare function defineQuery<Schema extends schema.DomainSchema>(): SingleQueryProjector<Schema> & DefinedQueryFactory<Schema>;
45
+ export {};
@@ -0,0 +1,96 @@
1
+ import { rememberDefinedErrors } from '../../error/error.js';
2
+ import { describe } from '../handles/terms.js';
3
+ import { defineQuery as defineV6Query } from '../legacy/v6/define.js';
4
+ /** A Query or Mutation id: lowercase segments of letters, digits and hyphens, joined by dots. */
5
+ export const DEFINITION_ID = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/u;
6
+ export const MAXIMUM_DEFINITION_ID = 128;
7
+ const FIELDS = new Set(['build', 'output', 'errors']);
8
+ const specs = new WeakMap();
9
+ /** The collections open while a build loads a Domain; no definition is kept outside one. */
10
+ const collections = new Set();
11
+ /** The build and the output of a Query v7; `undefined` for anything else. */
12
+ export function querySpecOf(query) {
13
+ return typeof query === 'object' && query !== null ? specs.get(query) : undefined;
14
+ }
15
+ function defineV7Query(id, spec) {
16
+ if (typeof id !== 'string' || id.length > MAXIMUM_DEFINITION_ID || !DEFINITION_ID.test(id))
17
+ throw new TypeError(`A Query id is lowercase segments of letters, digits and hyphens joined by dots, at most ${MAXIMUM_DEFINITION_ID} characters ('services.service.owner'); got ${describe(id)}.`);
18
+ if (typeof spec !== 'object' || spec === null || Array.isArray(spec))
19
+ throw new TypeError(`Query ${id} takes { build, output?, errors? }; got ${describe(spec)}.`);
20
+ const fields = spec;
21
+ const unknown = Reflect.ownKeys(fields).filter((key) => typeof key !== 'string' || !FIELDS.has(key));
22
+ if (unknown.length > 0)
23
+ throw new TypeError(`Query ${id} takes build, output and errors only; got ${unknown.map(String).join(', ')}: do that work in build or output.`);
24
+ const { build, output } = fields;
25
+ if (typeof build !== 'function')
26
+ throw new TypeError(`Query ${id}'s build is a function of (input, d, steps) that returns a read, a record of facets, or steps(first, (earlier) => next, …); got ${describe(build)}.`);
27
+ if (output !== undefined && typeof output !== 'function')
28
+ throw new TypeError(`Query ${id}'s output is a function of (results, input, context); got ${describe(output)}.`);
29
+ const errors = fields.errors ?? {};
30
+ if (typeof errors !== 'object' || errors === null || Array.isArray(errors))
31
+ throw new TypeError(`Query ${id}'s errors is a record of declared errors by UPPER_SNAKE code; got ${describe(errors)}.`);
32
+ const query = Object.freeze({ id, errors: Object.freeze({ ...errors }) });
33
+ collectDefinition(query);
34
+ specs.set(query, Object.freeze({
35
+ build: build,
36
+ ...(output !== undefined ? { output: output } : {}),
37
+ }));
38
+ return query;
39
+ }
40
+ /**
41
+ * Keep the errors a definition declares, a Query's or a Mutation's, and collect it into the
42
+ * collections open: a defined Mutation shares its Domain's Query ids and error definitions.
43
+ */
44
+ export function collectDefinition(query) {
45
+ rememberDefinedErrors(query, query.id, query.errors);
46
+ for (const collection of collections)
47
+ collection.push(query);
48
+ }
49
+ /**
50
+ * Collect into `queries` each Query or Mutation defined while `load` runs: a build collects the
51
+ * definitions that loading its Domain defines, and checks them with `admitDefinitions`. A module an
52
+ * earlier load of the same process evaluated defines nothing again, so its definitions were checked
53
+ * with that load.
54
+ */
55
+ export async function collectDefinitions(queries, load) {
56
+ collections.add(queries);
57
+ try {
58
+ return await load();
59
+ }
60
+ finally {
61
+ collections.delete(queries);
62
+ }
63
+ }
64
+ /**
65
+ * Refuse two definitions of one Domain under one id: a refusal and a call name a Query or Mutation
66
+ * by its id. Refuse two that declare one code with different definitions: a Function that spreads
67
+ * both errors keeps one of them, and would redact the other definition's error.
68
+ */
69
+ export function admitDefinitions(queries) {
70
+ const ids = new Set();
71
+ for (const { id } of queries) {
72
+ if (ids.has(id))
73
+ throw new TypeError(`Two Queries or Mutations of the Domain are named ${id}: an id is unique in its Domain.`);
74
+ ids.add(id);
75
+ }
76
+ const first = new Map();
77
+ for (const query of queries) {
78
+ for (const [code, declaration] of Object.entries(query.errors)) {
79
+ const seen = first.get(code);
80
+ if (seen === undefined)
81
+ first.set(code, { query: query.id, declaration });
82
+ else if (seen.declaration !== declaration)
83
+ throw new TypeError(`${seen.query} and ${query.id} declare ${code} with different definitions: declare it once and share that object, so a Function that re-declares both keeps each.`);
84
+ }
85
+ }
86
+ }
87
+ /**
88
+ * Define a Query: `defineQuery<Schema>()('services.service.owner', { build, output?, errors? })`.
89
+ * The V6 form, `defineQuery<Schema>()((domain) => ({ id, build, project? }))`, stays.
90
+ */
91
+ export function defineQuery() {
92
+ const v6 = defineV6Query();
93
+ return ((first, spec) => typeof first === 'string'
94
+ ? defineV7Query(first, spec)
95
+ : v6(first));
96
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * A Query (spec § Queries: the contract): an id, a `build` over the Domain's handles, an optional
3
+ * `output` and the errors it declares. `build` returns one read, a record of facets read together,
4
+ * or, when reads wait for others, `steps(first, (earlier) => next, …)`: two to eight records of
5
+ * facets, each function receiving the merged results of the steps before it.
6
+ */
7
+ import type { z } from 'zod';
8
+ import type { DeclaredErrorDefinition } from '../../../platform/schema/error.js';
9
+ import type { ClassOf, Index, KeyOf, Values } from '../handles/domain.js';
10
+ import type { Handles, PageRead, Read } from '../handles/handles.js';
11
+ import type { Page } from '../handles/rows.js';
12
+ /** One facet: a read, a list of reads (`items.map(…)`), or nothing (a read that is optional). */
13
+ export type Facet = Read<unknown> | readonly Read<unknown>[] | undefined;
14
+ /** Facets read together, by name. */
15
+ export type Facets = {
16
+ readonly [facet: string]: Facet;
17
+ };
18
+ /** What `build` returns without steps, and the first step: one read, or a record of facets. */
19
+ export type Built = Read<unknown> | Facets;
20
+ /** A read gives its answer; a list of reads a list of answers; nothing gives `undefined`. */
21
+ export type FacetResult<F> = F extends Read<infer T> ? T : F extends readonly Read<infer T>[] ? readonly T[] : F extends undefined ? undefined : never;
22
+ /** The results of one step; a page read, a Query's single read, gives its `Page`. */
23
+ export type Results<B> = B extends Read<infer T> ? T : B extends PageRead<infer T> ? Page<T> : {
24
+ readonly [K in keyof B]: FacetResult<B[K]>;
25
+ };
26
+ /** What a page Query's `output` keeps: the page shape. */
27
+ type PageShape<B> = B extends PageRead<unknown> ? Page<unknown> : unknown;
28
+ /** One object type: a Domain's exported declarations spell it out, naming no SDK internal. */
29
+ type Flat<T> = T extends unknown ? {
30
+ readonly [K in keyof T]: T[K];
31
+ } : never;
32
+ /**
33
+ * A later step's facets: none takes a name an earlier step read, whose result the merge would
34
+ * replace. An index signature (its `string` and `number` keys) names no facet in particular, so it
35
+ * refuses none.
36
+ */
37
+ type Later<Earlier> = Facets & {
38
+ readonly [K in keyof Earlier as string extends K ? never : number extends K ? never : K]?: '✗ an earlier step read this facet: name it otherwise';
39
+ };
40
+ /** The results of the steps so far, merged. */
41
+ export type Merged<B1, B2, B3 = {}, B4 = {}, B5 = {}, B6 = {}, B7 = {}, B8 = {}> = Flat<Results<B1> & Results<B2> & Results<B3> & Results<B4> & Results<B5> & Results<B6> & Results<B7> & Results<B8>>;
42
+ /** The errors a Query or Mutation declares, by UPPER_SNAKE code: at most 32. */
43
+ export type Errors = {
44
+ readonly [code: string]: DeclaredErrorDefinition;
45
+ };
46
+ /** The key of any Class of the Domain, its dependencies' or the Kernel's. */
47
+ export type ClassKeyOf<S> = KeyOf<Values<Index<S>>>;
48
+ /** A declared error of a Query, for `output` to throw. */
49
+ export interface DeclaredError<out C extends string> extends Error {
50
+ readonly '~code'?: C;
51
+ }
52
+ /** A row narrowed to a Class or any of its subclasses: the union members it may be, and `$class`. */
53
+ export type Narrowed<S, R extends {
54
+ readonly $class: string;
55
+ }, K> = R extends unknown ? [Extract<R['$class'], ClassOf<S, K>>] extends [never] ? never : R & {
56
+ readonly $class: Extract<R['$class'], ClassOf<S, K>>;
57
+ } : never;
58
+ /** What `output` receives besides the results and the input. */
59
+ export interface OutputContext<S, E extends Errors> {
60
+ /** A declared error, for `output` to throw: `throw error('SERVICE_NOT_FOUND', { service })`. */
61
+ error<C extends keyof E & string>(code: C, details: z.input<E[C]['details']>): DeclaredError<C>;
62
+ /** Narrows a row to a Class or any of its subclasses, by Class key. */
63
+ is<R extends {
64
+ readonly $class: string;
65
+ }, K extends ClassKeyOf<S>>(row: R, cls: K): row is Narrowed<S, R, K>;
66
+ }
67
+ /** A Query v7: its id, and the errors it declares, to spread into a Function's errors. */
68
+ export interface DefinedQuery<Id extends string = string, Input = unknown, Output = unknown, E extends Errors = Errors> {
69
+ readonly id: Id;
70
+ readonly errors: E;
71
+ /** A defined Mutation, `kind: 'mutation'`, writes: no read takes one. */
72
+ readonly kind?: never;
73
+ readonly '~input'?: Input;
74
+ readonly '~output'?: Output;
75
+ }
76
+ /**
77
+ * A Query whose single read is a page: the call sets its size and continues it. A `DefinedQuery`
78
+ * with its row type, not an interface of its own: a Domain's exported declaration names only
79
+ * `DefinedQuery`.
80
+ */
81
+ export type PageQuery<Id extends string = string, Input = unknown, Output = unknown, E extends Errors = Errors, Row = unknown> = DefinedQuery<Id, Input, Output, E> & {
82
+ readonly '~page'?: Row;
83
+ };
84
+ /** The input a Query is called with; a Query with no input is called with `undefined`. */
85
+ export type InputOf<R> = R extends DefinedQuery<string, infer I, unknown, Errors> ? I : never;
86
+ /** The output a Query answers. */
87
+ export type OutputOf<R> = R extends DefinedQuery<string, unknown, infer O, Errors> ? O : never;
88
+ type Output<S, Results, I, O, E extends Errors> = (results: Results, input: I, context: OutputContext<S, E>) => O;
89
+ /**
90
+ * Whether a type is portable JSON as far as its type can tell: a function, or an object that
91
+ * carries one (a Promise, a Date, a Map, a class with methods), is not. A value typed `unknown`
92
+ * may be JSON, so it passes, as does anything past eight levels.
93
+ */
94
+ type Portable<T, Depth extends readonly unknown[] = []> = unknown extends T ? true : Depth['length'] extends 8 ? true : T extends string | number | boolean | null | undefined ? true : T extends bigint | symbol | ((...args: never) => unknown) ? false : T extends readonly (infer Item)[] ? Portable<Item, [...Depth, unknown]> : {
95
+ [K in keyof T]-?: Portable<T[K], [...Depth, unknown]>;
96
+ }[keyof T] extends true ? true : false;
97
+ /** `output` returns portable JSON, checked by type (spec § API reference: Queries). */
98
+ type PortableOutput<O> = [Portable<O>] extends [true] ? unknown : '✗ output returns portable JSON: no function, Promise, Date, Map or other object with methods';
99
+ /** What `steps(…)` returns: the merged results of its steps, for `output`. */
100
+ export interface Stepped<out R> {
101
+ readonly '~stepped': R;
102
+ }
103
+ /** The results of what `build` returns: a read's, a page's, a record of facets', or the steps'. */
104
+ export type BuiltResults<B> = B extends Stepped<infer R> ? R : Results<B>;
105
+ /**
106
+ * `build`'s third parameter: reads that wait for others. The first step is a record of facets;
107
+ * each next one a function of the merged results of the steps before it, returning a record of
108
+ * facets none of them read. Two to eight steps, one overload per count, so a call's error is its
109
+ * own count's.
110
+ */
111
+ export interface Steps {
112
+ <B1 extends Facets, B2 extends Later<Results<B1>>>(first: B1, second: (earlier: Results<B1>) => B2): Stepped<Merged<B1, B2>>;
113
+ <B1 extends Facets, B2 extends Later<Results<B1>>, B3 extends Later<Merged<B1, B2>>>(first: B1, second: (earlier: Results<B1>) => B2, third: (earlier: Merged<B1, B2>) => B3): Stepped<Merged<B1, B2, B3>>;
114
+ <B1 extends Facets, B2 extends Later<Results<B1>>, B3 extends Later<Merged<B1, B2>>, B4 extends Later<Merged<B1, B2, B3>>>(first: B1, second: (earlier: Results<B1>) => B2, third: (earlier: Merged<B1, B2>) => B3, fourth: (earlier: Merged<B1, B2, B3>) => B4): Stepped<Merged<B1, B2, B3, B4>>;
115
+ <B1 extends Facets, B2 extends Later<Results<B1>>, B3 extends Later<Merged<B1, B2>>, B4 extends Later<Merged<B1, B2, B3>>, B5 extends Later<Merged<B1, B2, B3, B4>>>(first: B1, second: (earlier: Results<B1>) => B2, third: (earlier: Merged<B1, B2>) => B3, fourth: (earlier: Merged<B1, B2, B3>) => B4, fifth: (earlier: Merged<B1, B2, B3, B4>) => B5): Stepped<Merged<B1, B2, B3, B4, B5>>;
116
+ <B1 extends Facets, B2 extends Later<Results<B1>>, B3 extends Later<Merged<B1, B2>>, B4 extends Later<Merged<B1, B2, B3>>, B5 extends Later<Merged<B1, B2, B3, B4>>, B6 extends Later<Merged<B1, B2, B3, B4, B5>>>(first: B1, second: (earlier: Results<B1>) => B2, third: (earlier: Merged<B1, B2>) => B3, fourth: (earlier: Merged<B1, B2, B3>) => B4, fifth: (earlier: Merged<B1, B2, B3, B4>) => B5, sixth: (earlier: Merged<B1, B2, B3, B4, B5>) => B6): Stepped<Merged<B1, B2, B3, B4, B5, B6>>;
117
+ <B1 extends Facets, B2 extends Later<Results<B1>>, B3 extends Later<Merged<B1, B2>>, B4 extends Later<Merged<B1, B2, B3>>, B5 extends Later<Merged<B1, B2, B3, B4>>, B6 extends Later<Merged<B1, B2, B3, B4, B5>>, B7 extends Later<Merged<B1, B2, B3, B4, B5, B6>>>(first: B1, second: (earlier: Results<B1>) => B2, third: (earlier: Merged<B1, B2>) => B3, fourth: (earlier: Merged<B1, B2, B3>) => B4, fifth: (earlier: Merged<B1, B2, B3, B4>) => B5, sixth: (earlier: Merged<B1, B2, B3, B4, B5>) => B6, seventh: (earlier: Merged<B1, B2, B3, B4, B5, B6>) => B7): Stepped<Merged<B1, B2, B3, B4, B5, B6, B7>>;
118
+ <B1 extends Facets, B2 extends Later<Results<B1>>, B3 extends Later<Merged<B1, B2>>, B4 extends Later<Merged<B1, B2, B3>>, B5 extends Later<Merged<B1, B2, B3, B4>>, B6 extends Later<Merged<B1, B2, B3, B4, B5>>, B7 extends Later<Merged<B1, B2, B3, B4, B5, B6>>, B8 extends Later<Merged<B1, B2, B3, B4, B5, B6, B7>>>(first: B1, second: (earlier: Results<B1>) => B2, third: (earlier: Merged<B1, B2>) => B3, fourth: (earlier: Merged<B1, B2, B3>) => B4, fifth: (earlier: Merged<B1, B2, B3, B4>) => B5, sixth: (earlier: Merged<B1, B2, B3, B4, B5>) => B6, seventh: (earlier: Merged<B1, B2, B3, B4, B5, B6>) => B7, eighth: (earlier: Merged<B1, B2, B3, B4, B5, B6, B7>) => B8): Stepped<Merged<B1, B2, B3, B4, B5, B6, B7, B8>>;
119
+ }
120
+ /** `defineQuery<Schema>()`'s v7 form: a string id first. */
121
+ export interface DefinedQueryFactory<S> {
122
+ /**
123
+ * `build` returns one read, a record of facets, `steps(…)`, or a page as its single read, whose
124
+ * `output` keeps the page shape (the cursor stays the Kernel's). Its type is spelled out, not
125
+ * `PageQuery`: a Domain's exported declarations then name only `DefinedQuery`.
126
+ */
127
+ <const Id extends string, I, B extends Built | PageRead<unknown> | Stepped<unknown>, O = BuiltResults<B>, E extends Errors = {}>(id: Id, spec: {
128
+ readonly build: (input: I, d: Handles<S>, steps: Steps) => B;
129
+ readonly output?: Output<S, BuiltResults<B>, I, O & PageShape<B>, E> & PortableOutput<O>;
130
+ readonly errors?: E;
131
+ }): B extends PageRead<infer T> ? DefinedQuery<Id, I, O, E> & {
132
+ readonly '~page'?: T;
133
+ } : DefinedQuery<Id, I, O, E>;
134
+ }
135
+ export {};
@@ -1,12 +1,12 @@
1
1
  import type { Domain, schema } from '../../platform/schema/index.js';
2
2
  import type { QueryDefinition } from './query.js';
3
- /** The private projection of one recipe from its exact loaded Domain. */
3
+ /** The private projection of one Query definition from its exact loaded Domain. */
4
4
  export type QueryProjector = (domain: Domain) => Readonly<Record<string, unknown>>;
5
5
  export type ResolvedDomainOf<Schema extends schema.DomainSchema> = Extract<schema.DomainOf<Schema>, Domain>;
6
- /** One inert recipe of `kind`; its projector is retained privately. */
7
- export declare function recipe(kind: QueryDefinition['kind'], project: QueryProjector): QueryDefinition;
8
- /** The projector a recipe was registered with, if it was. */
6
+ /** One inert Query definition of `kind`; its projector is retained privately. */
7
+ export declare function inertQuery(kind: QueryDefinition['kind'], project: QueryProjector): QueryDefinition;
8
+ /** The projector a Query definition was registered with, if it was. */
9
9
  export declare function projectorOf(definition: object): QueryProjector | undefined;
10
- /** The fields every authored recipe shares: `id`, its operation, `prepare`, `project`, `reject`. */
10
+ /** The fields every authored Query definition shares: `id`, its operation, `prepare`, `project`, `reject`. */
11
11
  export declare function definitionBase(definition: Readonly<Record<string, unknown>>, operation: 'build' | 'compose'): void;
12
12
  export declare function isPromiseLike(input: unknown): input is PromiseLike<unknown>;
@@ -1,17 +1,17 @@
1
1
  const projectors = new WeakMap();
2
- /** One inert recipe of `kind`; its projector is retained privately. */
3
- export function recipe(kind, project) {
2
+ /** One inert Query definition of `kind`; its projector is retained privately. */
3
+ export function inertQuery(kind, project) {
4
4
  if (typeof project !== 'function')
5
5
  throw new TypeError('Query projection must be a function.');
6
6
  const definition = Object.freeze({ kind });
7
7
  projectors.set(definition, project);
8
8
  return definition;
9
9
  }
10
- /** The projector a recipe was registered with, if it was. */
10
+ /** The projector a Query definition was registered with, if it was. */
11
11
  export function projectorOf(definition) {
12
12
  return projectors.get(definition);
13
13
  }
14
- /** The fields every authored recipe shares: `id`, its operation, `prepare`, `project`, `reject`. */
14
+ /** The fields every authored Query definition shares: `id`, its operation, `prepare`, `project`, `reject`. */
15
15
  export function definitionBase(definition, operation) {
16
16
  if (definition === null || typeof definition !== 'object' || Array.isArray(definition))
17
17
  invalid();
@@ -1,12 +1,14 @@
1
1
  /**
2
- * The bounds a recipe keeps before anything is sent (S11, spec § Recipes: the contract): its
2
+ * The bounds a Query keeps before anything is sent (S11, spec § Queries: the contract): its
3
3
  * reads, its rows, each verb's bound, and the values of a property's `in`.
4
4
  */
5
5
  import type { ReadPlan } from '../handles/plan.js';
6
- import type { AnyQuery } from '../language/query.js';
7
- /** Each read of each stage, and each element of an `items.map`. */
6
+ import type { QueryDocument } from '../language/query.js';
7
+ /** Each read of each step, and each element of an `items.map`. */
8
8
  export declare const MAXIMUM_READS = 32;
9
- /** Across a recipe's nested lists. */
9
+ /** Steps of one Query: `steps(first, (earlier) => next, …)` takes two to this many. */
10
+ export declare const MAXIMUM_STEPS = 8;
11
+ /** Across a Query's nested lists. */
10
12
  export declare const MAXIMUM_ROWS = 100000;
11
13
  /** A verb's bound: its list, plus one for `all` and `first(n, { more })`, fits a Kernel page. */
12
14
  export declare const MAXIMUM_BOUND = 83332;
@@ -17,4 +19,4 @@ export declare function pastPage(plan: ReadPlan): boolean;
17
19
  /** The rows a read may answer, counted as the product of its verbs' bounds. */
18
20
  export declare function rowsOf(plan: ReadPlan): number;
19
21
  /** The first property `in` past its values, as a pointer into the document. */
20
- export declare function wideIn(document: AnyQuery): string | undefined;
22
+ export declare function wideIn(document: QueryDocument): string | undefined;
@@ -1,6 +1,8 @@
1
- /** Each read of each stage, and each element of an `items.map`. */
1
+ /** Each read of each step, and each element of an `items.map`. */
2
2
  export const MAXIMUM_READS = 32;
3
- /** Across a recipe's nested lists. */
3
+ /** Steps of one Query: `steps(first, (earlier) => next, …)` takes two to this many. */
4
+ export const MAXIMUM_STEPS = 8;
5
+ /** Across a Query's nested lists. */
4
6
  export const MAXIMUM_ROWS = 100_000;
5
7
  /** A verb's bound: its list, plus one for `all` and `first(n, { more })`, fits a Kernel page. */
6
8
  export const MAXIMUM_BOUND = 83_332;
@@ -0,0 +1,20 @@
1
+ import type { QueryPageRequest, QueryResponse } from '@astrale-os/kernel-client/graph';
2
+ import type { QueryAST, QueryResultFor } from '@astrale-os/kernel-core/graph/query';
3
+ import type { QueryDocument } from '../language/query.js';
4
+ /** What a Query v7 read is sent with: the cursor of the page it continues. */
5
+ export interface QueryRequestOptions {
6
+ readonly paging?: {
7
+ readonly after: string;
8
+ };
9
+ }
10
+ /**
11
+ * The client a Query reads through, as a Kernel Client session serves it: a v7 document per
12
+ * request, or a V6 Query's AST page by page.
13
+ */
14
+ export interface QueryClient {
15
+ query<Query extends QueryDocument>(query: Query, options: QueryRequestOptions): Promise<unknown>;
16
+ /** A V6 Query's read. It stays the last overload, which `Parameters` and `ReturnType` read. */
17
+ query<Ast extends QueryAST>(ast: Ast, options: {
18
+ readonly page: QueryPageRequest;
19
+ }): Promise<QueryResponse<QueryResultFor<Ast>>>;
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -12,8 +12,8 @@ export interface DeclaredClass {
12
12
  */
13
13
  export declare class UndeclaredCrossing extends TypeError {
14
14
  #private;
15
- constructor(recipe: string, message: string);
16
- get recipe(): string;
15
+ constructor(query: string, message: string);
16
+ get query(): string;
17
17
  }
18
18
  /** The S12 check of one Domain: its dependencies, and the operations it declares on their Classes. */
19
19
  export declare class Crossings {
@@ -21,5 +21,5 @@ export declare class Crossings {
21
21
  readonly model: Model;
22
22
  constructor(model: Model, domain: Domain, declared: readonly DeclaredClass[]);
23
23
  /** Throw when a relation of the read crosses a foreign Class its Domain does not declare. */
24
- check(recipe: string, plan: ReadPlan): void;
24
+ check(query: string, plan: ReadPlan): void;
25
25
  }
@@ -1,33 +1,40 @@
1
1
  /**
2
- * The Classes a recipe's relations cross, checked against what its Domain declares (S12, spec §
3
- * Details settled: "When S12 runs"). A relation that crosses a Class of a dependency needs `read`
4
- * and `traverse` on it in the Domain's requirements, or the read is a programming error, raised at
5
- * lowering before anything is sent, never answered as empty. It reads the document as written,
6
- * so an empty list the input gives refuses what any other list does. A requirement on a parent
7
- * covers its children, and an abstract Class with concrete children, each covered, is covered:
8
- * the check refuses only what no declaration serves.
2
+ * The Classes a Query's relations cross, checked against what its Domain declares (S12, spec §
3
+ * Details settled: "When S12 runs"). A relation that crosses a Class of a dependency other than the
4
+ * Kernel needs `read` and `traverse` on it in the Domain's requirements, or the read is a
5
+ * programming error, raised at lowering before anything is sent, never answered as empty. It reads
6
+ * the document as written, so an empty list the input gives refuses what any other list does. A
7
+ * requirement on a parent covers its children, and an abstract Class with concrete children, each
8
+ * covered, is covered: the check refuses only what no declaration serves.
9
9
  */
10
10
  import { Key } from '@astrale-os/kernel-dsl/v1/addressing';
11
11
  /** What a relation needs on each foreign Class it crosses. */
12
12
  const NEEDED = ['read', 'traverse'];
13
+ /**
14
+ * The Kernel's origin. Its Classes need nothing, even when the Domain declares it a dependency, but
15
+ * the dependency Classes under an abstract one (`Identity`, `Node`) each do.
16
+ */
17
+ const KERNEL = 'kernel.astrale.ai';
18
+ /** The Kernel's abstract Edge Class. Every Edge Class is under it, though none names it a parent. */
19
+ const EDGE = `${KERNEL}:class.Edge`;
13
20
  /**
14
21
  * The S12 refusal: a `TypeError`, the same for every input and every attempt, so a durable Step
15
22
  * fails at once on it.
16
23
  */
17
24
  export class UndeclaredCrossing extends TypeError {
18
- #recipe;
19
- constructor(recipe, message) {
25
+ #query;
26
+ constructor(query, message) {
20
27
  super(message);
21
- this.#recipe = recipe;
28
+ this.#query = query;
22
29
  }
23
- get recipe() {
24
- return this.#recipe;
30
+ get query() {
31
+ return this.#query;
25
32
  }
26
33
  }
27
34
  /** The S12 check of one Domain: its dependencies, and the operations it declares on their Classes. */
28
35
  export class Crossings {
29
36
  model;
30
- /** Each direct dependency's origin, by the alias the Domain gives it. */
37
+ /** Each direct dependency's origin but the Kernel's, by the alias the Domain gives it. */
31
38
  #dependencies = new Map();
32
39
  /** The operations declared on each Class, by key. */
33
40
  #declared = new Map();
@@ -36,7 +43,8 @@ export class Crossings {
36
43
  constructor(model, domain, declared) {
37
44
  this.model = model;
38
45
  for (const [alias, dependency] of Object.entries(domain.dependencies))
39
- this.#dependencies.set(dependency.origin, alias);
46
+ if (dependency.origin !== KERNEL)
47
+ this.#dependencies.set(dependency.origin, alias);
40
48
  for (const requirement of declared) {
41
49
  const key = Key.of(requirement.class);
42
50
  const operations = this.#declared.get(key) ?? new Set();
@@ -48,52 +56,52 @@ export class Crossings {
48
56
  .filter((c) => !c.abstract);
49
57
  }
50
58
  /** Throw when a relation of the read crosses a foreign Class its Domain does not declare. */
51
- check(recipe, plan) {
59
+ check(query, plan) {
52
60
  const document = (plan.written ?? plan.document);
53
61
  const root = (document?.list ?? document?.exists ?? document?.count);
54
62
  if (root === undefined)
55
63
  return;
56
64
  const branches = plan.root?.kind === 'nodes' ? plan.root.branches : undefined;
57
- this.#where(recipe, root.where, undefined);
58
- this.#branches(recipe, root.with, branches, undefined);
65
+ this.#where(query, root.where, undefined);
66
+ this.#branches(query, root.with, branches, undefined);
59
67
  }
60
- #branches(recipe, value, plans, parent) {
68
+ #branches(query, value, plans, parent) {
61
69
  for (const branch of (value ?? [])) {
62
70
  const plan = plans?.find((p) => p.name === branch.name);
63
71
  const name = plan?.key ?? branch.name;
64
72
  const relation = parent === undefined ? name : `${parent}.${name}`;
65
- this.#traversal(recipe, branch, `its relation ${relation}`);
66
- this.#branches(recipe, branch.with, plan?.branches, relation);
73
+ this.#traversal(query, branch, `its relation ${relation}`);
74
+ this.#branches(query, branch.with, plan?.branches, relation);
67
75
  }
68
76
  }
69
77
  /** The traversals a condition holds, at any depth. */
70
- #where(recipe, value, at) {
78
+ #where(query, value, at) {
71
79
  const where = value;
72
80
  switch (where?.op) {
73
81
  case 'and':
74
82
  case 'or':
75
83
  for (const operand of where.operands)
76
- this.#where(recipe, operand, at);
84
+ this.#where(query, operand, at);
77
85
  return;
78
86
  case 'not':
79
87
  case 'source':
80
88
  case 'target':
81
- return this.#where(recipe, where.operand, at);
89
+ return this.#where(query, where.operand, at);
82
90
  case 'exists':
83
- return this.#traversal(recipe, where, at === undefined ? 'a condition' : `${at}'s condition`);
91
+ return this.#traversal(query, where, at === undefined ? 'a condition' : `${at}'s condition`);
84
92
  }
85
93
  }
86
94
  /** The Edge Classes of `via`, the Node Classes they reach, then the traversal's own conditions. */
87
- #traversal(recipe, traversal, label) {
95
+ #traversal(query, traversal, label) {
88
96
  const via = traversal.via;
89
97
  const direction = traversal.direction;
90
98
  const edges = via.flatMap((key) => this.#known(key));
91
99
  const relation = `${label} (via ${via.join(', ')}, ${direction})`;
92
100
  const reached = this.#narrowed(this.model.reached(edges, direction), traversal.node);
93
101
  for (const c of [...edges, ...reached])
94
- this.#require(recipe, relation, c);
95
- this.#where(recipe, traversal.edge, label);
96
- this.#where(recipe, traversal.node, label);
102
+ this.#require(query, relation, c);
103
+ this.#where(query, traversal.edge, label);
104
+ this.#where(query, traversal.node, label);
97
105
  }
98
106
  /**
99
107
  * The reached Classes the node condition keeps. An `is` or `isExactly` conjunct, or an `or` of
@@ -114,27 +122,42 @@ export class Crossings {
114
122
  }
115
123
  return [...new Set(classes)];
116
124
  }
117
- #require(recipe, relation, c) {
125
+ #require(query, relation, c) {
126
+ if (c.ref.origin === KERNEL) {
127
+ if (c.abstract)
128
+ for (const child of this.#under(c))
129
+ this.#require(query, relation, child);
130
+ return;
131
+ }
118
132
  const alias = this.#dependencies.get(c.ref.origin);
119
133
  if (alias === undefined)
120
134
  return;
121
135
  const missing = this.#missing(c);
122
136
  if (missing.length === 0 || (c.abstract && this.#covered(c)))
123
137
  return;
124
- throw new UndeclaredCrossing(recipe, `Recipe ${recipe}: ${relation} crosses ${c.key}, a Class of the dependency ${alias}, on ` +
138
+ throw new UndeclaredCrossing(query, `Query ${query}: ${relation} crosses ${c.key}, a Class of the dependency ${alias}, on ` +
125
139
  `which the Domain declares no ${missing.join(' or ')}. Its requirements need ` +
126
140
  `{ class: ${c.key}, operations: ['read', 'traverse'] } in classes.`);
127
141
  }
128
142
  /** What a Class lacks of `read` and `traverse`, declared on it or on a parent. */
129
143
  #missing(c) {
130
- const declared = new Set([...this.model.satisfies(c)].flatMap((key) => [...(this.#declared.get(key) ?? [])]));
144
+ const declared = new Set([...this.#satisfies(c)].flatMap((key) => [...(this.#declared.get(key) ?? [])]));
131
145
  return NEEDED.filter((operation) => !declared.has(operation));
132
146
  }
133
147
  /** An abstract Class has concrete Classes under it, and the Domain may cross each. */
134
148
  #covered(c) {
135
- const children = this.#concrete.filter((k) => this.model.satisfies(k).has(c.key));
149
+ const children = this.#under(c);
136
150
  return children.length > 0 && children.every((k) => this.#served(k));
137
151
  }
152
+ /** The concrete Classes under a Class, of the Domain and its dependencies. */
153
+ #under(c) {
154
+ return this.#concrete.filter((k) => this.#satisfies(k).has(c.key));
155
+ }
156
+ /** The keys a Class satisfies, the Kernel's `Edge` included for an Edge Class. */
157
+ #satisfies(c) {
158
+ const keys = this.model.satisfies(c);
159
+ return c.kind === 'edge' ? new Set([...keys, EDGE]) : keys;
160
+ }
138
161
  /** Whether a relation may cross a Class: the Domain's own, or a dependency's it declares. */
139
162
  #served(c) {
140
163
  return !this.#dependencies.has(c.ref.origin) || this.#missing(c).length === 0;