@astrale-os/sdk 0.6.0-beta.40 → 0.6.0-beta.42
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 +22 -2
- package/dist/application/action/context.d.ts +49 -18
- package/dist/application/action/index.d.ts +1 -1
- package/dist/application/error/error.d.ts +16 -1
- package/dist/application/error/error.js +39 -5
- package/dist/application/error/index.d.ts +1 -1
- package/dist/application/error/index.js +1 -1
- package/dist/application/mutation/authoring/builder.d.ts +81 -33
- package/dist/application/mutation/authoring/builder.js +100 -30
- package/dist/application/mutation/authoring/guard.d.ts +37 -0
- package/dist/application/mutation/authoring/guard.js +150 -0
- package/dist/application/mutation/authoring/index.d.ts +5 -2
- package/dist/application/mutation/authoring/index.js +2 -1
- package/dist/application/mutation/authoring/names.d.ts +19 -0
- package/dist/application/mutation/authoring/names.js +83 -0
- package/dist/application/mutation/authoring/transition.d.ts +3 -2
- package/dist/application/mutation/define.d.ts +24 -10
- package/dist/application/mutation/define.js +49 -53
- package/dist/application/mutation/execution/execute.d.ts +2 -2
- package/dist/application/mutation/execution/execute.js +78 -7
- package/dist/application/mutation/execution/executor.d.ts +4 -1
- package/dist/application/mutation/execution/executor.js +6 -1
- package/dist/application/mutation/failure.d.ts +11 -1
- package/dist/application/mutation/failure.js +18 -3
- package/dist/application/mutation/index.d.ts +8 -4
- package/dist/application/mutation/index.js +5 -2
- package/dist/application/mutation/legacy/v6/builder.d.ts +84 -0
- package/dist/application/mutation/legacy/v6/define.d.ts +18 -0
- package/dist/application/mutation/legacy/v6/define.js +56 -0
- package/dist/application/mutation/legacy/v6/mutation.d.ts +28 -0
- package/dist/application/mutation/legacy/v6/mutation.js +1 -0
- package/dist/application/mutation/{authoring → legacy/v6}/precondition.d.ts +4 -0
- package/dist/application/mutation/mutation.d.ts +30 -19
- package/dist/application/query/answer/answer.d.ts +117 -0
- package/dist/application/query/answer/answer.js +1 -0
- package/dist/application/query/answer/conformance.d.ts +4 -0
- package/dist/application/query/answer/conformance.js +27 -0
- package/dist/application/query/answer/facts.d.ts +17 -0
- package/dist/application/query/answer/facts.js +57 -0
- package/dist/application/query/composite/builder.d.ts +1 -1
- package/dist/application/query/composite/builder.js +1 -1
- package/dist/application/query/composite/plan.d.ts +1 -1
- package/dist/application/query/composite/plan.js +1 -1
- package/dist/application/query/composite/query.d.ts +1 -2
- package/dist/application/query/define.d.ts +2 -33
- package/dist/application/query/define.js +3 -130
- package/dist/application/query/definition.d.ts +12 -0
- package/dist/application/query/definition.js +47 -0
- package/dist/application/query/execution/bounds.d.ts +20 -0
- package/dist/application/query/execution/bounds.js +86 -0
- package/dist/application/query/execution/crossings.d.ts +25 -0
- package/dist/application/query/execution/crossings.js +169 -0
- package/dist/application/query/execution/error.d.ts +68 -0
- package/dist/application/query/execution/error.js +50 -0
- package/dist/application/query/execution/execute.d.ts +2 -7
- package/dist/application/query/execution/execute.js +3 -51
- package/dist/application/query/execution/recipe.d.ts +70 -0
- package/dist/application/query/execution/recipe.js +276 -0
- package/dist/application/query/execution/refusal.d.ts +37 -0
- package/dist/application/query/execution/refusal.js +80 -0
- package/dist/application/query/execution/rows.d.ts +42 -0
- package/dist/application/query/execution/rows.js +308 -0
- package/dist/application/query/execution/split.d.ts +24 -0
- package/dist/application/query/execution/split.js +190 -0
- package/dist/application/query/handles/build.d.ts +10 -0
- package/dist/application/query/handles/build.js +53 -0
- package/dist/application/query/handles/conditions.d.ts +94 -0
- package/dist/application/query/handles/conditions.js +1 -0
- package/dist/application/query/handles/domain.d.ts +157 -0
- package/dist/application/query/handles/domain.js +1 -0
- package/dist/application/query/handles/handles.d.ts +268 -0
- package/dist/application/query/handles/handles.js +1 -0
- package/dist/application/query/handles/model.d.ts +96 -0
- package/dist/application/query/handles/model.js +246 -0
- package/dist/application/query/handles/plan.d.ts +99 -0
- package/dist/application/query/handles/plan.js +37 -0
- package/dist/application/query/handles/reads.d.ts +106 -0
- package/dist/application/query/handles/reads.js +839 -0
- package/dist/application/query/handles/relations.d.ts +100 -0
- package/dist/application/query/handles/relations.js +1 -0
- package/dist/application/query/handles/rows.d.ts +94 -0
- package/dist/application/query/handles/rows.js +4 -0
- package/dist/application/query/handles/terms.d.ts +95 -0
- package/dist/application/query/handles/terms.js +265 -0
- package/dist/application/query/index.d.ts +10 -7
- package/dist/application/query/index.js +9 -4
- package/dist/application/query/language/index.d.ts +2 -0
- package/dist/application/query/language/index.js +2 -0
- package/dist/application/query/language/query.d.ts +323 -0
- package/dist/application/query/language/query.js +2 -0
- package/dist/application/query/{collection → legacy/v6/collection}/identity.d.ts +2 -2
- package/dist/application/query/{collection → legacy/v6/collection}/metadata.d.ts +1 -1
- package/dist/application/query/{collection → legacy/v6/collection}/union.d.ts +3 -3
- package/dist/application/query/{collection → legacy/v6/collection}/union.js +1 -1
- package/dist/application/query/{conformance.d.ts → legacy/v6/conformance.d.ts} +3 -3
- package/dist/application/query/{conformance.js → legacy/v6/conformance.js} +1 -1
- package/dist/application/query/legacy/v6/define.d.ts +37 -0
- package/dist/application/query/legacy/v6/define.js +90 -0
- package/dist/application/query/legacy/v6/execution/execute.d.ts +16 -0
- package/dist/application/query/legacy/v6/execution/execute.js +50 -0
- package/dist/application/query/legacy/v6/index.d.ts +6 -0
- package/dist/application/query/legacy/v6/index.js +3 -0
- package/dist/application/query/legacy/v6/kernel.d.ts +6 -0
- package/dist/application/query/legacy/v6/kernel.js +5 -0
- package/dist/application/query/{single → legacy/v6/single}/pagination.js +1 -1
- package/dist/application/query/{single → legacy/v6/single}/query.d.ts +2 -14
- package/dist/application/query/legacy/v6/single/query.js +1 -0
- package/dist/application/query/query.d.ts +16 -1
- package/dist/application/query/recipe/define.d.ts +45 -0
- package/dist/application/query/recipe/define.js +102 -0
- package/dist/application/query/recipe/recipe.d.ts +134 -0
- package/dist/application/query/recipe/recipe.js +1 -0
- package/dist/application/workflow/durable.d.ts +46 -20
- package/dist/execution/invocation/capabilities/bind.d.ts +6 -4
- package/dist/execution/invocation/capabilities/bind.js +39 -3
- package/dist/execution/invocation/dispatch/action.js +4 -2
- package/dist/execution/invocation/dispatch/context.js +1 -1
- package/dist/execution/invocation/dispatch/workflow.js +4 -2
- package/dist/execution/invocation/result/index.d.ts +1 -0
- package/dist/execution/invocation/result/index.js +1 -0
- package/dist/execution/invocation/result/recipe.d.ts +6 -0
- package/dist/execution/invocation/result/recipe.js +17 -0
- package/dist/execution/service/create.js +1 -0
- package/dist/execution/workflows/attempts.d.ts +87 -0
- package/dist/execution/workflows/attempts.js +205 -0
- package/dist/execution/workflows/engine.d.ts +6 -12
- package/dist/execution/workflows/failure.d.ts +27 -17
- package/dist/execution/workflows/failure.js +87 -44
- package/dist/execution/workflows/index.d.ts +1 -1
- package/dist/execution/workflows/options.d.ts +21 -6
- package/dist/execution/workflows/options.js +28 -18
- package/dist/execution/workflows/receiver.d.ts +3 -0
- package/dist/execution/workflows/receiver.js +1 -0
- package/dist/execution/workflows/run.d.ts +2 -0
- package/dist/execution/workflows/run.js +130 -82
- package/dist/execution/workflows/steps.d.ts +10 -1
- package/dist/execution/workflows/steps.js +34 -8
- package/dist/platform/schema/kernel-compiled.d.ts +2 -0
- package/dist/platform/schema/kernel-compiled.js +2 -0
- package/dist/testing/durable/engine.d.ts +18 -11
- package/dist/testing/durable/engine.js +43 -38
- package/dist/testing/durable/index.d.ts +2 -2
- package/dist/testing/durable/index.js +1 -1
- package/dist/testing/durable/steps.js +9 -4
- package/dist/testing/index.d.ts +4 -2
- package/dist/testing/index.js +2 -1
- package/dist/testing/recipe.d.ts +17 -0
- package/dist/testing/recipe.js +29 -0
- package/dist/tooling/cli/orchestrate.js +6 -2
- package/dist/tooling/linter/adapters/typescript/index.d.ts +2 -1
- package/dist/tooling/linter/adapters/typescript/index.js +2 -1
- package/dist/tooling/linter/adapters/typescript/query-observation.js +37 -12
- package/dist/tooling/linter/adapters/typescript/recipe-definitions.d.ts +34 -0
- package/dist/tooling/linter/adapters/typescript/recipe-definitions.js +88 -0
- package/dist/tooling/linter/adapters/typescript/type-analysis.d.ts +2 -0
- package/dist/tooling/linter/adapters/typescript/type-analysis.js +10 -0
- package/dist/tooling/linter/implementations/source/global.js +15 -9
- package/dist/tooling/linter/implementations/source/mutations.js +128 -19
- package/dist/tooling/linter/implementations/source/policy-edges.d.ts +2 -0
- package/dist/tooling/linter/implementations/source/policy-edges.js +113 -0
- package/dist/tooling/linter/implementations/source/recipes.d.ts +2 -0
- package/dist/tooling/linter/implementations/source/recipes.js +409 -0
- package/dist/tooling/linter/implementations/source/shared.d.ts +7 -1
- package/dist/tooling/linter/implementations/source/shared.js +31 -10
- package/dist/tooling/linter/implementations/source/verifiers.js +4 -0
- package/dist/tooling/linter/implementations/source/workflows.js +0 -1
- package/dist/tooling/linter/policy/generated.js +2 -2
- package/dist/tooling/linter/requirements/registry.js +4 -0
- package/docs/durable-workflows.md +117 -62
- package/docs/handler-dependencies.md +76 -4
- package/docs/legacy/query-v6.md +62 -0
- package/docs/queries.md +438 -0
- package/package.json +11 -8
- /package/dist/application/{query/single/query.js → mutation/legacy/v6/builder.js} +0 -0
- /package/dist/application/mutation/{authoring → legacy/v6}/precondition.js +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/identity.js +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/limits.d.ts +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/limits.js +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/metadata.js +0 -0
- /package/dist/application/query/{execution → legacy/v6/execution}/pages.d.ts +0 -0
- /package/dist/application/query/{execution → legacy/v6/execution}/pages.js +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/index.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/index.js +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/node.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/node.js +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/pagination.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/selection.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/selection.js +0 -0
|
@@ -1,34 +1,7 @@
|
|
|
1
|
-
import type { QueryAST } from '@astrale-os/kernel-core/graph/query';
|
|
2
|
-
import type { AnyResolvedClass } from '@astrale-os/kernel-dsl/v1';
|
|
3
1
|
import type { Domain, schema } from '../../platform/schema/index.js';
|
|
4
2
|
import type { ProjectedCompositeQuery, QueryComposition, QueryPlan, QueryPlanOutput, RawCompositeQuery } from './composite/index.js';
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
7
|
-
import { type CollectionQuerySpec } from './collection/metadata.js';
|
|
8
|
-
type AuthoredRaw<Input, Ast extends QueryAST> = Omit<RawQuery<Input, Ast>, 'kind' | 'page'> & {
|
|
9
|
-
readonly page?: {
|
|
10
|
-
readonly size: number;
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
type AuthoredProjected<Input, Output, Ast extends QueryAST> = Omit<ProjectedQuery<Input, Output, Ast>, 'kind' | 'page'> & {
|
|
14
|
-
readonly page?: {
|
|
15
|
-
readonly size: number;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
type AuthoredPaginated<Input, Output, Ast extends QueryAST> = Omit<PaginatedQuery<Input, Output, Ast>, 'kind' | 'page'> & {
|
|
19
|
-
readonly page?: {
|
|
20
|
-
readonly size: number;
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
type ResolvedDomainOf<Schema extends schema.DomainSchema> = Extract<schema.DomainOf<Schema>, Domain>;
|
|
24
|
-
interface SingleQueryProjector<Schema extends schema.DomainSchema> {
|
|
25
|
-
<Input, Ast extends QueryAST>(project: (domain: ResolvedDomainOf<Schema>) => AuthoredRaw<Input, Ast>): SingleQueryDefinition<ResolvedDomainOf<Schema>, Input, RawQuery<Input, Ast> extends SingleQuery<Input, infer Output> ? Output : never>;
|
|
26
|
-
<Input, Output, Ast extends QueryAST>(project: (domain: ResolvedDomainOf<Schema>) => AuthoredProjected<Input, Output, Ast>): SingleQueryDefinition<ResolvedDomainOf<Schema>, Input, Output>;
|
|
27
|
-
<Input, Output, Ast extends QueryAST>(project: (domain: ResolvedDomainOf<Schema>) => AuthoredPaginated<Input, Output, Ast>): SingleQueryDefinition<ResolvedDomainOf<Schema>, Input, Output>;
|
|
28
|
-
}
|
|
29
|
-
interface CollectionQueryProjector<Schema extends schema.DomainSchema> {
|
|
30
|
-
<Class extends AnyResolvedClass>(project: (domain: ResolvedDomainOf<Schema>) => CollectionQuerySpec<Class>): CollectionQueryDefinition<ResolvedDomainOf<Schema>, Class>;
|
|
31
|
-
}
|
|
3
|
+
import type { ResolvedDomainOf } from './definition.js';
|
|
4
|
+
import type { CompositeQueryDefinition, QueryDefinition, RealizedQuery } from './query.js';
|
|
32
5
|
type AuthoredInferredComposite<Input, Composed, Plan extends QueryPlan<unknown>, Output, DomainValue extends Domain> = Omit<ProjectedCompositeQuery<Input, Composed, Output, DomainValue>, 'kind' | 'compose' | 'project'> & {
|
|
33
6
|
compose(builder: QueryComposition<DomainValue>, input: Input): Plan;
|
|
34
7
|
project(value: QueryPlanOutput<Plan>, input: Input): Output;
|
|
@@ -38,10 +11,6 @@ interface CompositeQueryProjector<Schema extends schema.DomainSchema> {
|
|
|
38
11
|
<Input, Output>(project: (domain: ResolvedDomainOf<Schema>) => Omit<RawCompositeQuery<Input, Output, ResolvedDomainOf<Schema>>, 'kind'>): CompositeQueryDefinition<ResolvedDomainOf<Schema>, Input, Output>;
|
|
39
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>;
|
|
40
13
|
}
|
|
41
|
-
/** Define one inert single Query recipe projected from its exact loaded Domain. */
|
|
42
|
-
export declare function defineQuery<Schema extends schema.DomainSchema>(): SingleQueryProjector<Schema>;
|
|
43
|
-
/** Define one reusable complete polymorphic Class-extent Query. */
|
|
44
|
-
export declare function defineCollectionQuery<Schema extends schema.DomainSchema>(): CollectionQueryProjector<Schema>;
|
|
45
14
|
/** Define one inert composite Query recipe projected from its exact loaded Domain. */
|
|
46
15
|
export declare function defineCompositeQuery<Schema extends schema.DomainSchema>(): CompositeQueryProjector<Schema>;
|
|
47
16
|
export declare function realizeQuery<DomainValue extends Domain, Input, Output>(query: QueryDefinition<DomainValue, Input, Output>, domain: DomainValue): RealizedQuery<Input, Output>;
|
|
@@ -1,62 +1,13 @@
|
|
|
1
|
-
import { Query } from '@astrale-os/kernel-core/graph/query';
|
|
2
1
|
import { isDomain } from '@astrale-os/kernel-dsl/v1/domain';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { realizeCollectionQuery, registerCollectionQuery, } from './collection/metadata.js';
|
|
6
|
-
import { definedValues } from './conformance.js';
|
|
7
|
-
import { queryResult } from './single/index.js';
|
|
8
|
-
const DEFAULT_PAGE_SIZE = 256;
|
|
9
|
-
const MAXIMUM_PAGES = 1_024;
|
|
10
|
-
const projectors = new WeakMap();
|
|
2
|
+
import { definitionBase, isPromiseLike, projectorOf, recipe } from './definition.js';
|
|
3
|
+
import { admitSingle } from './legacy/v6/define.js';
|
|
11
4
|
const realized = new WeakMap();
|
|
12
|
-
/** Define one inert single Query recipe projected from its exact loaded Domain. */
|
|
13
|
-
export function defineQuery() {
|
|
14
|
-
return ((project) => recipe('query', project));
|
|
15
|
-
}
|
|
16
|
-
/** Define one reusable complete polymorphic Class-extent Query. */
|
|
17
|
-
export function defineCollectionQuery() {
|
|
18
|
-
return ((project) => {
|
|
19
|
-
const definition = defineQuery()((domain) => {
|
|
20
|
-
const specification = realizeCollectionQuery(definition, domain);
|
|
21
|
-
const selected = specification.class;
|
|
22
|
-
if (selected.kind === 'node') {
|
|
23
|
-
return {
|
|
24
|
-
id: specification.id,
|
|
25
|
-
page: { size: COLLECTION_QUERY_LIMITS.pageSize },
|
|
26
|
-
pagination: { maximumPages: COLLECTION_QUERY_LIMITS.maximumPages },
|
|
27
|
-
build: () => Query.from({ nodes: [selected] }).select({
|
|
28
|
-
kind: 'nodes',
|
|
29
|
-
projection: { kind: 'value' },
|
|
30
|
-
}),
|
|
31
|
-
project: (pages) => definedValues(domain, requireUniqueCollectionValues(queryResult.completeNodeValues(pages, specification.id), `Collection Query ${specification.id}`)),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
id: specification.id,
|
|
36
|
-
page: { size: COLLECTION_QUERY_LIMITS.pageSize },
|
|
37
|
-
pagination: { maximumPages: COLLECTION_QUERY_LIMITS.maximumPages },
|
|
38
|
-
build: () => Query.from({ edges: [selected] }).select({
|
|
39
|
-
kind: 'edges',
|
|
40
|
-
projection: { edge: 'value', source: 'reference', target: 'reference' },
|
|
41
|
-
}),
|
|
42
|
-
project: (pages) => definedValues(domain, requireUniqueCollectionValues(Object.freeze(pages.flatMap((page) => queryResult.edgeItems(page, specification.id).map((item) => {
|
|
43
|
-
if (item.edge.kind !== 'value') {
|
|
44
|
-
throw new TypeError(`Collection Query ${specification.id} must project Edge values.`);
|
|
45
|
-
}
|
|
46
|
-
return item.edge.value;
|
|
47
|
-
}))), `Collection Query ${specification.id}`)),
|
|
48
|
-
};
|
|
49
|
-
});
|
|
50
|
-
registerCollectionQuery(definition, project);
|
|
51
|
-
return definition;
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
5
|
/** Define one inert composite Query recipe projected from its exact loaded Domain. */
|
|
55
6
|
export function defineCompositeQuery() {
|
|
56
7
|
return ((project) => recipe('composite-query', project));
|
|
57
8
|
}
|
|
58
9
|
export function realizeQuery(query, domain) {
|
|
59
|
-
const project =
|
|
10
|
+
const project = projectorOf(query);
|
|
60
11
|
if (project === undefined)
|
|
61
12
|
throw new TypeError('Query must be produced by defineQuery.');
|
|
62
13
|
if (!isDomain(domain))
|
|
@@ -76,37 +27,6 @@ export function realizeQuery(query, domain) {
|
|
|
76
27
|
byDomain.set(domain, admitted);
|
|
77
28
|
return admitted;
|
|
78
29
|
}
|
|
79
|
-
function recipe(kind, project) {
|
|
80
|
-
if (typeof project !== 'function')
|
|
81
|
-
throw new TypeError('Query projection must be a function.');
|
|
82
|
-
const definition = Object.freeze({ kind });
|
|
83
|
-
projectors.set(definition, project);
|
|
84
|
-
return definition;
|
|
85
|
-
}
|
|
86
|
-
function admitSingle(definition) {
|
|
87
|
-
definitionBase(definition, 'build');
|
|
88
|
-
const page = pageOf(definition.page);
|
|
89
|
-
const pagination = definition.pagination;
|
|
90
|
-
if (pagination !== undefined) {
|
|
91
|
-
if (typeof definition.project !== 'function') {
|
|
92
|
-
throw new TypeError('Paginated Query requires a project function.');
|
|
93
|
-
}
|
|
94
|
-
if (pagination === null ||
|
|
95
|
-
typeof pagination !== 'object' ||
|
|
96
|
-
Array.isArray(pagination) ||
|
|
97
|
-
Reflect.ownKeys(pagination).length !== 1 ||
|
|
98
|
-
!Object.hasOwn(pagination, 'maximumPages')) {
|
|
99
|
-
throw new TypeError('Query pagination is invalid.');
|
|
100
|
-
}
|
|
101
|
-
const maximumPages = Reflect.get(pagination, 'maximumPages');
|
|
102
|
-
if (!Number.isSafeInteger(maximumPages) ||
|
|
103
|
-
maximumPages < 1 ||
|
|
104
|
-
maximumPages > MAXIMUM_PAGES) {
|
|
105
|
-
throw new TypeError(`Query pagination must be bounded from 1 through ${MAXIMUM_PAGES}.`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return Object.freeze({ ...definition, page, kind: 'single' });
|
|
109
|
-
}
|
|
110
30
|
function admitComposite(definition) {
|
|
111
31
|
definitionBase(definition, 'compose');
|
|
112
32
|
if (Object.hasOwn(definition, 'page') || Object.hasOwn(definition, 'pagination')) {
|
|
@@ -114,50 +34,3 @@ function admitComposite(definition) {
|
|
|
114
34
|
}
|
|
115
35
|
return Object.freeze({ ...definition, kind: 'composite' });
|
|
116
36
|
}
|
|
117
|
-
function definitionBase(definition, operation) {
|
|
118
|
-
if (definition === null || typeof definition !== 'object' || Array.isArray(definition))
|
|
119
|
-
invalid();
|
|
120
|
-
if (typeof definition.id !== 'string' ||
|
|
121
|
-
definition.id.length === 0 ||
|
|
122
|
-
definition.id.trim() !== definition.id ||
|
|
123
|
-
typeof definition[operation] !== 'function') {
|
|
124
|
-
invalid();
|
|
125
|
-
}
|
|
126
|
-
for (const optional of ['prepare', 'project', 'reject']) {
|
|
127
|
-
if (definition[optional] !== undefined && typeof definition[optional] !== 'function')
|
|
128
|
-
invalid();
|
|
129
|
-
}
|
|
130
|
-
const allowed = new Set([
|
|
131
|
-
'id',
|
|
132
|
-
operation,
|
|
133
|
-
'prepare',
|
|
134
|
-
'project',
|
|
135
|
-
'reject',
|
|
136
|
-
...(operation === 'build' ? ['page', 'pagination'] : []),
|
|
137
|
-
]);
|
|
138
|
-
if (Reflect.ownKeys(definition).some((key) => typeof key !== 'string' || !allowed.has(key))) {
|
|
139
|
-
invalid();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
function pageOf(input) {
|
|
143
|
-
if (input === undefined)
|
|
144
|
-
return Object.freeze({ size: DEFAULT_PAGE_SIZE });
|
|
145
|
-
if (input === null ||
|
|
146
|
-
typeof input !== 'object' ||
|
|
147
|
-
Array.isArray(input) ||
|
|
148
|
-
Reflect.ownKeys(input).length !== 1 ||
|
|
149
|
-
!Object.hasOwn(input, 'size') ||
|
|
150
|
-
!Number.isSafeInteger(Reflect.get(input, 'size')) ||
|
|
151
|
-
Reflect.get(input, 'size') < 1) {
|
|
152
|
-
throw new TypeError('Query page size must be a positive safe integer.');
|
|
153
|
-
}
|
|
154
|
-
return Object.freeze({ size: Reflect.get(input, 'size') });
|
|
155
|
-
}
|
|
156
|
-
function invalid() {
|
|
157
|
-
throw new TypeError('Query definition is invalid.');
|
|
158
|
-
}
|
|
159
|
-
function isPromiseLike(input) {
|
|
160
|
-
return (input !== null &&
|
|
161
|
-
(typeof input === 'object' || typeof input === 'function') &&
|
|
162
|
-
typeof input.then === 'function');
|
|
163
|
-
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Domain, schema } from '../../platform/schema/index.js';
|
|
2
|
+
import type { QueryDefinition } from './query.js';
|
|
3
|
+
/** The private projection of one recipe from its exact loaded Domain. */
|
|
4
|
+
export type QueryProjector = (domain: Domain) => Readonly<Record<string, unknown>>;
|
|
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. */
|
|
9
|
+
export declare function projectorOf(definition: object): QueryProjector | undefined;
|
|
10
|
+
/** The fields every authored recipe shares: `id`, its operation, `prepare`, `project`, `reject`. */
|
|
11
|
+
export declare function definitionBase(definition: Readonly<Record<string, unknown>>, operation: 'build' | 'compose'): void;
|
|
12
|
+
export declare function isPromiseLike(input: unknown): input is PromiseLike<unknown>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const projectors = new WeakMap();
|
|
2
|
+
/** One inert recipe of `kind`; its projector is retained privately. */
|
|
3
|
+
export function recipe(kind, project) {
|
|
4
|
+
if (typeof project !== 'function')
|
|
5
|
+
throw new TypeError('Query projection must be a function.');
|
|
6
|
+
const definition = Object.freeze({ kind });
|
|
7
|
+
projectors.set(definition, project);
|
|
8
|
+
return definition;
|
|
9
|
+
}
|
|
10
|
+
/** The projector a recipe was registered with, if it was. */
|
|
11
|
+
export function projectorOf(definition) {
|
|
12
|
+
return projectors.get(definition);
|
|
13
|
+
}
|
|
14
|
+
/** The fields every authored recipe shares: `id`, its operation, `prepare`, `project`, `reject`. */
|
|
15
|
+
export function definitionBase(definition, operation) {
|
|
16
|
+
if (definition === null || typeof definition !== 'object' || Array.isArray(definition))
|
|
17
|
+
invalid();
|
|
18
|
+
if (typeof definition.id !== 'string' ||
|
|
19
|
+
definition.id.length === 0 ||
|
|
20
|
+
definition.id.trim() !== definition.id ||
|
|
21
|
+
typeof definition[operation] !== 'function') {
|
|
22
|
+
invalid();
|
|
23
|
+
}
|
|
24
|
+
for (const optional of ['prepare', 'project', 'reject']) {
|
|
25
|
+
if (definition[optional] !== undefined && typeof definition[optional] !== 'function')
|
|
26
|
+
invalid();
|
|
27
|
+
}
|
|
28
|
+
const allowed = new Set([
|
|
29
|
+
'id',
|
|
30
|
+
operation,
|
|
31
|
+
'prepare',
|
|
32
|
+
'project',
|
|
33
|
+
'reject',
|
|
34
|
+
...(operation === 'build' ? ['page', 'pagination'] : []),
|
|
35
|
+
]);
|
|
36
|
+
if (Reflect.ownKeys(definition).some((key) => typeof key !== 'string' || !allowed.has(key))) {
|
|
37
|
+
invalid();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function invalid() {
|
|
41
|
+
throw new TypeError('Query definition is invalid.');
|
|
42
|
+
}
|
|
43
|
+
export function isPromiseLike(input) {
|
|
44
|
+
return (input !== null &&
|
|
45
|
+
(typeof input === 'object' || typeof input === 'function') &&
|
|
46
|
+
typeof input.then === 'function');
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bounds a recipe keeps before anything is sent (S11, spec § Recipes: the contract): its
|
|
3
|
+
* reads, its rows, each verb's bound, and the values of a property's `in`.
|
|
4
|
+
*/
|
|
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`. */
|
|
8
|
+
export declare const MAXIMUM_READS = 32;
|
|
9
|
+
/** Across a recipe's nested lists. */
|
|
10
|
+
export declare const MAXIMUM_ROWS = 100000;
|
|
11
|
+
/** A verb's bound: its list, plus one for `all` and `first(n, { more })`, fits a Kernel page. */
|
|
12
|
+
export declare const MAXIMUM_BOUND = 83332;
|
|
13
|
+
/** Values of a property's `in`, until it is index-backed. */
|
|
14
|
+
export declare const MAXIMUM_IN = 16;
|
|
15
|
+
/** Whether a list verb of the read, at its root or in a relation, is bound past a Kernel page. */
|
|
16
|
+
export declare function pastPage(plan: ReadPlan): boolean;
|
|
17
|
+
/** The rows a read may answer, counted as the product of its verbs' bounds. */
|
|
18
|
+
export declare function rowsOf(plan: ReadPlan): number;
|
|
19
|
+
/** The first property `in` past its values, as a pointer into the document. */
|
|
20
|
+
export declare function wideIn(document: AnyQuery): string | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Each read of each stage, and each element of an `items.map`. */
|
|
2
|
+
export const MAXIMUM_READS = 32;
|
|
3
|
+
/** Across a recipe's nested lists. */
|
|
4
|
+
export const MAXIMUM_ROWS = 100_000;
|
|
5
|
+
/** A verb's bound: its list, plus one for `all` and `first(n, { more })`, fits a Kernel page. */
|
|
6
|
+
export const MAXIMUM_BOUND = 83_332;
|
|
7
|
+
/** Values of a property's `in`, until it is index-backed. */
|
|
8
|
+
export const MAXIMUM_IN = 16;
|
|
9
|
+
/** The rows one verb reads: `get` and `find` 2, `first(n)` n, `page` and `all` max + 1. */
|
|
10
|
+
function verbRows(verb, bound = 0) {
|
|
11
|
+
switch (verb) {
|
|
12
|
+
case 'get':
|
|
13
|
+
case 'find':
|
|
14
|
+
return 2;
|
|
15
|
+
case 'first':
|
|
16
|
+
return 1;
|
|
17
|
+
case 'firstN':
|
|
18
|
+
return bound;
|
|
19
|
+
case 'firstMore':
|
|
20
|
+
case 'page':
|
|
21
|
+
case 'all':
|
|
22
|
+
return bound + 1;
|
|
23
|
+
default:
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** The rows of one parent's relations, each row counting itself and what it carries. */
|
|
28
|
+
function carried(branches) {
|
|
29
|
+
return branches.reduce((total, b) => total + (b.empty ? 0 : verbRows(b.verb, b.bound) * (1 + carried(b.branches))), 0);
|
|
30
|
+
}
|
|
31
|
+
/** The rows of a document read from its own limits: a list's `limit`, its Branches'. */
|
|
32
|
+
function documentRows(document) {
|
|
33
|
+
const list = document.list;
|
|
34
|
+
if (list === undefined)
|
|
35
|
+
return 0;
|
|
36
|
+
const rows = (pattern) => {
|
|
37
|
+
const branches = (pattern.with ?? []);
|
|
38
|
+
return branches.reduce((total, b) => total + (b.mode === 'count' ? 0 : (b.limit ?? 0) * (1 + rows(b))), 0);
|
|
39
|
+
};
|
|
40
|
+
return (list.limit ?? 0) * (1 + rows(list));
|
|
41
|
+
}
|
|
42
|
+
const LISTS = new Set(['firstN', 'firstMore', 'page', 'all']);
|
|
43
|
+
const pastBound = (verb, bound = 0) => LISTS.has(verb) && bound > MAXIMUM_BOUND;
|
|
44
|
+
const branchPastBound = (branches) => branches.some((b) => pastBound(b.verb, b.bound) || branchPastBound(b.branches ?? []));
|
|
45
|
+
/** Whether a list verb of the read, at its root or in a relation, is bound past a Kernel page. */
|
|
46
|
+
export function pastPage(plan) {
|
|
47
|
+
const branches = plan.root?.kind === 'nodes' ? plan.root.branches : [];
|
|
48
|
+
return pastBound(plan.verb, plan.bound) || branchPastBound(branches);
|
|
49
|
+
}
|
|
50
|
+
/** The rows a read may answer, counted as the product of its verbs' bounds. */
|
|
51
|
+
export function rowsOf(plan) {
|
|
52
|
+
if (plan.empty)
|
|
53
|
+
return 0;
|
|
54
|
+
if (plan.verb === 'document')
|
|
55
|
+
return documentRows(plan.document);
|
|
56
|
+
const branches = plan.root?.kind === 'nodes' ? plan.root.branches : [];
|
|
57
|
+
return verbRows(plan.verb, plan.bound) * (1 + carried(branches));
|
|
58
|
+
}
|
|
59
|
+
/** The first property `in` past its values, as a pointer into the document. */
|
|
60
|
+
export function wideIn(document) {
|
|
61
|
+
const visit = (value, path) => {
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
for (const [index, entry] of value.entries()) {
|
|
64
|
+
const found = visit(entry, `${path}/${index}`);
|
|
65
|
+
if (found !== undefined)
|
|
66
|
+
return found;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (value === null || typeof value !== 'object')
|
|
71
|
+
return undefined;
|
|
72
|
+
const record = value;
|
|
73
|
+
if (record.op === 'in')
|
|
74
|
+
return record.values.length > MAXIMUM_IN ? path : undefined;
|
|
75
|
+
// A value written in a condition is data: it is never read as a condition.
|
|
76
|
+
if (record.op === 'eq' || record.op === 'range' || record.op === 'id')
|
|
77
|
+
return undefined;
|
|
78
|
+
for (const [name, entry] of Object.entries(record)) {
|
|
79
|
+
const found = visit(entry, `${path}/${name.replaceAll('~', '~0').replaceAll('/', '~1')}`);
|
|
80
|
+
if (found !== undefined)
|
|
81
|
+
return found;
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
};
|
|
85
|
+
return visit(document, '');
|
|
86
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ClassRef, Domain } from '../../../platform/schema/index.js';
|
|
2
|
+
import type { Model } from '../handles/model.js';
|
|
3
|
+
import type { ReadPlan } from '../handles/plan.js';
|
|
4
|
+
/** A Class requirement as a build compiles it: an entry of `requirements.capabilities.classes`. */
|
|
5
|
+
export interface DeclaredClass {
|
|
6
|
+
readonly class: ClassRef;
|
|
7
|
+
readonly operations: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The S12 refusal: a `TypeError`, the same for every input and every attempt, so a durable Step
|
|
11
|
+
* fails at once on it.
|
|
12
|
+
*/
|
|
13
|
+
export declare class UndeclaredCrossing extends TypeError {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(recipe: string, message: string);
|
|
16
|
+
get recipe(): string;
|
|
17
|
+
}
|
|
18
|
+
/** The S12 check of one Domain: its dependencies, and the operations it declares on their Classes. */
|
|
19
|
+
export declare class Crossings {
|
|
20
|
+
#private;
|
|
21
|
+
readonly model: Model;
|
|
22
|
+
constructor(model: Model, domain: Domain, declared: readonly DeclaredClass[]);
|
|
23
|
+
/** Throw when a relation of the read crosses a foreign Class its Domain does not declare. */
|
|
24
|
+
check(recipe: string, plan: ReadPlan): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
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.
|
|
9
|
+
*/
|
|
10
|
+
import { Key } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
11
|
+
/** What a relation needs on each foreign Class it crosses. */
|
|
12
|
+
const NEEDED = ['read', 'traverse'];
|
|
13
|
+
/**
|
|
14
|
+
* The S12 refusal: a `TypeError`, the same for every input and every attempt, so a durable Step
|
|
15
|
+
* fails at once on it.
|
|
16
|
+
*/
|
|
17
|
+
export class UndeclaredCrossing extends TypeError {
|
|
18
|
+
#recipe;
|
|
19
|
+
constructor(recipe, message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.#recipe = recipe;
|
|
22
|
+
}
|
|
23
|
+
get recipe() {
|
|
24
|
+
return this.#recipe;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** The S12 check of one Domain: its dependencies, and the operations it declares on their Classes. */
|
|
28
|
+
export class Crossings {
|
|
29
|
+
model;
|
|
30
|
+
/** Each direct dependency's origin, by the alias the Domain gives it. */
|
|
31
|
+
#dependencies = new Map();
|
|
32
|
+
/** The operations declared on each Class, by key. */
|
|
33
|
+
#declared = new Map();
|
|
34
|
+
/** The concrete Classes of the Domain and its dependencies: those a Node or Edge can be of. */
|
|
35
|
+
#concrete;
|
|
36
|
+
constructor(model, domain, declared) {
|
|
37
|
+
this.model = model;
|
|
38
|
+
for (const [alias, dependency] of Object.entries(domain.dependencies))
|
|
39
|
+
this.#dependencies.set(dependency.origin, alias);
|
|
40
|
+
for (const requirement of declared) {
|
|
41
|
+
const key = Key.of(requirement.class);
|
|
42
|
+
const operations = this.#declared.get(key) ?? new Set();
|
|
43
|
+
requirement.operations.forEach((operation) => operations.add(operation));
|
|
44
|
+
this.#declared.set(key, operations);
|
|
45
|
+
}
|
|
46
|
+
this.#concrete = [domain, ...Object.values(domain.dependencies)]
|
|
47
|
+
.flatMap((d) => Object.values(d.classes))
|
|
48
|
+
.filter((c) => !c.abstract);
|
|
49
|
+
}
|
|
50
|
+
/** Throw when a relation of the read crosses a foreign Class its Domain does not declare. */
|
|
51
|
+
check(recipe, plan) {
|
|
52
|
+
const document = (plan.written ?? plan.document);
|
|
53
|
+
const root = (document?.list ?? document?.exists ?? document?.count);
|
|
54
|
+
if (root === undefined)
|
|
55
|
+
return;
|
|
56
|
+
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);
|
|
59
|
+
}
|
|
60
|
+
#branches(recipe, value, plans, parent) {
|
|
61
|
+
for (const branch of (value ?? [])) {
|
|
62
|
+
const plan = plans?.find((p) => p.name === branch.name);
|
|
63
|
+
const name = plan?.key ?? branch.name;
|
|
64
|
+
const relation = parent === undefined ? name : `${parent}.${name}`;
|
|
65
|
+
this.#traversal(recipe, branch, `its relation ${relation}`);
|
|
66
|
+
this.#branches(recipe, branch.with, plan?.branches, relation);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** The traversals a condition holds, at any depth. */
|
|
70
|
+
#where(recipe, value, at) {
|
|
71
|
+
const where = value;
|
|
72
|
+
switch (where?.op) {
|
|
73
|
+
case 'and':
|
|
74
|
+
case 'or':
|
|
75
|
+
for (const operand of where.operands)
|
|
76
|
+
this.#where(recipe, operand, at);
|
|
77
|
+
return;
|
|
78
|
+
case 'not':
|
|
79
|
+
case 'source':
|
|
80
|
+
case 'target':
|
|
81
|
+
return this.#where(recipe, where.operand, at);
|
|
82
|
+
case 'exists':
|
|
83
|
+
return this.#traversal(recipe, where, at === undefined ? 'a condition' : `${at}'s condition`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** The Edge Classes of `via`, the Node Classes they reach, then the traversal's own conditions. */
|
|
87
|
+
#traversal(recipe, traversal, label) {
|
|
88
|
+
const via = traversal.via;
|
|
89
|
+
const direction = traversal.direction;
|
|
90
|
+
const edges = via.flatMap((key) => this.#known(key));
|
|
91
|
+
const relation = `${label} (via ${via.join(', ')}, ${direction})`;
|
|
92
|
+
const reached = this.#narrowed(this.model.reached(edges, direction), traversal.node);
|
|
93
|
+
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);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The reached Classes the node condition keeps. An `is` or `isExactly` conjunct, or an `or` of
|
|
100
|
+
* them, keeps a reached Class that satisfies a Class an `is` names, and narrows any other to the
|
|
101
|
+
* named Classes that satisfy it: an `isExactly` keeps its Class alone.
|
|
102
|
+
*/
|
|
103
|
+
#narrowed(reached, node) {
|
|
104
|
+
let classes = reached;
|
|
105
|
+
for (const condition of conjuncts(node)) {
|
|
106
|
+
const named = namedClasses(condition);
|
|
107
|
+
if (named === undefined)
|
|
108
|
+
continue;
|
|
109
|
+
const is = named.filter((n) => !n.exact).flatMap((n) => this.#known(n.key));
|
|
110
|
+
const all = named.flatMap((n) => this.#known(n.key));
|
|
111
|
+
classes = classes.flatMap((c) => is.some((n) => this.model.satisfies(c).has(n.key))
|
|
112
|
+
? [c]
|
|
113
|
+
: all.filter((n) => this.model.satisfies(n).has(c.key)));
|
|
114
|
+
}
|
|
115
|
+
return [...new Set(classes)];
|
|
116
|
+
}
|
|
117
|
+
#require(recipe, relation, c) {
|
|
118
|
+
const alias = this.#dependencies.get(c.ref.origin);
|
|
119
|
+
if (alias === undefined)
|
|
120
|
+
return;
|
|
121
|
+
const missing = this.#missing(c);
|
|
122
|
+
if (missing.length === 0 || (c.abstract && this.#covered(c)))
|
|
123
|
+
return;
|
|
124
|
+
throw new UndeclaredCrossing(recipe, `Recipe ${recipe}: ${relation} crosses ${c.key}, a Class of the dependency ${alias}, on ` +
|
|
125
|
+
`which the Domain declares no ${missing.join(' or ')}. Its requirements need ` +
|
|
126
|
+
`{ class: ${c.key}, operations: ['read', 'traverse'] } in classes.`);
|
|
127
|
+
}
|
|
128
|
+
/** What a Class lacks of `read` and `traverse`, declared on it or on a parent. */
|
|
129
|
+
#missing(c) {
|
|
130
|
+
const declared = new Set([...this.model.satisfies(c)].flatMap((key) => [...(this.#declared.get(key) ?? [])]));
|
|
131
|
+
return NEEDED.filter((operation) => !declared.has(operation));
|
|
132
|
+
}
|
|
133
|
+
/** An abstract Class has concrete Classes under it, and the Domain may cross each. */
|
|
134
|
+
#covered(c) {
|
|
135
|
+
const children = this.#concrete.filter((k) => this.model.satisfies(k).has(c.key));
|
|
136
|
+
return children.length > 0 && children.every((k) => this.#served(k));
|
|
137
|
+
}
|
|
138
|
+
/** Whether a relation may cross a Class: the Domain's own, or a dependency's it declares. */
|
|
139
|
+
#served(c) {
|
|
140
|
+
return !this.#dependencies.has(c.ref.origin) || this.#missing(c).length === 0;
|
|
141
|
+
}
|
|
142
|
+
/** The Class a key names, when the Domain can name it: the Kernel refuses any other. */
|
|
143
|
+
#known(key) {
|
|
144
|
+
try {
|
|
145
|
+
return [this.model.classOf(key)];
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** The Classes an `is` or `isExactly` names, alone or in an `or` of them; else `undefined`. */
|
|
153
|
+
function namedClasses(condition) {
|
|
154
|
+
if (condition.op === 'is' || condition.op === 'isExactly')
|
|
155
|
+
return [{ key: condition.class, exact: condition.op === 'isExactly' }];
|
|
156
|
+
if (condition.op !== 'or')
|
|
157
|
+
return undefined;
|
|
158
|
+
const named = condition.operands.map(namedClasses);
|
|
159
|
+
return named.every((n) => n !== undefined) ? named.flat() : undefined;
|
|
160
|
+
}
|
|
161
|
+
/** The conditions a condition requires all of. */
|
|
162
|
+
function conjuncts(value) {
|
|
163
|
+
const where = value;
|
|
164
|
+
if (where === undefined)
|
|
165
|
+
return [];
|
|
166
|
+
if (where.op === 'and')
|
|
167
|
+
return where.operands.flatMap(conjuncts);
|
|
168
|
+
return [where];
|
|
169
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one error a recipe's reads raise (spec § Errors): a code, the recipe's id and the details
|
|
3
|
+
* of that code. A recipe's own business refusals are its declared errors, raised by `output`.
|
|
4
|
+
*/
|
|
5
|
+
import type { NodeId } from '../../../platform/graph/node/index.js';
|
|
6
|
+
import type { EdgeKey } from '../handles/rows.js';
|
|
7
|
+
/** The details of each code. */
|
|
8
|
+
export interface QueryErrorDetails {
|
|
9
|
+
/** `get` at the root found nothing. */
|
|
10
|
+
NOT_FOUND: {
|
|
11
|
+
readonly class: string;
|
|
12
|
+
readonly target?: string;
|
|
13
|
+
};
|
|
14
|
+
/** `get` or `find` met a fact the reader may not read: a Node, or an Edge read at the root. */
|
|
15
|
+
NOT_READABLE: {
|
|
16
|
+
readonly class: string;
|
|
17
|
+
readonly id: NodeId | EdgeKey;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A relation's `get` found none, or a second row came where one is allowed. At the root, `get`
|
|
21
|
+
* or `find` has no parent, and the relation is its Classes.
|
|
22
|
+
*/
|
|
23
|
+
INTEGRITY: {
|
|
24
|
+
readonly relation: string;
|
|
25
|
+
readonly parent?: NodeId;
|
|
26
|
+
readonly found: 0 | 2;
|
|
27
|
+
};
|
|
28
|
+
/** Past a verb's `max`, the recipe's 100,000 rows or its 32 reads. */
|
|
29
|
+
TOO_MANY: {
|
|
30
|
+
readonly limit: 'max' | 'rows' | 'reads';
|
|
31
|
+
readonly bound: number;
|
|
32
|
+
};
|
|
33
|
+
/** The Kernel refused the read. */
|
|
34
|
+
REFUSED: {
|
|
35
|
+
readonly kernelCode: string;
|
|
36
|
+
readonly retryable: boolean;
|
|
37
|
+
};
|
|
38
|
+
/** The Kernel refused the query the recipe built, before reading anything. */
|
|
39
|
+
INPUT_INVALID: {
|
|
40
|
+
readonly kernelCode?: string;
|
|
41
|
+
readonly phase?: string;
|
|
42
|
+
readonly path?: string;
|
|
43
|
+
readonly limit?: 'in';
|
|
44
|
+
};
|
|
45
|
+
/** The Host's Registry no longer matches what the code was built against. */
|
|
46
|
+
OUTDATED: {
|
|
47
|
+
readonly kernelCode: 'QUERY_EXPECTATION_MISMATCH';
|
|
48
|
+
};
|
|
49
|
+
/** The Host does not serve v7, or does not give each row's Class. */
|
|
50
|
+
UNSUPPORTED: {
|
|
51
|
+
readonly feature: 'v7' | 'classes';
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export type QueryErrorCode = keyof QueryErrorDetails;
|
|
55
|
+
export declare class QueryError<C extends QueryErrorCode = QueryErrorCode> extends Error {
|
|
56
|
+
readonly code: C;
|
|
57
|
+
/** The recipe's id. */
|
|
58
|
+
readonly recipe: string;
|
|
59
|
+
readonly details: QueryErrorDetails[C];
|
|
60
|
+
readonly name = "QueryError";
|
|
61
|
+
constructor(code: C,
|
|
62
|
+
/** The recipe's id. */
|
|
63
|
+
recipe: string, details: QueryErrorDetails[C], options?: ErrorOptions & {
|
|
64
|
+
readonly reason?: string;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Whether `error` is a `QueryError`, of `code` if given. */
|
|
68
|
+
export declare function isQueryError<C extends QueryErrorCode = QueryErrorCode>(error: unknown, code?: C): error is QueryError<C>;
|