@astrale-os/sdk 0.5.0-beta.28 → 0.5.0-beta.29
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/dist/application/action/context.d.ts +6 -6
- package/dist/application/mutation/define.d.ts +10 -2
- package/dist/application/mutation/define.js +42 -3
- package/dist/application/mutation/execution/execute.d.ts +7 -6
- package/dist/application/mutation/execution/execute.js +7 -10
- package/dist/application/mutation/execution/executor.d.ts +4 -4
- package/dist/application/mutation/execution/executor.js +4 -4
- package/dist/application/mutation/execution/projection.d.ts +2 -2
- package/dist/application/mutation/index.d.ts +1 -1
- package/dist/application/mutation/mutation.d.ts +14 -6
- package/dist/application/query/composite/builder.d.ts +6 -5
- package/dist/application/query/composite/builder.js +2 -2
- package/dist/application/query/composite/plan.d.ts +3 -2
- package/dist/application/query/composite/query.d.ts +10 -8
- package/dist/application/query/define.d.ts +18 -6
- package/dist/application/query/define.js +46 -2
- package/dist/application/query/execution/execute.d.ts +10 -4
- package/dist/application/query/execution/execute.js +18 -10
- package/dist/application/query/execution/executor.d.ts +4 -4
- package/dist/application/query/execution/executor.js +3 -8
- package/dist/application/query/index.d.ts +2 -2
- package/dist/application/query/index.js +1 -1
- package/dist/application/query/query.d.ts +23 -4
- package/dist/execution/invocation/dispatch/context.js +2 -2
- package/dist/execution/runtime/load.d.ts +4 -1
- package/dist/execution/runtime/load.js +3 -1
- package/dist/tooling/linter/adapters/typescript/query-observation.js +14 -6
- package/dist/tooling/linter/implementations/source/global.js +103 -3
- package/dist/tooling/linter/implementations/source/mutations.js +42 -19
- package/dist/tooling/linter/implementations/source/queries.js +19 -6
- package/dist/tooling/linter/implementations/source/shared.d.ts +3 -0
- package/dist/tooling/linter/implementations/source/shared.js +81 -5
- package/dist/tooling/linter/policy/generated.js +2 -2
- package/package.json +1 -1
|
@@ -29,7 +29,7 @@ export interface ActionExecution {
|
|
|
29
29
|
body(): Uint8Array;
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
type AuthorityOf<Callable> = Callable extends {
|
|
32
|
+
type AuthorityOf<Callable, DomainValue extends Domain> = Callable extends {
|
|
33
33
|
readonly auth: 'anonymous';
|
|
34
34
|
} ? {
|
|
35
35
|
readonly caller: AnonymousCaller;
|
|
@@ -39,13 +39,13 @@ type AuthorityOf<Callable> = Callable extends {
|
|
|
39
39
|
} | {
|
|
40
40
|
readonly caller: AuthenticatedCaller;
|
|
41
41
|
readonly client: BoundClientSession;
|
|
42
|
-
readonly query: QueryExecutor
|
|
43
|
-
readonly mutate: MutationExecutor
|
|
42
|
+
readonly query: QueryExecutor<DomainValue>;
|
|
43
|
+
readonly mutate: MutationExecutor<DomainValue>;
|
|
44
44
|
} : {
|
|
45
45
|
readonly caller: AuthenticatedCaller;
|
|
46
46
|
readonly client: BoundClientSession;
|
|
47
|
-
readonly query: QueryExecutor
|
|
48
|
-
readonly mutate: MutationExecutor
|
|
47
|
+
readonly query: QueryExecutor<DomainValue>;
|
|
48
|
+
readonly mutate: MutationExecutor<DomainValue>;
|
|
49
49
|
};
|
|
50
50
|
type SelfOf<Callable> = Callable extends ResolvedMethod<unknown, unknown, false> ? {
|
|
51
51
|
readonly self: NodeId;
|
|
@@ -59,5 +59,5 @@ export type ActionContext<Callable extends ResolvedCallable, DomainValue extends
|
|
|
59
59
|
readonly input: CallableInputOf<Callable>;
|
|
60
60
|
readonly integrations: IntegrationClients<Definitions>;
|
|
61
61
|
readonly execution: ActionExecution;
|
|
62
|
-
} & AuthorityOf<Callable> & SelfOf<Callable>;
|
|
62
|
+
} & AuthorityOf<Callable, DomainValue> & SelfOf<Callable>;
|
|
63
63
|
export {};
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { MutationResult } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type { Domain, schema } from '../../platform/schema/index.js';
|
|
3
|
+
import type { Mutation, MutationDefinition } from './mutation.js';
|
|
4
|
+
type ResolvedDomainOf<Schema extends schema.DomainSchema> = Extract<schema.DomainOf<Schema>, Domain>;
|
|
5
|
+
interface MutationProjector<Schema extends schema.DomainSchema> {
|
|
6
|
+
<Input, Output = MutationResult>(project: (domain: ResolvedDomainOf<Schema>) => MutationDefinition<Input, Output>): Mutation<ResolvedDomainOf<Schema>, Input, Output>;
|
|
7
|
+
}
|
|
8
|
+
/** Define one inert Mutation recipe projected from its exact loaded Domain. */
|
|
9
|
+
export declare function defineMutation<Schema extends schema.DomainSchema>(): MutationProjector<Schema>;
|
|
10
|
+
export declare function realizeMutation<DomainValue extends Domain, Input, Output>(mutation: Mutation<DomainValue, Input, Output>, domain: DomainValue): MutationDefinition<Input, Output>;
|
|
11
|
+
export {};
|
|
@@ -1,13 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
import { isDomain } from '@astrale-os/kernel-dsl/v1/domain';
|
|
2
|
+
const projectors = new WeakMap();
|
|
3
|
+
const realized = new WeakMap();
|
|
4
|
+
/** Define one inert Mutation recipe projected from its exact loaded Domain. */
|
|
5
|
+
export function defineMutation() {
|
|
6
|
+
return ((project) => {
|
|
7
|
+
if (typeof project !== 'function')
|
|
8
|
+
throw new TypeError('Mutation projection must be a function.');
|
|
9
|
+
const mutation = Object.freeze({ kind: 'mutation' });
|
|
10
|
+
projectors.set(mutation, project);
|
|
11
|
+
return mutation;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function realizeMutation(mutation, domain) {
|
|
15
|
+
const project = projectors.get(mutation);
|
|
16
|
+
if (project === undefined)
|
|
17
|
+
throw new TypeError('Mutation must be produced by defineMutation.');
|
|
18
|
+
if (!isDomain(domain))
|
|
19
|
+
throw new TypeError('Mutation projection requires an admitted Domain.');
|
|
20
|
+
let byDomain = realized.get(mutation);
|
|
21
|
+
const cached = byDomain?.get(domain);
|
|
22
|
+
if (cached !== undefined)
|
|
23
|
+
return cached;
|
|
24
|
+
const authored = project(domain);
|
|
25
|
+
if (isPromiseLike(authored))
|
|
26
|
+
throw new TypeError('Mutation projection must be synchronous.');
|
|
27
|
+
const admitted = admitMutation(authored);
|
|
28
|
+
if (byDomain === undefined) {
|
|
29
|
+
byDomain = new WeakMap();
|
|
30
|
+
realized.set(mutation, byDomain);
|
|
31
|
+
}
|
|
32
|
+
byDomain.set(domain, admitted);
|
|
33
|
+
return admitted;
|
|
34
|
+
}
|
|
35
|
+
function admitMutation(definition) {
|
|
2
36
|
if (definition === null || typeof definition !== 'object' || Array.isArray(definition))
|
|
3
37
|
invalid();
|
|
4
38
|
if (typeof definition.id !== 'string' ||
|
|
5
39
|
definition.id.length === 0 ||
|
|
6
40
|
definition.id.trim() !== definition.id ||
|
|
7
|
-
typeof definition.
|
|
41
|
+
typeof definition.build !== 'function' ||
|
|
8
42
|
(definition.project !== undefined && typeof definition.project !== 'function') ||
|
|
9
43
|
(definition.reject !== undefined && typeof definition.reject !== 'function') ||
|
|
10
|
-
Reflect.ownKeys(definition).some((key) => key !== 'id' && key !== '
|
|
44
|
+
Reflect.ownKeys(definition).some((key) => key !== 'id' && key !== 'build' && key !== 'project' && key !== 'reject')) {
|
|
11
45
|
invalid();
|
|
12
46
|
}
|
|
13
47
|
return Object.freeze({ ...definition });
|
|
@@ -15,3 +49,8 @@ export function defineMutation(definition) {
|
|
|
15
49
|
function invalid() {
|
|
16
50
|
throw new TypeError('Mutation definition is invalid.');
|
|
17
51
|
}
|
|
52
|
+
function isPromiseLike(input) {
|
|
53
|
+
return (input !== null &&
|
|
54
|
+
(typeof input === 'object' || typeof input === 'function') &&
|
|
55
|
+
typeof input.then === 'function');
|
|
56
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { MutationAST, type MutationResult } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
2
|
import type { Domain } from '../../../platform/schema/index.js';
|
|
3
3
|
import type { Mutation } from '../mutation.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export interface MutationClient {
|
|
5
|
+
mutate(ast: ReturnType<typeof MutationAST.build>, options?: {
|
|
6
|
+
readonly expected?: Domain['closure'];
|
|
7
|
+
}): Promise<MutationResult>;
|
|
8
|
+
}
|
|
9
|
+
export declare function executeMutation<DomainValue extends Domain, Input, Output>(client: MutationClient, domain: DomainValue, definition: Mutation<DomainValue, Input, Output>, input: Input): Promise<Output>;
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import { MutationAST } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
2
|
import { richMutationBuilder } from '../authoring/index.js';
|
|
3
|
+
import { realizeMutation } from '../define.js';
|
|
3
4
|
import { mutationFailure } from '../failure.js';
|
|
4
5
|
import { projectMutation } from './projection.js';
|
|
5
|
-
export async function executeMutation(client, definition, input) {
|
|
6
|
-
return executeMutationWithExpected(client, definition, input);
|
|
7
|
-
}
|
|
8
|
-
/** Private Runtime binding; public callers use executeMutation with an explicit Client. */
|
|
9
|
-
export async function executeMutationWithExpected(client, definition, input, expected) {
|
|
6
|
+
export async function executeMutation(client, domain, definition, input) {
|
|
10
7
|
if (client === null || typeof client !== 'object' || typeof client.mutate !== 'function') {
|
|
11
8
|
throw new TypeError('Mutation execution requires a Client mutate capability.');
|
|
12
9
|
}
|
|
10
|
+
const operation = realizeMutation(definition, domain);
|
|
13
11
|
try {
|
|
14
|
-
const ast = MutationAST.build((builder) =>
|
|
15
|
-
|
|
16
|
-
return projectMutation(definition, result, input);
|
|
12
|
+
const ast = MutationAST.build((builder) => operation.build(input, richMutationBuilder(builder)));
|
|
13
|
+
return projectMutation(operation, await client.mutate(ast, { expected: domain.closure }), input);
|
|
17
14
|
}
|
|
18
15
|
catch (cause) {
|
|
19
16
|
const expected = mutationFailure(cause);
|
|
20
|
-
if (
|
|
21
|
-
return
|
|
17
|
+
if (operation.reject !== undefined && expected !== undefined)
|
|
18
|
+
return operation.reject(expected);
|
|
22
19
|
throw cause;
|
|
23
20
|
}
|
|
24
21
|
}
|
|
@@ -2,8 +2,8 @@ import type { Domain } from '../../../platform/schema/index.js';
|
|
|
2
2
|
import type { Mutation } from '../mutation.js';
|
|
3
3
|
import type { MutationClient } from './execute.js';
|
|
4
4
|
/** Invocation-bound executor for authored Mutation definitions. */
|
|
5
|
-
export interface MutationExecutor {
|
|
6
|
-
<Input, Output>(definition: Mutation<Input, Output>, input: Input): Promise<Output>;
|
|
5
|
+
export interface MutationExecutor<DomainValue extends Domain = Domain> {
|
|
6
|
+
<Input, Output>(definition: Mutation<DomainValue, Input, Output>, input: Input): Promise<Output>;
|
|
7
7
|
}
|
|
8
|
-
/** Private Runtime binding
|
|
9
|
-
export declare function bindMutation(client: MutationClient,
|
|
8
|
+
/** Private Runtime binding to one Client and the exact loaded root Domain. */
|
|
9
|
+
export declare function bindMutation<DomainValue extends Domain>(client: MutationClient, domain: DomainValue): MutationExecutor<DomainValue>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/** Private Runtime binding
|
|
3
|
-
export function bindMutation(client,
|
|
4
|
-
return (definition, input) =>
|
|
1
|
+
import { executeMutation } from './execute.js';
|
|
2
|
+
/** Private Runtime binding to one Client and the exact loaded root Domain. */
|
|
3
|
+
export function bindMutation(client, domain) {
|
|
4
|
+
return (definition, input) => executeMutation(client, domain, definition, input);
|
|
5
5
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { MutationResult } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
|
-
import type {
|
|
3
|
-
export declare function projectMutation<Input, Output>(definition:
|
|
2
|
+
import type { MutationDefinition } from '../mutation.js';
|
|
3
|
+
export declare function projectMutation<Input, Output>(definition: MutationDefinition<Input, Output>, result: MutationResult, input: Input): Output;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { defineMutation } from './define.js';
|
|
2
2
|
export { executeMutation, type MutationClient, type MutationExecutor } from './execution/index.js';
|
|
3
3
|
export type { MutationFailure } from './failure.js';
|
|
4
|
-
export type { Mutation, MutationInputOf, MutationOutputOf } from './mutation.js';
|
|
4
|
+
export type { Mutation, MutationDefinition, MutationInputOf, MutationOutputOf } from './mutation.js';
|
|
5
5
|
export type { CreatedNode, EdgeSelector, ExistingEndpoint, MutationEndpoint, QueryPrecondition, RichMutationBuilder, RichPropertyConditions, RichPropertyDelta, RichPropertyInput, RichPropertyName, } from './authoring/index.js';
|
|
6
6
|
/** Canonical low-level Mutation documents remain available as the explicit escape hatch. */
|
|
7
7
|
export * from '@astrale-os/kernel-core/graph/mutate';
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import type { MutationResult } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
|
+
import type { Domain } from '../../platform/schema/index.js';
|
|
2
3
|
import type { RichMutationBuilder } from './authoring/index.js';
|
|
3
4
|
import type { MutationFailure } from './failure.js';
|
|
5
|
+
declare const MUTATION_DOMAIN: unique symbol;
|
|
4
6
|
declare const MUTATION_INPUT: unique symbol;
|
|
5
7
|
declare const MUTATION_OUTPUT: unique symbol;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
/** Inert Domain-bound Mutation recipe. Its projector is retained privately by the SDK. */
|
|
9
|
+
export interface Mutation<DomainValue extends Domain = Domain, Input = unknown, Output = MutationResult> {
|
|
10
|
+
readonly kind: 'mutation';
|
|
11
|
+
readonly [MUTATION_DOMAIN]: (domain: DomainValue) => DomainValue;
|
|
8
12
|
readonly [MUTATION_INPUT]?: Input;
|
|
9
13
|
readonly [MUTATION_OUTPUT]?: Output;
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
}
|
|
15
|
+
/** Canonical admitted operation produced by realizing one Mutation recipe. */
|
|
16
|
+
export interface MutationDefinition<Input, Output = MutationResult> {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
build(input: Input, mutation: RichMutationBuilder): undefined;
|
|
19
|
+
project?(result: MutationResult, input: NoInfer<Input>): Output;
|
|
12
20
|
reject?(failure: MutationFailure): Output;
|
|
13
21
|
}
|
|
14
|
-
export type MutationInputOf<Value> = Value extends Mutation<infer Input, infer _Output> ? Input : never;
|
|
15
|
-
export type MutationOutputOf<Value> = Value extends Mutation<infer _Input, infer Output> ? Output : never;
|
|
22
|
+
export type MutationInputOf<Value> = Value extends Mutation<infer _Domain, infer Input, infer _Output> ? Input : Value extends MutationDefinition<infer Input, infer _Output> ? Input : never;
|
|
23
|
+
export type MutationOutputOf<Value> = Value extends Mutation<infer _Domain, infer _Input, infer Output> ? Output : Value extends MutationDefinition<infer _Input, infer Output> ? Output : never;
|
|
16
24
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Domain } from '../../../platform/schema/index.js';
|
|
2
|
+
import type { SingleQueryDefinition } from '../query.js';
|
|
2
3
|
import type { QueryComposition, QueryPlan } from './query.js';
|
|
3
4
|
declare const PLAN: unique symbol;
|
|
4
5
|
export interface PlanReference {
|
|
@@ -10,7 +11,7 @@ export interface LeafPlan {
|
|
|
10
11
|
readonly kind: 'leaf';
|
|
11
12
|
readonly key: number;
|
|
12
13
|
readonly id: string;
|
|
13
|
-
readonly definition:
|
|
14
|
+
readonly definition: SingleQueryDefinition<Domain, unknown, unknown>;
|
|
14
15
|
readonly input: unknown;
|
|
15
16
|
}
|
|
16
17
|
export interface CombinedPlan {
|
|
@@ -25,11 +26,11 @@ export interface InternalPlan extends QueryPlan<unknown> {
|
|
|
25
26
|
node: PlanNode;
|
|
26
27
|
}>;
|
|
27
28
|
}
|
|
28
|
-
export interface QueryCompositionSession {
|
|
29
|
-
readonly builder: QueryComposition
|
|
29
|
+
export interface QueryCompositionSession<DomainValue extends Domain = Domain> {
|
|
30
|
+
readonly builder: QueryComposition<DomainValue>;
|
|
30
31
|
close(output: unknown): InternalPlan;
|
|
31
32
|
}
|
|
32
|
-
export declare function createQueryComposition(): QueryCompositionSession
|
|
33
|
+
export declare function createQueryComposition<DomainValue extends Domain>(): QueryCompositionSession<DomainValue>;
|
|
33
34
|
export declare function inspectPlan(plan: InternalPlan): Readonly<{
|
|
34
35
|
token: object;
|
|
35
36
|
node: PlanNode;
|
|
@@ -10,8 +10,8 @@ export function createQueryComposition() {
|
|
|
10
10
|
query(id, definition, input) {
|
|
11
11
|
requireOpen(open);
|
|
12
12
|
requireLocalId(id, ids);
|
|
13
|
-
if (definition?.kind !== '
|
|
14
|
-
invalid('Composite leaves must be single
|
|
13
|
+
if (definition?.kind !== 'query')
|
|
14
|
+
invalid('Composite leaves must be single Query recipes.');
|
|
15
15
|
requireLocalReferences(input, token);
|
|
16
16
|
return createPlan(token, Object.freeze({
|
|
17
17
|
kind: 'leaf',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Domain } from '../../../platform/schema/index.js';
|
|
2
|
+
import type { SingleQueryDefinition } from '../query.js';
|
|
2
3
|
import type { InternalPlan } from './builder.js';
|
|
3
4
|
export interface QueryPlanAnalysis {
|
|
4
5
|
readonly queries: number;
|
|
5
6
|
readonly depth: number;
|
|
6
7
|
}
|
|
7
8
|
export declare function analyzeQueryPlan(plan: InternalPlan): QueryPlanAnalysis;
|
|
8
|
-
export declare function executeQueryPlan(plan: InternalPlan, run: (definition:
|
|
9
|
+
export declare function executeQueryPlan(plan: InternalPlan, run: (definition: SingleQueryDefinition<Domain, unknown, unknown>, input: unknown) => Promise<unknown>): Promise<unknown>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Domain } from '../../../platform/schema/index.js';
|
|
2
|
+
import type { SingleQueryDefinition } from '../query.js';
|
|
3
|
+
import type { QueryLifecycle } from '../single/index.js';
|
|
2
4
|
declare const QUERY_PLAN_OUTPUT: unique symbol;
|
|
3
5
|
declare const QUERY_PLAN_VALUE: unique symbol;
|
|
4
6
|
export type QueryPlanValue<Value> = Readonly<{
|
|
@@ -14,21 +16,21 @@ export interface QueryPlan<Output> {
|
|
|
14
16
|
readonly value: QueryPlanValue<Output>;
|
|
15
17
|
}
|
|
16
18
|
export type QueryPlanOutput<Plan extends QueryPlan<unknown>> = Plan[typeof QUERY_PLAN_OUTPUT];
|
|
17
|
-
export interface QueryComposition {
|
|
18
|
-
query<Input, Output>(id: string, definition:
|
|
19
|
+
export interface QueryComposition<DomainValue extends Domain = Domain> {
|
|
20
|
+
query<Input, Output>(id: string, definition: SingleQueryDefinition<DomainValue, Input, Output>, input: QueryPlanInput<Input>): QueryPlan<Output>;
|
|
19
21
|
combine<const Plans extends Readonly<Record<string, QueryPlan<unknown>>>>(plans: Plans): QueryPlan<{
|
|
20
22
|
readonly [Name in keyof Plans]: QueryPlanOutput<Plans[Name]>;
|
|
21
23
|
}>;
|
|
22
24
|
}
|
|
23
|
-
export interface CompositeQueryBase<Input, Composed, Output> extends QueryLifecycle<Input, Output> {
|
|
25
|
+
export interface CompositeQueryBase<Input, Composed, Output, DomainValue extends Domain = Domain> extends QueryLifecycle<Input, Output> {
|
|
24
26
|
readonly kind: 'composite';
|
|
25
|
-
compose(builder: QueryComposition
|
|
27
|
+
compose(builder: QueryComposition<DomainValue>, input: Input): QueryPlan<Composed>;
|
|
26
28
|
}
|
|
27
|
-
export interface RawCompositeQuery<Input, Output> extends CompositeQueryBase<Input, Output, Output> {
|
|
29
|
+
export interface RawCompositeQuery<Input, Output, DomainValue extends Domain = Domain> extends CompositeQueryBase<Input, Output, Output, DomainValue> {
|
|
28
30
|
readonly project?: undefined;
|
|
29
31
|
}
|
|
30
|
-
export interface ProjectedCompositeQuery<Input, Composed, Output> extends CompositeQueryBase<Input, Composed, Output> {
|
|
32
|
+
export interface ProjectedCompositeQuery<Input, Composed, Output, DomainValue extends Domain = Domain> extends CompositeQueryBase<Input, Composed, Output, DomainValue> {
|
|
31
33
|
project(value: Composed, input: Input): Output;
|
|
32
34
|
}
|
|
33
|
-
export type CompositeQuery<Input, Composed, Output = Composed> = RawCompositeQuery<Input, Composed> | ProjectedCompositeQuery<Input, Composed, Output>;
|
|
35
|
+
export type CompositeQuery<Input, Composed, Output = Composed, DomainValue extends Domain = Domain> = RawCompositeQuery<Input, Composed, DomainValue> | ProjectedCompositeQuery<Input, Composed, Output, DomainValue>;
|
|
34
36
|
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { QueryAST } from '@astrale-os/kernel-core/graph/query';
|
|
2
|
+
import type { Domain, schema } from '../../platform/schema/index.js';
|
|
2
3
|
import type { ProjectedCompositeQuery, RawCompositeQuery } from './composite/index.js';
|
|
3
|
-
import type {
|
|
4
|
+
import type { CompositeQueryDefinition, QueryDefinition, RealizedQuery, SingleQueryDefinition } from './query.js';
|
|
5
|
+
import type { PaginatedQuery, ProjectedQuery, RawQuery, SingleQuery } from './single/index.js';
|
|
4
6
|
type AuthoredRaw<Input, Ast extends QueryAST> = Omit<RawQuery<Input, Ast>, 'kind' | 'page'> & {
|
|
5
7
|
readonly page?: {
|
|
6
8
|
readonly size: number;
|
|
@@ -16,9 +18,19 @@ type AuthoredPaginated<Input, Output, Ast extends QueryAST> = Omit<PaginatedQuer
|
|
|
16
18
|
readonly size: number;
|
|
17
19
|
};
|
|
18
20
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
type ResolvedDomainOf<Schema extends schema.DomainSchema> = Extract<schema.DomainOf<Schema>, Domain>;
|
|
22
|
+
interface SingleQueryProjector<Schema extends schema.DomainSchema> {
|
|
23
|
+
<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>;
|
|
24
|
+
<Input, Output, Ast extends QueryAST>(project: (domain: ResolvedDomainOf<Schema>) => AuthoredProjected<Input, Output, Ast>): SingleQueryDefinition<ResolvedDomainOf<Schema>, Input, Output>;
|
|
25
|
+
<Input, Output, Ast extends QueryAST>(project: (domain: ResolvedDomainOf<Schema>) => AuthoredPaginated<Input, Output, Ast>): SingleQueryDefinition<ResolvedDomainOf<Schema>, Input, Output>;
|
|
26
|
+
}
|
|
27
|
+
interface CompositeQueryProjector<Schema extends schema.DomainSchema> {
|
|
28
|
+
<Input, Output>(project: (domain: ResolvedDomainOf<Schema>) => Omit<RawCompositeQuery<Input, Output, ResolvedDomainOf<Schema>>, 'kind'>): CompositeQueryDefinition<ResolvedDomainOf<Schema>, Input, Output>;
|
|
29
|
+
<Input, Composed, Output>(project: (domain: ResolvedDomainOf<Schema>) => Omit<ProjectedCompositeQuery<Input, Composed, Output, ResolvedDomainOf<Schema>>, 'kind'>): CompositeQueryDefinition<ResolvedDomainOf<Schema>, Input, Output>;
|
|
30
|
+
}
|
|
31
|
+
/** Define one inert single Query recipe projected from its exact loaded Domain. */
|
|
32
|
+
export declare function defineQuery<Schema extends schema.DomainSchema>(): SingleQueryProjector<Schema>;
|
|
33
|
+
/** Define one inert composite Query recipe projected from its exact loaded Domain. */
|
|
34
|
+
export declare function defineCompositeQuery<Schema extends schema.DomainSchema>(): CompositeQueryProjector<Schema>;
|
|
35
|
+
export declare function realizeQuery<DomainValue extends Domain, Input, Output>(definition: QueryDefinition<DomainValue, Input, Output>, domain: DomainValue): RealizedQuery<Input, Output>;
|
|
24
36
|
export {};
|
|
@@ -1,6 +1,45 @@
|
|
|
1
|
+
import { isDomain } from '@astrale-os/kernel-dsl/v1/domain';
|
|
1
2
|
const DEFAULT_PAGE_SIZE = 256;
|
|
2
3
|
const MAXIMUM_PAGES = 1_024;
|
|
3
|
-
|
|
4
|
+
const projectors = new WeakMap();
|
|
5
|
+
const realized = new WeakMap();
|
|
6
|
+
/** Define one inert single Query recipe projected from its exact loaded Domain. */
|
|
7
|
+
export function defineQuery() {
|
|
8
|
+
return ((project) => recipe('query', project));
|
|
9
|
+
}
|
|
10
|
+
/** Define one inert composite Query recipe projected from its exact loaded Domain. */
|
|
11
|
+
export function defineCompositeQuery() {
|
|
12
|
+
return ((project) => recipe('composite-query', project));
|
|
13
|
+
}
|
|
14
|
+
export function realizeQuery(definition, domain) {
|
|
15
|
+
const project = projectors.get(definition);
|
|
16
|
+
if (project === undefined)
|
|
17
|
+
throw new TypeError('Query must be produced by defineQuery.');
|
|
18
|
+
if (!isDomain(domain))
|
|
19
|
+
throw new TypeError('Query projection requires an admitted Domain.');
|
|
20
|
+
let byDomain = realized.get(definition);
|
|
21
|
+
const cached = byDomain?.get(domain);
|
|
22
|
+
if (cached !== undefined)
|
|
23
|
+
return cached;
|
|
24
|
+
const authored = project(domain);
|
|
25
|
+
if (isPromiseLike(authored))
|
|
26
|
+
throw new TypeError('Query projection must be synchronous.');
|
|
27
|
+
const admitted = definition.kind === 'query' ? admitSingle(authored) : admitComposite(authored);
|
|
28
|
+
if (byDomain === undefined) {
|
|
29
|
+
byDomain = new WeakMap();
|
|
30
|
+
realized.set(definition, byDomain);
|
|
31
|
+
}
|
|
32
|
+
byDomain.set(domain, admitted);
|
|
33
|
+
return admitted;
|
|
34
|
+
}
|
|
35
|
+
function recipe(kind, project) {
|
|
36
|
+
if (typeof project !== 'function')
|
|
37
|
+
throw new TypeError('Query projection must be a function.');
|
|
38
|
+
const definition = Object.freeze({ kind });
|
|
39
|
+
projectors.set(definition, project);
|
|
40
|
+
return definition;
|
|
41
|
+
}
|
|
42
|
+
function admitSingle(definition) {
|
|
4
43
|
definitionBase(definition, 'build');
|
|
5
44
|
const page = pageOf(definition.page);
|
|
6
45
|
const pagination = definition.pagination;
|
|
@@ -24,7 +63,7 @@ export function defineQuery(definition) {
|
|
|
24
63
|
}
|
|
25
64
|
return Object.freeze({ ...definition, page, kind: 'single' });
|
|
26
65
|
}
|
|
27
|
-
|
|
66
|
+
function admitComposite(definition) {
|
|
28
67
|
definitionBase(definition, 'compose');
|
|
29
68
|
if (Object.hasOwn(definition, 'page') || Object.hasOwn(definition, 'pagination')) {
|
|
30
69
|
throw new TypeError('Composite Query cannot declare pagination.');
|
|
@@ -73,3 +112,8 @@ function pageOf(input) {
|
|
|
73
112
|
function invalid() {
|
|
74
113
|
throw new TypeError('Query definition is invalid.');
|
|
75
114
|
}
|
|
115
|
+
function isPromiseLike(input) {
|
|
116
|
+
return (input !== null &&
|
|
117
|
+
(typeof input === 'object' || typeof input === 'function') &&
|
|
118
|
+
typeof input.then === 'function');
|
|
119
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type {
|
|
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 { Domain } from '../../../platform/schema/index.js';
|
|
2
4
|
import type { CompositeQuery } from '../composite/index.js';
|
|
3
5
|
import type { QueryDefinition } from '../query.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
export interface QueryClient {
|
|
7
|
+
query<Ast extends QueryAST>(ast: Ast, options: {
|
|
8
|
+
readonly page: QueryPageRequest;
|
|
9
|
+
readonly expected?: Domain['closure'];
|
|
10
|
+
}): Promise<QueryResponse<QueryResultFor<Ast>>>;
|
|
11
|
+
}
|
|
12
|
+
export declare function executeQuery<DomainValue extends Domain, Input, Output>(client: QueryClient, domain: DomainValue, definition: QueryDefinition<DomainValue, Input, Output>, input: Input): Promise<Output>;
|
|
7
13
|
export type ExecutableCompositeQuery = CompositeQuery<unknown, unknown, unknown>;
|
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
import { createQueryComposition, executeQueryPlan } from '../composite/index.js';
|
|
2
|
+
import { realizeQuery } from '../define.js';
|
|
2
3
|
import { queryFailure } from '../failure.js';
|
|
3
4
|
import { completeSelection } from '../single/index.js';
|
|
4
5
|
import { executePages } from './pages.js';
|
|
5
|
-
export async function executeQuery(client, definition, input) {
|
|
6
|
+
export async function executeQuery(client, domain, definition, input) {
|
|
6
7
|
if (client === null || typeof client !== 'object' || typeof client.query !== 'function') {
|
|
7
8
|
throw new TypeError('Query execution requires a Client query capability.');
|
|
8
9
|
}
|
|
9
|
-
const execute = client.query.
|
|
10
|
+
const execute = (ast, options) => client.query(ast, { ...options, expected: domain.closure });
|
|
11
|
+
const operation = realizeQuery(definition, domain);
|
|
10
12
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
if (operation.kind === 'single')
|
|
14
|
+
return (await runSingle(execute, operation, input));
|
|
15
|
+
const prepared = operation.prepare?.(input) ?? input;
|
|
14
16
|
const session = createQueryComposition();
|
|
15
|
-
const composed =
|
|
17
|
+
const composed = operation.compose(session.builder, prepared);
|
|
16
18
|
if (isPromiseLike(composed))
|
|
17
19
|
throw new TypeError('Composite Query composition must be synchronous.');
|
|
18
20
|
const plan = session.close(composed);
|
|
19
|
-
const result = await executeQueryPlan(plan, (child, childInput) => runSingle(execute, child, childInput));
|
|
20
|
-
return (
|
|
21
|
+
const result = await executeQueryPlan(plan, (child, childInput) => runSingle(execute, realizeSingle(child, domain), childInput));
|
|
22
|
+
return (operation.project === undefined ? result : operation.project(result, prepared));
|
|
21
23
|
}
|
|
22
24
|
catch (cause) {
|
|
23
25
|
const expected = queryFailure(cause);
|
|
24
|
-
if (
|
|
25
|
-
return
|
|
26
|
+
if (operation.reject !== undefined && expected !== undefined) {
|
|
27
|
+
return operation.reject(expected);
|
|
26
28
|
}
|
|
27
29
|
throw cause;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
32
|
+
function realizeSingle(definition, domain) {
|
|
33
|
+
const operation = realizeQuery(definition, domain);
|
|
34
|
+
if (operation.kind !== 'single')
|
|
35
|
+
throw new TypeError('Composite Query leaves must be single.');
|
|
36
|
+
return operation;
|
|
37
|
+
}
|
|
30
38
|
async function runSingle(execute, definition, input) {
|
|
31
39
|
const prepared = definition.prepare?.(input) ?? input;
|
|
32
40
|
const ast = definition.build(prepared);
|
|
@@ -2,8 +2,8 @@ import type { Domain } from '../../../platform/schema/index.js';
|
|
|
2
2
|
import type { QueryDefinition } from '../query.js';
|
|
3
3
|
import type { QueryClient } from './execute.js';
|
|
4
4
|
/** Invocation-bound executor for authored Query definitions. */
|
|
5
|
-
export interface QueryExecutor {
|
|
6
|
-
<Input, Output>(definition: QueryDefinition<Input, Output>, input: Input): Promise<Output>;
|
|
5
|
+
export interface QueryExecutor<DomainValue extends Domain = Domain> {
|
|
6
|
+
<Input, Output>(definition: QueryDefinition<DomainValue, Input, Output>, input: Input): Promise<Output>;
|
|
7
7
|
}
|
|
8
|
-
/** Private Runtime binding
|
|
9
|
-
export declare function bindQuery(client: QueryClient,
|
|
8
|
+
/** Private Runtime binding to one Client and the exact loaded root Domain. */
|
|
9
|
+
export declare function bindQuery<DomainValue extends Domain>(client: QueryClient, domain: DomainValue): QueryExecutor<DomainValue>;
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { executeQuery } from './execute.js';
|
|
2
|
-
/** Private Runtime binding
|
|
3
|
-
export function bindQuery(client,
|
|
4
|
-
|
|
5
|
-
query(ast, options) {
|
|
6
|
-
return client.query(ast, { ...options, expected });
|
|
7
|
-
},
|
|
8
|
-
});
|
|
9
|
-
return (definition, input) => executeQuery(bound, definition, input);
|
|
2
|
+
/** Private Runtime binding to one Client and the exact loaded root Domain. */
|
|
3
|
+
export function bindQuery(client, domain) {
|
|
4
|
+
return (definition, input) => executeQuery(client, domain, definition, input);
|
|
10
5
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { defineCompositeQuery, defineQuery } from './define.js';
|
|
1
|
+
export { defineCompositeQuery, defineQuery, realizeQuery } from './define.js';
|
|
2
2
|
export { executeQuery, type QueryClient, type QueryExecutor } from './execution/index.js';
|
|
3
3
|
export { QueryPaginationError, type QueryFailure, type QueryPaginationErrorCode, } from './failure.js';
|
|
4
|
-
export type { QueryAstOf, QueryDefinition, QueryInputOf, QueryOutputOf } from './query.js';
|
|
4
|
+
export type { CompositeQueryDefinition, QueryAstOf, QueryDefinition, QueryInputOf, QueryOutputOf, SingleQueryDefinition, } from './query.js';
|
|
5
5
|
export { queryResult } from './single/index.js';
|
|
6
6
|
export type { CompleteQueryProjectionContext, PaginatedQuery, ProjectedQuery, QueryPages, QueryPagination, QueryProjectionContext, RawQuery, SingleQuery, } from './single/index.js';
|
|
7
7
|
export type { CompositeQuery, ProjectedCompositeQuery, QueryComposition, QueryPlan, QueryPlanInput, QueryPlanOutput, QueryPlanValue, RawCompositeQuery, } from './composite/index.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { defineCompositeQuery, defineQuery } from './define.js';
|
|
1
|
+
export { defineCompositeQuery, defineQuery, realizeQuery } from './define.js';
|
|
2
2
|
export { executeQuery } from './execution/index.js';
|
|
3
3
|
export { QueryPaginationError, } from './failure.js';
|
|
4
4
|
export { queryResult } from './single/index.js';
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
import type { QueryAST, QueryResult } from '@astrale-os/kernel-core/graph/query';
|
|
2
|
+
import type { Domain } from '../../platform/schema/index.js';
|
|
2
3
|
import type { CompositeQuery } from './composite/index.js';
|
|
3
4
|
import type { QueryLifecycle, SingleQuery } from './single/index.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
declare const QUERY_DOMAIN: unique symbol;
|
|
6
|
+
declare const QUERY_INPUT: unique symbol;
|
|
7
|
+
declare const QUERY_OUTPUT: unique symbol;
|
|
8
|
+
/** Inert Domain-bound Query recipe. Its projector is retained privately by the SDK. */
|
|
9
|
+
export interface QueryDefinition<DomainValue extends Domain = Domain, Input = unknown, Output = QueryResult> {
|
|
10
|
+
readonly kind: 'query' | 'composite-query';
|
|
11
|
+
readonly [QUERY_DOMAIN]: (domain: DomainValue) => DomainValue;
|
|
12
|
+
readonly [QUERY_INPUT]?: Input;
|
|
13
|
+
readonly [QUERY_OUTPUT]?: Output;
|
|
14
|
+
}
|
|
15
|
+
export interface SingleQueryDefinition<DomainValue extends Domain = Domain, Input = unknown, Output = QueryResult> extends QueryDefinition<DomainValue, Input, Output> {
|
|
16
|
+
readonly kind: 'query';
|
|
17
|
+
}
|
|
18
|
+
export interface CompositeQueryDefinition<DomainValue extends Domain = Domain, Input = unknown, Output = unknown> extends QueryDefinition<DomainValue, Input, Output> {
|
|
19
|
+
readonly kind: 'composite-query';
|
|
20
|
+
}
|
|
21
|
+
/** Canonical admitted operation produced by realizing one Query recipe. */
|
|
22
|
+
export type RealizedQuery<Input, Output = QueryResult, Ast extends QueryAST = QueryAST> = SingleQuery<Input, Output, Ast> | CompositeQuery<Input, unknown, Output, Domain>;
|
|
23
|
+
export type QueryInputOf<Value> = Value extends QueryDefinition<infer _Domain, infer Input, infer _Output> ? Input : Value extends QueryLifecycle<infer Input, infer _Output> ? Input : never;
|
|
24
|
+
export type QueryOutputOf<Value> = Value extends QueryDefinition<infer _Domain, infer _Input, infer Output> ? Output : Value extends QueryLifecycle<infer _Input, infer Output> ? Output : never;
|
|
25
|
+
export type QueryAstOf<Value> = Value extends RealizedQuery<unknown, unknown, infer Ast> ? Ast : never;
|
|
26
|
+
export {};
|
|
@@ -21,8 +21,8 @@ export function context(request, runtime, authentication, self) {
|
|
|
21
21
|
...(client === null
|
|
22
22
|
? {}
|
|
23
23
|
: {
|
|
24
|
-
query: bindQuery(client, runtime.loaded.domain
|
|
25
|
-
mutate: bindMutation(client, runtime.loaded.domain
|
|
24
|
+
query: bindQuery(client, runtime.loaded.domain),
|
|
25
|
+
mutate: bindMutation(client, runtime.loaded.domain),
|
|
26
26
|
}),
|
|
27
27
|
integrations: clients(runtime, request.context, client),
|
|
28
28
|
execution,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { Domain } from '@astrale-os/kernel-dsl/v1/domain';
|
|
2
|
+
import type { DomainSchema, DomainOf } from '@astrale-os/kernel-dsl/v1/schema';
|
|
2
3
|
import type { RuntimeRegistry } from '../../application/runtime/index.js';
|
|
3
4
|
import type { Build } from '../../deployment/build/build.js';
|
|
4
5
|
import type { Release } from '../../deployment/release/release.js';
|
|
6
|
+
type DomainOfBuild<BuildValue extends Build> = BuildValue extends Build<infer Schema extends DomainSchema, object> ? Extract<DomainOf<Schema>, Domain> : Domain;
|
|
5
7
|
export interface LoadedRuntime<BuildValue extends Build = Build> {
|
|
6
8
|
readonly release: Release<BuildValue>;
|
|
7
|
-
readonly domain:
|
|
9
|
+
readonly domain: DomainOfBuild<BuildValue>;
|
|
8
10
|
readonly registry: RuntimeRegistry;
|
|
9
11
|
}
|
|
10
12
|
/** Load one execution Runtime from the exact evidence retained by its admitted Build. */
|
|
11
13
|
export declare function load<const BuildValue extends Build>(release: Release<BuildValue>): LoadedRuntime<BuildValue>;
|
|
14
|
+
export {};
|