@astrale-os/sdk 0.6.0-beta.43 → 0.6.0-beta.45
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 +6 -6
- package/dist/application/action/authoring.d.ts +1 -1
- package/dist/application/action/context.d.ts +9 -8
- package/dist/application/error/error.d.ts +7 -7
- package/dist/application/error/error.js +12 -12
- 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 +12 -12
- package/dist/application/mutation/authoring/builder.js +3 -3
- package/dist/application/mutation/authoring/guard.d.ts +2 -2
- package/dist/application/mutation/authoring/index.d.ts +1 -1
- package/dist/application/mutation/define.d.ts +8 -8
- package/dist/application/mutation/define.js +12 -12
- package/dist/application/mutation/execution/execute.d.ts +2 -2
- package/dist/application/mutation/execution/execute.js +10 -10
- package/dist/application/mutation/execution/executor.d.ts +2 -2
- package/dist/application/mutation/execution/executor.js +2 -2
- package/dist/application/mutation/index.d.ts +2 -2
- package/dist/application/mutation/legacy/v6/define.d.ts +1 -1
- package/dist/application/mutation/legacy/v6/mutation.d.ts +1 -1
- package/dist/application/mutation/mutation.d.ts +12 -12
- package/dist/application/query/composite/builder.js +1 -1
- package/dist/application/query/define.d.ts +1 -1
- package/dist/application/query/define.js +3 -3
- package/dist/application/query/defined/define.d.ts +45 -0
- package/dist/application/query/defined/define.js +96 -0
- package/dist/application/query/{recipe/recipe.d.ts → defined/query.d.ts} +21 -20
- package/dist/application/query/definition.d.ts +5 -5
- package/dist/application/query/definition.js +4 -4
- package/dist/application/query/execution/bounds.d.ts +5 -5
- package/dist/application/query/execution/bounds.js +2 -2
- package/dist/application/query/execution/client.d.ts +21 -0
- package/dist/application/query/execution/client.js +1 -0
- package/dist/application/query/execution/crossings.d.ts +3 -3
- package/dist/application/query/execution/crossings.js +23 -23
- package/dist/application/query/execution/error.d.ts +8 -8
- package/dist/application/query/execution/error.js +5 -5
- package/dist/application/query/execution/execute.d.ts +4 -4
- package/dist/application/query/execution/executor.d.ts +2 -2
- package/dist/application/query/execution/rows.d.ts +5 -5
- package/dist/application/query/execution/rows.js +42 -30
- package/dist/application/query/execution/{recipe.d.ts → run.d.ts} +21 -31
- package/dist/application/query/execution/{recipe.js → run.js} +49 -49
- package/dist/application/query/execution/split.d.ts +3 -3
- package/dist/application/query/execution/split.js +1 -1
- package/dist/application/query/handles/build.d.ts +1 -1
- package/dist/application/query/handles/domain.d.ts +1 -1
- package/dist/application/query/handles/handles.d.ts +14 -12
- package/dist/application/query/handles/plan.d.ts +10 -9
- package/dist/application/query/handles/plan.js +1 -1
- package/dist/application/query/handles/reads.js +4 -4
- package/dist/application/query/handles/rows.d.ts +1 -1
- package/dist/application/query/index.d.ts +9 -6
- package/dist/application/query/index.js +3 -3
- package/dist/application/query/language/index.d.ts +1 -1
- package/dist/application/query/language/query.d.ts +5 -5
- package/dist/application/query/legacy/v6/collection/union.d.ts +1 -1
- package/dist/application/query/legacy/v6/collection/union.js +3 -3
- package/dist/application/query/legacy/v6/define.d.ts +1 -1
- package/dist/application/query/legacy/v6/define.js +3 -3
- package/dist/application/query/legacy/v6/execution/execute.d.ts +6 -4
- package/dist/application/query/legacy/v6/index.d.ts +2 -1
- package/dist/application/query/legacy/v6/index.js +1 -1
- package/dist/application/query/query.d.ts +3 -3
- package/dist/application/routes/admission.d.ts +1 -1
- package/dist/application/routes/admission.js +2 -2
- package/dist/application/routes/index.d.ts +3 -2
- package/dist/application/routes/route.d.ts +1 -1
- package/dist/application/runtime/declarations.d.ts +6 -6
- package/dist/application/runtime/define.d.ts +5 -5
- package/dist/application/workflow/durable.d.ts +9 -9
- package/dist/execution/invocation/capabilities/bind.d.ts +1 -1
- package/dist/execution/invocation/capabilities/bind.js +8 -8
- package/dist/execution/invocation/dispatch/action.js +2 -2
- package/dist/execution/invocation/dispatch/workflow.js +2 -2
- package/dist/execution/invocation/result/index.d.ts +1 -1
- package/dist/execution/invocation/result/index.js +1 -1
- package/dist/execution/invocation/result/undeclared.d.ts +7 -0
- package/dist/execution/invocation/result/undeclared.js +18 -0
- package/dist/execution/workflows/failure.d.ts +7 -7
- package/dist/execution/workflows/failure.js +19 -18
- package/dist/execution/workflows/receiver.d.ts +1 -1
- package/dist/execution/workflows/run.js +2 -2
- package/dist/execution/workflows/steps.d.ts +1 -1
- package/dist/platform/schema/class-authoring.d.ts +2 -8
- package/dist/platform/schema/index.d.ts +1 -1
- package/dist/platform/schema/kernel-compiled.d.ts +1 -1
- package/dist/platform/schema/kernel-compiled.js +1 -1
- package/dist/platform/schema/kernel.d.ts +4 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +1 -1
- package/dist/testing/{recipe.d.ts → query.d.ts} +5 -5
- package/dist/testing/{recipe.js → query.js} +9 -9
- package/dist/tooling/cli/orchestrate.js +7 -6
- package/dist/tooling/linter/adapters/typescript/{recipe-definitions.d.ts → definition-calls.d.ts} +10 -10
- package/dist/tooling/linter/adapters/typescript/{recipe-definitions.js → definition-calls.js} +10 -10
- package/dist/tooling/linter/adapters/typescript/index.d.ts +1 -1
- package/dist/tooling/linter/adapters/typescript/index.js +1 -1
- package/dist/tooling/linter/adapters/typescript/query-observation.js +6 -6
- package/dist/tooling/linter/implementations/source/definitions.d.ts +2 -0
- package/dist/tooling/linter/implementations/source/{recipes.js → definitions.js} +27 -27
- package/dist/tooling/linter/implementations/source/global.js +8 -6
- package/dist/tooling/linter/implementations/source/mutations.js +5 -5
- package/dist/tooling/linter/implementations/source/shared.d.ts +2 -2
- package/dist/tooling/linter/implementations/source/shared.js +7 -7
- package/dist/tooling/linter/implementations/source/verifiers.js +2 -2
- package/dist/tooling/linter/policy/generated.js +3 -3
- package/dist/tooling/linter/requirements/registry.js +4 -4
- package/docs/durable-workflows.md +8 -8
- package/docs/handler-dependencies.md +1 -1
- package/docs/legacy/query-v6.md +6 -6
- package/docs/queries.md +38 -38
- package/package.json +6 -6
- package/dist/application/query/recipe/define.d.ts +0 -44
- package/dist/application/query/recipe/define.js +0 -95
- package/dist/execution/invocation/result/recipe.d.ts +0 -6
- package/dist/execution/invocation/result/recipe.js +0 -17
- package/dist/tooling/linter/implementations/source/recipes.d.ts +0 -2
- /package/dist/application/query/{recipe/recipe.js → defined/query.js} +0 -0
|
@@ -17,6 +17,8 @@ export interface KernelDomain extends schema.DomainOf<CoreKernelSchema> {
|
|
|
17
17
|
export interface KernelSchema extends DefinedSchema<CoreKernelSchema['origin'], KernelDomain> {
|
|
18
18
|
}
|
|
19
19
|
export declare const KernelSchema: KernelSchema;
|
|
20
|
-
|
|
21
|
-
export
|
|
20
|
+
/** The Kernel Domain, resolved: the SDK-owned name of `K`'s type. */
|
|
21
|
+
export interface Kernel extends schema.ResolvedDomainOf<KernelSchema> {
|
|
22
|
+
}
|
|
23
|
+
export declare const K: Kernel;
|
|
22
24
|
export {};
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -12,5 +12,5 @@ export { isTests, tests } from './tests.js';
|
|
|
12
12
|
export type { Tests, TestsInput } from './tests.js';
|
|
13
13
|
export { createTestWorkflowEngine } from './durable/index.js';
|
|
14
14
|
export type { TestExecution, TestJournalEntry, TestStartOptions, TestStepCapabilities, TestWorkflowEngine, TestWorkflowEngineOptions, } from './durable/index.js';
|
|
15
|
-
export {
|
|
16
|
-
export type {
|
|
15
|
+
export { checkQuery } from './query.js';
|
|
16
|
+
export type { CheckQueryOptions } from './query.js';
|
package/dist/testing/index.js
CHANGED
|
@@ -7,4 +7,4 @@ export { loadDataset, loadDatasets } from './load.js';
|
|
|
7
7
|
export { dataset, isDatasetReference } from './reference.js';
|
|
8
8
|
export { isTests, tests } from './tests.js';
|
|
9
9
|
export { createTestWorkflowEngine } from './durable/index.js';
|
|
10
|
-
export {
|
|
10
|
+
export { checkQuery } from './query.js';
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { DomainDefinitionIdentity } from '../application/domain.js';
|
|
2
|
-
import type { InputOf,
|
|
2
|
+
import type { InputOf, DefinedQuery } from '../application/query/defined/query.js';
|
|
3
3
|
import type { schema } from '../platform/schema/index.js';
|
|
4
|
-
export interface
|
|
4
|
+
export interface CheckQueryOptions {
|
|
5
5
|
/**
|
|
6
|
-
* The Domain the
|
|
6
|
+
* The Domain the Query reads: its definition, whose requirements S12 checks a relation
|
|
7
7
|
* against, or its Schema alone, which holds no requirements, so S12 is not checked.
|
|
8
8
|
*/
|
|
9
9
|
readonly domain: DomainDefinitionIdentity | schema.DomainSchema;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Build a
|
|
12
|
+
* Build a Query for `sampleInput` and lower its first step, as a call would, without a Kernel:
|
|
13
13
|
* it throws what the call would raise before sending, a bound past S11 (`QueryError` `TOO_MANY` or
|
|
14
14
|
* `INPUT_INVALID`), a relation across a Class the Domain's definition does not declare (S12), or a
|
|
15
15
|
* step that does not build.
|
|
16
16
|
*/
|
|
17
|
-
export declare function
|
|
17
|
+
export declare function checkQuery<R extends DefinedQuery>(query: R, sampleInput: InputOf<R>, options: CheckQueryOptions): void;
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import { isDomainDefinition } from '../application/define.js';
|
|
2
|
-
import {
|
|
2
|
+
import { querySpecOf } from '../application/query/defined/define.js';
|
|
3
|
+
import { planStep, Run, stepsFor, stepsOf } from '../application/query/execution/run.js';
|
|
3
4
|
import { handlesOf } from '../application/query/handles/build.js';
|
|
4
5
|
import { describe } from '../application/query/handles/terms.js';
|
|
5
|
-
import { recipeSpecOf } from '../application/query/recipe/define.js';
|
|
6
6
|
import { schema as schemaApi } from '../platform/schema/index.js';
|
|
7
7
|
/** Never reached: a check sends nothing. */
|
|
8
8
|
const NO_KERNEL = Object.freeze({
|
|
9
|
-
query: () => Promise.reject(new Error('
|
|
9
|
+
query: () => Promise.reject(new Error('checkQuery sends no read.')),
|
|
10
10
|
});
|
|
11
11
|
/**
|
|
12
|
-
* Build a
|
|
12
|
+
* Build a Query for `sampleInput` and lower its first step, as a call would, without a Kernel:
|
|
13
13
|
* it throws what the call would raise before sending, a bound past S11 (`QueryError` `TOO_MANY` or
|
|
14
14
|
* `INPUT_INVALID`), a relation across a Class the Domain's definition does not declare (S12), or a
|
|
15
15
|
* step that does not build.
|
|
16
16
|
*/
|
|
17
|
-
export function
|
|
18
|
-
const spec =
|
|
17
|
+
export function checkQuery(query, sampleInput, options) {
|
|
18
|
+
const spec = querySpecOf(query);
|
|
19
19
|
if (spec === undefined)
|
|
20
|
-
throw new TypeError(`Not a v7
|
|
20
|
+
throw new TypeError(`Not a Query v7: define one with defineQuery<Schema>()(id, { build }); got ${describe(query)}.`);
|
|
21
21
|
const definition = isDomainDefinition(options?.domain) ? options.domain : undefined;
|
|
22
22
|
const domain = schemaApi.resolve(definition?.schema ?? options?.domain);
|
|
23
23
|
// A definition types its requirements as written; the check reads any.
|
|
24
24
|
const requirements = definition?.requirements;
|
|
25
25
|
const declared = definition === undefined ? undefined : (requirements?.classes ?? []);
|
|
26
|
-
const run = new Run(NO_KERNEL, domain,
|
|
26
|
+
const run = new Run(NO_KERNEL, domain, query.id, declared);
|
|
27
27
|
const build = spec.build;
|
|
28
|
-
const built = build(sampleInput, handlesOf(domain), stepsFor(
|
|
28
|
+
const built = build(sampleInput, handlesOf(domain), stepsFor(query.id));
|
|
29
29
|
const steps = stepsOf(built);
|
|
30
30
|
if (steps === undefined)
|
|
31
31
|
planStep(run, built, true);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { admitDefinitions, collectDefinitions, } from '../../application/query/defined/define.js';
|
|
4
4
|
import { admitRuntime, compile, resolveRuntime } from '../../deployment/index.js';
|
|
5
5
|
import { packageDomain } from '../packaging/index.js';
|
|
6
6
|
import { runAdapterCommand } from './adapter-command.js';
|
|
@@ -61,9 +61,10 @@ export async function run(argv) {
|
|
|
61
61
|
error(`No astrale.config.ts found in ${projectDir}.`);
|
|
62
62
|
return 1;
|
|
63
63
|
}
|
|
64
|
-
// The
|
|
65
|
-
|
|
66
|
-
const
|
|
64
|
+
// The Queries and Mutations loading this project and its runtime define: the ones the build
|
|
65
|
+
// checks together.
|
|
66
|
+
const queries = [];
|
|
67
|
+
const project = await collectDefinitions(queries, () => loadProject(configPath));
|
|
67
68
|
if (parsed.command === 'list')
|
|
68
69
|
return list(parsed, project, projectDir);
|
|
69
70
|
if (parsed.command === 'adapter') {
|
|
@@ -104,9 +105,9 @@ export async function run(argv) {
|
|
|
104
105
|
});
|
|
105
106
|
}
|
|
106
107
|
const build = compile(project.domain);
|
|
107
|
-
const resolved = await
|
|
108
|
+
const resolved = await collectDefinitions(queries, () => resolveRuntime(runtimeEntrypoint(project), (path) => importModule(resolve(dirname(configPath), path))));
|
|
108
109
|
admitRuntime({ runtime: build.runtime }, resolved);
|
|
109
|
-
|
|
110
|
+
admitDefinitions(queries);
|
|
110
111
|
if (parsed.command === 'diff') {
|
|
111
112
|
const lifecycle = deploymentLifecycle('Diff interrupted.');
|
|
112
113
|
try {
|
package/dist/tooling/linter/adapters/typescript/{recipe-definitions.d.ts → definition-calls.d.ts}
RENAMED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import type { SourceFile } from './index.js';
|
|
3
|
-
/** The modules each
|
|
4
|
-
declare const
|
|
3
|
+
/** The modules each definition factory is exported from. */
|
|
4
|
+
declare const FACTORY_MODULES: {
|
|
5
5
|
readonly defineQuery: Set<string>;
|
|
6
6
|
readonly defineMutation: Set<string>;
|
|
7
7
|
};
|
|
8
|
-
/** The factories that define
|
|
9
|
-
export type
|
|
8
|
+
/** The factories that define from an id: Queries v7, and Mutations (D31). */
|
|
9
|
+
export type DefinitionFactory = keyof typeof FACTORY_MODULES;
|
|
10
10
|
/**
|
|
11
|
-
* A
|
|
11
|
+
* A definition from an id: `defineQuery<Schema>()` applied to an id and a spec `{ build, output? }`, or
|
|
12
12
|
* `defineMutation<Schema>()` applied to an id and a spec `{ build, output?, reject? }`.
|
|
13
13
|
*/
|
|
14
|
-
export interface
|
|
14
|
+
export interface DefinitionCall {
|
|
15
15
|
/** The application `(id, spec)`. */
|
|
16
16
|
readonly call: ts.CallExpression;
|
|
17
17
|
/** The factory, `defineQuery<Schema>()`, applied directly or through the const it is bound to. */
|
|
@@ -19,16 +19,16 @@ export interface RecipeDefinition {
|
|
|
19
19
|
readonly origin: 'resolved' | 'ambiguous';
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* The
|
|
22
|
+
* The definitions a file makes with `name` from an id. Each applies `defineQuery<Schema>()` (or
|
|
23
23
|
* `defineMutation<Schema>()`) to two values, its id and its spec, where a V6 Query (or a Mutation
|
|
24
24
|
* of the closure form) applies it to one Domain projector. A const bound to the factory is followed
|
|
25
25
|
* to its applications in the same file.
|
|
26
26
|
*/
|
|
27
|
-
export declare function
|
|
27
|
+
export declare function definitionCalls(file: SourceFile, name?: DefinitionFactory): readonly DefinitionCall[];
|
|
28
28
|
/**
|
|
29
|
-
* Whether a `defineQuery<Schema>()` (or `defineMutation<Schema>()`) call
|
|
29
|
+
* Whether a `defineQuery<Schema>()` (or `defineMutation<Schema>()`) call defines from an id: applied to
|
|
30
30
|
* an id and a spec, or bound to a const that no application in the file passes one Domain
|
|
31
31
|
* projector.
|
|
32
32
|
*/
|
|
33
|
-
export declare function
|
|
33
|
+
export declare function isDefinitionFactory(file: SourceFile, factory: ts.CallExpression): boolean;
|
|
34
34
|
export {};
|
package/dist/tooling/linter/adapters/typescript/{recipe-definitions.js → definition-calls.js}
RENAMED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { resolveImportedSymbol, unwrap, visit } from './syntax.js';
|
|
3
|
-
/** The modules each
|
|
4
|
-
const
|
|
3
|
+
/** The modules each definition factory is exported from. */
|
|
4
|
+
const FACTORY_MODULES = {
|
|
5
5
|
defineQuery: new Set(['@astrale-os/sdk', '@astrale-os/sdk/query']),
|
|
6
6
|
defineMutation: new Set(['@astrale-os/sdk', '@astrale-os/sdk/mutation']),
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
|
-
* The
|
|
9
|
+
* The definitions a file makes with `name` from an id. Each applies `defineQuery<Schema>()` (or
|
|
10
10
|
* `defineMutation<Schema>()`) to two values, its id and its spec, where a V6 Query (or a Mutation
|
|
11
11
|
* of the closure form) applies it to one Domain projector. A const bound to the factory is followed
|
|
12
12
|
* to its applications in the same file.
|
|
13
13
|
*/
|
|
14
|
-
export function
|
|
15
|
-
const
|
|
14
|
+
export function definitionCalls(file, name = 'defineQuery') {
|
|
15
|
+
const definitions = [];
|
|
16
16
|
visit(file.source, (node) => {
|
|
17
17
|
if (!ts.isCallExpression(node) || node.arguments.length !== 2)
|
|
18
18
|
return;
|
|
@@ -20,16 +20,16 @@ export function recipeDefinitions(file, name = 'defineQuery') {
|
|
|
20
20
|
const factory = ts.isCallExpression(callee) ? callee : boundFactory(file, callee);
|
|
21
21
|
const origin = factory && factoryOrigin(file, factory, name);
|
|
22
22
|
if (factory && origin)
|
|
23
|
-
|
|
23
|
+
definitions.push({ call: node, factory, origin });
|
|
24
24
|
});
|
|
25
|
-
return
|
|
25
|
+
return definitions;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Whether a `defineQuery<Schema>()` (or `defineMutation<Schema>()`) call
|
|
28
|
+
* Whether a `defineQuery<Schema>()` (or `defineMutation<Schema>()`) call defines from an id: applied to
|
|
29
29
|
* an id and a spec, or bound to a const that no application in the file passes one Domain
|
|
30
30
|
* projector.
|
|
31
31
|
*/
|
|
32
|
-
export function
|
|
32
|
+
export function isDefinitionFactory(file, factory) {
|
|
33
33
|
const parent = factory.parent;
|
|
34
34
|
if (ts.isCallExpression(parent) && unwrap(parent.expression) === factory)
|
|
35
35
|
return parent.arguments.length === 2;
|
|
@@ -51,7 +51,7 @@ function factoryOrigin(file, factory, name) {
|
|
|
51
51
|
return undefined;
|
|
52
52
|
if (symbol.kind === 'ambiguous')
|
|
53
53
|
return 'ambiguous';
|
|
54
|
-
return
|
|
54
|
+
return FACTORY_MODULES[name].has(symbol.module) ? 'resolved' : undefined;
|
|
55
55
|
}
|
|
56
56
|
/** The factory a top-level const binds, when the callee names one. */
|
|
57
57
|
function boundFactory(file, callee) {
|
|
@@ -5,7 +5,7 @@ export { bindingContains, importedSymbol, isLocallyDeclared, resolveImportedSymb
|
|
|
5
5
|
export { assertSingleTypeUniverse } from './type-universe.js';
|
|
6
6
|
export { observeQueryDefinitions } from './query-observation.js';
|
|
7
7
|
export { queryCollectionKind } from './query-collection-type.js';
|
|
8
|
-
export {
|
|
8
|
+
export { isDefinitionFactory, definitionCalls, type DefinitionCall, type DefinitionFactory, } from './definition-calls.js';
|
|
9
9
|
export { declarationOf, expressionType, type ExpressionType } from './type-analysis.js';
|
|
10
10
|
export { collectSuppressionDirectives } from './suppression-directives.js';
|
|
11
11
|
import type ts from 'typescript';
|
|
@@ -5,6 +5,6 @@ export { bindingContains, importedSymbol, isLocallyDeclared, resolveImportedSymb
|
|
|
5
5
|
export { assertSingleTypeUniverse } from './type-universe.js';
|
|
6
6
|
export { observeQueryDefinitions } from './query-observation.js';
|
|
7
7
|
export { queryCollectionKind } from './query-collection-type.js';
|
|
8
|
-
export {
|
|
8
|
+
export { isDefinitionFactory, definitionCalls, } from './definition-calls.js';
|
|
9
9
|
export { declarationOf, expressionType } from './type-analysis.js';
|
|
10
10
|
export { collectSuppressionDirectives } from './suppression-directives.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { ambiguous, known, unknown, } from '../../analysis/query/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { definitionCalls } from './definition-calls.js';
|
|
4
4
|
import { location, resolveImportedSymbol, unwrap, visit } from './syntax.js';
|
|
5
5
|
const SDK_MODULES = new Set(['@astrale-os/sdk', '@astrale-os/sdk/query']);
|
|
6
6
|
const QUERY_MODULES = new Set([
|
|
@@ -14,9 +14,9 @@ export function observeQueryDefinitions(project) {
|
|
|
14
14
|
.flatMap(observeFile));
|
|
15
15
|
}
|
|
16
16
|
function observeFile(file) {
|
|
17
|
-
const
|
|
18
|
-
const applications = new Set(
|
|
19
|
-
const observations =
|
|
17
|
+
const definitions = definitionCalls(file);
|
|
18
|
+
const applications = new Set(definitions.map(({ call }) => call));
|
|
19
|
+
const observations = definitions.map((definition) => definitionObservation(file, definition));
|
|
20
20
|
visit(file.source, (node) => {
|
|
21
21
|
if (!ts.isCallExpression(node) || applications.has(node))
|
|
22
22
|
return;
|
|
@@ -79,10 +79,10 @@ function observeFile(file) {
|
|
|
79
79
|
return observations;
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
-
* A v7
|
|
82
|
+
* A Query v7 is one single definition: its build returns reads built from the Domain's handles,
|
|
83
83
|
* which lower to canonical Query JSON by construction, and it has no projection callback.
|
|
84
84
|
*/
|
|
85
|
-
function
|
|
85
|
+
function definitionObservation(file, { call, factory, origin }) {
|
|
86
86
|
const subjectId = `${file.path}#${call.getStart(file.source)}`;
|
|
87
87
|
const sourceLocation = location(file, call);
|
|
88
88
|
if (origin === 'ambiguous')
|
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import { declarationOf, expressionType, isLocallyDeclared,
|
|
2
|
+
import { declarationOf, expressionType, isLocallyDeclared, definitionCalls, unwrap, visit, } from '../../adapters/typescript/index.js';
|
|
3
3
|
import { production, propertyExpression, returnedExpressions, violation } from './shared.js';
|
|
4
|
-
/** Rows of bound product past which a
|
|
4
|
+
/** Rows of bound product past which a Query is flagged: a tenth of what one call may read. */
|
|
5
5
|
const ROWS = 10_000;
|
|
6
|
-
export const
|
|
6
|
+
export const definitionRules = [
|
|
7
7
|
{
|
|
8
|
-
id: 'QRY-
|
|
9
|
-
ruleRevision: '
|
|
8
|
+
id: 'QRY-CLOCK',
|
|
9
|
+
ruleRevision: '776cea7dd21460547625a1e6b7a74fcd80956bd7f4cfbdc6641e6458661095ee',
|
|
10
10
|
evaluate(project) {
|
|
11
|
-
return
|
|
11
|
+
return definitionsOf(project).flatMap(({ file, definition }) => clockEvidence(file, definition));
|
|
12
12
|
},
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
id: 'QRY-
|
|
16
|
-
ruleRevision: '
|
|
15
|
+
id: 'QRY-ROWS',
|
|
16
|
+
ruleRevision: '2b470871ff394e42f19b7fc1ddcfcd4e853ced9e65412e3dfe0d0c66e9caf4a2',
|
|
17
17
|
evaluate(project) {
|
|
18
|
-
return
|
|
18
|
+
return definitionsOf(project).flatMap(({ file, definition }) => rowsEvidence(project, file, definition));
|
|
19
19
|
},
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
id: 'QRY-
|
|
23
|
-
ruleRevision: '
|
|
22
|
+
id: 'QRY-FALSY',
|
|
23
|
+
ruleRevision: '3041a35ba8209331f3c64d8aada93f6b7a8194802a23927f29d919a1e7354719',
|
|
24
24
|
evaluate(project) {
|
|
25
|
-
return
|
|
25
|
+
return definitionsOf(project).flatMap(({ file, definition }) => falsyFilterEvidence(project, file, definition));
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
];
|
|
29
|
-
function
|
|
30
|
-
return production(project, 'queries').flatMap((file) =>
|
|
29
|
+
function definitionsOf(project) {
|
|
30
|
+
return production(project, 'queries').flatMap((file) => definitionCalls(file)
|
|
31
31
|
.filter(({ origin }) => origin === 'resolved')
|
|
32
|
-
.map((
|
|
32
|
+
.map((definition) => ({ file, definition })));
|
|
33
33
|
}
|
|
34
|
-
/** A
|
|
34
|
+
/** A Query's spec as written: its id, and its `build` and `output` written inline. */
|
|
35
35
|
function specOf(file, { call }) {
|
|
36
36
|
const id = unwrap(call.arguments[0]);
|
|
37
37
|
const spec = unwrap(call.arguments[1]);
|
|
@@ -57,8 +57,8 @@ const CLOCKS = [
|
|
|
57
57
|
['process', 'uptime'],
|
|
58
58
|
];
|
|
59
59
|
/** Each clock read in `build` and `output`, their callbacks included: they all run in the call. */
|
|
60
|
-
function clockEvidence(file,
|
|
61
|
-
const { name, build, output } = specOf(file,
|
|
60
|
+
function clockEvidence(file, definition) {
|
|
61
|
+
const { name, build, output } = specOf(file, definition);
|
|
62
62
|
const evidence = [];
|
|
63
63
|
const fields = [
|
|
64
64
|
...(build === undefined ? [] : [{ field: 'build', fn: build }]),
|
|
@@ -69,7 +69,7 @@ function clockEvidence(file, recipe) {
|
|
|
69
69
|
const read = clockRead(node);
|
|
70
70
|
if (read === undefined)
|
|
71
71
|
return;
|
|
72
|
-
evidence.push(violation(file, node, `
|
|
72
|
+
evidence.push(violation(file, node, `Query ${name}'s ${field} reads the clock (${read}): no write moves time, so nothing would refresh the read. Take now in the Query's input.`));
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
return evidence;
|
|
@@ -105,12 +105,12 @@ function isGlobal(expression, path) {
|
|
|
105
105
|
return ts.isIdentifier(current) && current.text === path[0] && !isLocallyDeclared(current);
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
|
-
* A
|
|
108
|
+
* A Query whose bounds multiply past `ROWS`, counted as a call counts them (S11): each verb's
|
|
109
109
|
* rows, `find` 2, `first(n)` n, `all` and `page` max + 1, times one plus what each row carries.
|
|
110
|
-
* A bound that is not a constant counts nothing, so the count is the least the
|
|
110
|
+
* A bound that is not a constant counts nothing, so the count is the least the Query may read.
|
|
111
111
|
*/
|
|
112
|
-
function rowsEvidence(project, file,
|
|
113
|
-
const { name, build } = specOf(file,
|
|
112
|
+
function rowsEvidence(project, file, definition) {
|
|
113
|
+
const { name, build } = specOf(file, definition);
|
|
114
114
|
const handles = boundNames(build?.parameters[1]?.name);
|
|
115
115
|
if (build === undefined || handles.size === 0)
|
|
116
116
|
return [];
|
|
@@ -126,7 +126,7 @@ function rowsEvidence(project, file, recipe) {
|
|
|
126
126
|
if (rows <= ROWS)
|
|
127
127
|
return [];
|
|
128
128
|
return [
|
|
129
|
-
violation(file,
|
|
129
|
+
violation(file, definition.call, `Query ${name} reads up to ${rows.toLocaleString('en-US')} rows, the product of its bounds, past ${ROWS.toLocaleString('en-US')}: lower a bound, or split the read.`),
|
|
130
130
|
];
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
@@ -288,8 +288,8 @@ const COMBINATIONS = new Set(['and', 'or']);
|
|
|
288
288
|
* number: the entry is then `false`, `0` or `undefined`, which is skipped, so the comparison is
|
|
289
289
|
* dropped.
|
|
290
290
|
*/
|
|
291
|
-
function falsyFilterEvidence(project, file,
|
|
292
|
-
const { name, build } = specOf(file,
|
|
291
|
+
function falsyFilterEvidence(project, file, definition) {
|
|
292
|
+
const { name, build } = specOf(file, definition);
|
|
293
293
|
const evidence = [];
|
|
294
294
|
if (build !== undefined)
|
|
295
295
|
visit(build, (node) => {
|
|
@@ -306,7 +306,7 @@ function falsyFilterEvidence(project, file, recipe) {
|
|
|
306
306
|
const explicit = admitted === 'number'
|
|
307
307
|
? `${value} !== undefined && ${value} !== 0 && …`
|
|
308
308
|
: `${value} ? ….eq(true) : undefined`;
|
|
309
|
-
evidence.push(violation(file, entry, `
|
|
309
|
+
evidence.push(violation(file, entry, `Query ${name} drops the condition on ${value} when ${value} is false or 0: the ${skipped.call} entry is then falsy, and ${skipped.call} skips it. To compare whenever ${value} is set, write ${value} !== undefined && …; if false or 0 means no filter, write that explicitly: ${explicit}.`));
|
|
310
310
|
}
|
|
311
311
|
});
|
|
312
312
|
return evidence;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import {
|
|
2
|
+
import { DEFINITION_ID } from '../../../../application/query/defined/define.js';
|
|
3
3
|
import { importedSymbol, resolveImportedSymbol, resolveProjectImport, unwrap, visit, } from '../../adapters/typescript/index.js';
|
|
4
4
|
import { DSL_MODULES, SDK_MODULES, ambiguity, definitionOriginAmbiguity, definitionObjects, isDeepPackageImport, isForeignDomain, isLocallyBoundIdentifier, packageRoot, production, propertyExpression, violation, } from './shared.js';
|
|
5
5
|
const CANONICAL_TYPES = new Set([
|
|
@@ -530,15 +530,17 @@ export const globalRules = [
|
|
|
530
530
|
ts.isNoSubstitutionTemplateLiteral(idExpression))
|
|
531
531
|
? idExpression.text
|
|
532
532
|
: undefined;
|
|
533
|
-
// A v7
|
|
534
|
-
const form = candidate.id ?
|
|
533
|
+
// A Query v7 or defined Mutation id is the one `defineQuery` admits; any other keeps the stable form.
|
|
534
|
+
const form = candidate.id ? DEFINITION_ID : STABLE_DEFINITION_ID;
|
|
535
535
|
if (!idExpression || !id || !form.test(id)) {
|
|
536
536
|
evidence.push(violation(file, idExpression ?? call, `${definition.name}${member ? `.${member}` : ''} ID must be a stable semantic literal.`));
|
|
537
537
|
continue;
|
|
538
538
|
}
|
|
539
|
-
// An id is unique in its layer;
|
|
540
|
-
// the one the runtime admits them in.
|
|
541
|
-
const scopes = candidate.id
|
|
539
|
+
// An id is unique in its layer; Queries and Mutations defined from an id also share one
|
|
540
|
+
// namespace, the one the runtime admits them in.
|
|
541
|
+
const scopes = candidate.id
|
|
542
|
+
? [definition.layer, 'Query or Mutation']
|
|
543
|
+
: [definition.layer];
|
|
542
544
|
const duplicated = scopes.find((scope) => owners.has(`${scope}\0${id}`));
|
|
543
545
|
if (duplicated) {
|
|
544
546
|
evidence.push(violation(file, idExpression, `${duplicated} definition ID ${id} is duplicated; first declared in ${owners.get(`${duplicated}\0${id}`).file.path}.`));
|
|
@@ -540,7 +540,7 @@ function mutationBuilderScopes(file) {
|
|
|
540
540
|
continue;
|
|
541
541
|
}
|
|
542
542
|
for (const [imported, local] of sourceImport.bindings) {
|
|
543
|
-
if (imported === 'RichMutationBuilder' || imported === '
|
|
543
|
+
if (imported === 'RichMutationBuilder' || imported === 'DefinedMutationBuilder')
|
|
544
544
|
richBuilderTypes.add(local);
|
|
545
545
|
}
|
|
546
546
|
}
|
|
@@ -663,7 +663,7 @@ function classOperationForBuilder(operation) {
|
|
|
663
663
|
}
|
|
664
664
|
}
|
|
665
665
|
function dependencyClassTarget(expression, handles) {
|
|
666
|
-
// A
|
|
666
|
+
// A defined Mutation names a dependency's Class by its handle, `d.<alias>.<Class>`.
|
|
667
667
|
const path = handlePath(expression, handles);
|
|
668
668
|
if (path !== undefined) {
|
|
669
669
|
return path.length === 2 && !OWN_HANDLES.has(path[0])
|
|
@@ -721,7 +721,7 @@ function handleParameters(file) {
|
|
|
721
721
|
return parameters;
|
|
722
722
|
}
|
|
723
723
|
/**
|
|
724
|
-
* The handle a
|
|
724
|
+
* The handle a defined Mutation's expression names, as its path from `d`: `d.Item` and a destructured
|
|
725
725
|
* `{ Item }` are `['Item']`, `d.directory.Person` is `['directory', 'Person']`. Undefined for an
|
|
726
726
|
* expression that does not start from the handles, or not statically.
|
|
727
727
|
*/
|
|
@@ -775,7 +775,7 @@ function bindingPath(binding, handles, seen) {
|
|
|
775
775
|
? path
|
|
776
776
|
: undefined;
|
|
777
777
|
}
|
|
778
|
-
/** The builder a Mutation's `build` receives: second in the closure form, third in a
|
|
778
|
+
/** The builder a Mutation's `build` receives: second in the closure form, third in a defined Mutation. */
|
|
779
779
|
function builderParameter(definition, build) {
|
|
780
780
|
return build.parameters[definition.id === undefined ? 1 : 2];
|
|
781
781
|
}
|
|
@@ -1317,7 +1317,7 @@ function selectedClassNames(expression, handles, seen = new Set()) {
|
|
|
1317
1317
|
const whenFalse = selectedClassNames(value.whenFalse, handles, seen);
|
|
1318
1318
|
return whenTrue && whenFalse ? new Set([...whenTrue, ...whenFalse]) : undefined;
|
|
1319
1319
|
}
|
|
1320
|
-
// A
|
|
1320
|
+
// A defined Mutation names its Class by its handle: the last segment of its path from `d`.
|
|
1321
1321
|
const path = handlePath(value, handles);
|
|
1322
1322
|
if (path !== undefined)
|
|
1323
1323
|
return path.length > 0 ? new Set([path.at(-1)]) : undefined;
|
|
@@ -7,7 +7,7 @@ export interface DefinitionObject {
|
|
|
7
7
|
readonly call: ts.CallExpression;
|
|
8
8
|
readonly object?: ts.ObjectLiteralExpression;
|
|
9
9
|
readonly projector?: ts.FunctionLikeDeclaration;
|
|
10
|
-
/** A
|
|
10
|
+
/** A definition's id, its first argument: a Query v7 or a defined Mutation has no Domain projector. */
|
|
11
11
|
readonly id?: ts.Expression;
|
|
12
12
|
readonly origin: 'resolved' | 'ambiguous';
|
|
13
13
|
}
|
|
@@ -23,7 +23,7 @@ export declare function memberCalls(file: SourceFile, importedName: string, memb
|
|
|
23
23
|
export declare function definitionObjects(file: SourceFile, importedName: string, member?: string, modules?: Set<string>, argumentIndex?: number): DefinitionObject[];
|
|
24
24
|
export declare function definitionOriginAmbiguity(file: SourceFile, definition: DefinitionObject, name: string): SourceRuleEvidence | undefined;
|
|
25
25
|
/**
|
|
26
|
-
* Prove the clean curried Domain projector shape for Query and Mutation definitions. A
|
|
26
|
+
* Prove the clean curried Domain projector shape for Query and Mutation definitions. A definition from an id,
|
|
27
27
|
* `defineQuery<typeof Schema>()(id, { build })` or `defineMutation<typeof Schema>()(id, { build })`,
|
|
28
28
|
* has no projector to prove.
|
|
29
29
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import { bindingContains, importedSymbol,
|
|
2
|
+
import { bindingContains, importedSymbol, isDefinitionFactory, location, objectProperty, propertyChain, definitionCalls, resolveImportedSymbol, resolveProjectImport, staticText, unwrap, visit, } from '../../adapters/typescript/index.js';
|
|
3
3
|
export const SDK_MODULES = new Set([
|
|
4
4
|
'@astrale-os/sdk',
|
|
5
5
|
'@astrale-os/sdk/action',
|
|
@@ -59,10 +59,10 @@ export function memberCalls(file, importedName, member, modules = SDK_MODULES) {
|
|
|
59
59
|
}
|
|
60
60
|
export function definitionObjects(file, importedName, member, modules = SDK_MODULES, argumentIndex = 0) {
|
|
61
61
|
if (!member && DOMAIN_BOUND_DEFINITIONS.has(importedName)) {
|
|
62
|
-
const
|
|
63
|
-
?
|
|
62
|
+
const definitions = importedName === 'defineQuery' || importedName === 'defineMutation'
|
|
63
|
+
? definitionCalls(file, importedName)
|
|
64
64
|
: [];
|
|
65
|
-
const applications = new Set(
|
|
65
|
+
const applications = new Set(definitions.map(({ call }) => call));
|
|
66
66
|
return [
|
|
67
67
|
...curriedCalls(file, importedName, modules)
|
|
68
68
|
.filter(({ call }) => !applications.has(call))
|
|
@@ -75,7 +75,7 @@ export function definitionObjects(file, importedName, member, modules = SDK_MODU
|
|
|
75
75
|
object: projector && returnedObject(projector),
|
|
76
76
|
};
|
|
77
77
|
}),
|
|
78
|
-
...
|
|
78
|
+
...definitions.map(({ call, origin }) => ({
|
|
79
79
|
call,
|
|
80
80
|
origin,
|
|
81
81
|
id: call.arguments[0],
|
|
@@ -122,7 +122,7 @@ export function definitionOriginAmbiguity(file, definition, name) {
|
|
|
122
122
|
: undefined;
|
|
123
123
|
}
|
|
124
124
|
/**
|
|
125
|
-
* Prove the clean curried Domain projector shape for Query and Mutation definitions. A
|
|
125
|
+
* Prove the clean curried Domain projector shape for Query and Mutation definitions. A definition from an id,
|
|
126
126
|
* `defineQuery<typeof Schema>()(id, { build })` or `defineMutation<typeof Schema>()(id, { build })`,
|
|
127
127
|
* has no projector to prove.
|
|
128
128
|
*/
|
|
@@ -132,7 +132,7 @@ export function domainProjectorEvidence(file, importedName, label) {
|
|
|
132
132
|
if (candidate.origin === 'ambiguous')
|
|
133
133
|
continue;
|
|
134
134
|
if ((importedName === 'defineQuery' || importedName === 'defineMutation') &&
|
|
135
|
-
|
|
135
|
+
isDefinitionFactory(file, candidate.call))
|
|
136
136
|
continue;
|
|
137
137
|
const application = candidate.call.parent;
|
|
138
138
|
if (candidate.call.arguments.length !== 0 ||
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { actionRules } from './actions.js';
|
|
2
|
+
import { definitionRules } from './definitions.js';
|
|
2
3
|
import { dependencyRules } from './dependencies.js';
|
|
3
4
|
import { globalRules } from './global.js';
|
|
4
5
|
import { integrationRules } from './integrations.js';
|
|
@@ -6,7 +7,6 @@ import { mutationRules } from './mutations.js';
|
|
|
6
7
|
import { policyEdgeRules } from './policy-edges.js';
|
|
7
8
|
import { providerRules } from './providers.js';
|
|
8
9
|
import { queryRules } from './queries.js';
|
|
9
|
-
import { recipeRules } from './recipes.js';
|
|
10
10
|
import { pureRuleRules } from './rules.js';
|
|
11
11
|
import { schemaRules } from './schema.js';
|
|
12
12
|
import { stateRules } from './states.js';
|
|
@@ -22,7 +22,7 @@ export const sourceRuleVerifiers = Object.freeze([
|
|
|
22
22
|
...stateRules,
|
|
23
23
|
...pureRuleRules,
|
|
24
24
|
...queryRules,
|
|
25
|
-
...
|
|
25
|
+
...definitionRules,
|
|
26
26
|
...mutationRules,
|
|
27
27
|
...workflowRules,
|
|
28
28
|
...dependencyRules,
|