@astrale-os/kernel-dsl 0.2.0-beta.31 → 0.2.0-beta.32
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/v1/builder/callable/authoring.d.ts +20 -13
- package/dist/v1/builder/callable/authoring.js +2 -2
- package/dist/v1/builder/class/authoring.d.ts +27 -9
- package/dist/v1/builder/lowering/callable.js +5 -5
- package/dist/v1/builder/lowering/schema/context.js +1 -1
- package/dist/v1/builder/schema/projection.d.ts +10 -10
- package/dist/v1/compiled/admission/accept.js +3 -7
- package/dist/v1/compiled/admission/semantics.js +20 -55
- package/dist/v1/compiled/compiler/compile.js +1 -1
- package/dist/v1/domain/projection/classes.js +9 -6
- package/dist/v1/index.d.ts +1 -1
- package/dist/v1/language/callable/composition.d.ts +15 -0
- package/dist/v1/language/callable/composition.js +42 -0
- package/dist/v1/language/callable/model/callable.d.ts +1 -2
- package/dist/v1/language/callable/resolved.d.ts +5 -5
- package/dist/v1/schema/analysis/class.d.ts +3 -3
- package/dist/v1/schema/analysis/class.js +7 -56
- package/dist/v1/schema/normalization/domain.js +1 -1
- package/dist/v1/schema/validation/class.js +1 -1
- package/dist/v1/schema/validation/domain.structure.js +2 -2
- package/dist/v1/schema/validation/policy/objects.js +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Value as PortableValue } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import type { z } from 'zod';
|
|
3
|
-
import type { FunctionAuth
|
|
3
|
+
import type { FunctionAuth } from '../../language/callable/model/callable.js';
|
|
4
4
|
import type { PolicyCheckExpression, PolicyCheckObject, PolicyObjectRef } from '../../language/policy/model/policy.js';
|
|
5
5
|
import type { Class } from '../class/authoring.js';
|
|
6
6
|
import type { ValueSchemaInput } from '../class/property.js';
|
|
@@ -43,10 +43,8 @@ export interface CallableInputBase {
|
|
|
43
43
|
readonly description?: string;
|
|
44
44
|
readonly input: ValueSchemaInput;
|
|
45
45
|
readonly output: CallableOutputInput;
|
|
46
|
-
readonly static?: boolean;
|
|
47
|
-
readonly inheritance?: MethodInheritance;
|
|
48
46
|
}
|
|
49
|
-
|
|
47
|
+
type ConcreteCallableInput = CallableInputBase & ({
|
|
50
48
|
readonly auth: 'anonymous';
|
|
51
49
|
readonly policy?: never;
|
|
52
50
|
} | {
|
|
@@ -56,18 +54,27 @@ export type CallableInput = CallableInputBase & ({
|
|
|
56
54
|
readonly auth: 'authorized';
|
|
57
55
|
readonly policy?: CallablePolicyBuilder;
|
|
58
56
|
});
|
|
59
|
-
export type FunctionInput =
|
|
57
|
+
export type FunctionInput = ConcreteCallableInput & {
|
|
60
58
|
readonly static?: never;
|
|
61
|
-
readonly
|
|
59
|
+
readonly abstract?: never;
|
|
62
60
|
};
|
|
63
|
-
export type MethodInput =
|
|
64
|
-
|
|
61
|
+
export type MethodInput = (ConcreteCallableInput & {
|
|
62
|
+
readonly static?: boolean;
|
|
63
|
+
readonly abstract?: false;
|
|
64
|
+
}) | (CallableInputBase & {
|
|
65
|
+
readonly static?: boolean;
|
|
66
|
+
readonly abstract: true;
|
|
67
|
+
readonly auth: FunctionAuth;
|
|
68
|
+
readonly policy?: never;
|
|
69
|
+
});
|
|
70
|
+
export type CallableInput = FunctionInput | MethodInput;
|
|
71
|
+
export interface CallableProjection<out Input = unknown, out Output = unknown, out Auth extends FunctionAuth = FunctionAuth, out Static extends boolean = boolean, out Abstract extends boolean = boolean> {
|
|
65
72
|
readonly [CALLABLE_PROJECTION_VALUE]: true;
|
|
66
73
|
readonly input: Input;
|
|
67
74
|
readonly output: Output;
|
|
68
75
|
readonly auth: Auth;
|
|
69
76
|
readonly static: Static;
|
|
70
|
-
readonly
|
|
77
|
+
readonly abstract: Abstract;
|
|
71
78
|
}
|
|
72
79
|
type ValueInputOf<Schema> = Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.input<Schema> : PortableValue;
|
|
73
80
|
type ValueOutputOf<Schema> = Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.output<Schema> : PortableValue;
|
|
@@ -93,12 +100,12 @@ export type ProjectionOfCallable<Value extends Function | Method> = Value extend
|
|
|
93
100
|
export declare function callableConfiguration(value: Function | Method): CallableInput;
|
|
94
101
|
export type PolicyObjectAuthoring = Class | Function | Method | Policy | View | CoreNode;
|
|
95
102
|
export type PolicyObjectInput = Lazy<PolicyObjectAuthoring | PolicyObjectRef>;
|
|
96
|
-
export declare function func<const Config extends FunctionInput, Input = ValueInputOf<Config['input']>, Output = CallableOutputOf<Config['output']>, Auth extends FunctionAuth = Config['auth']>(config: Config): Function<CallableProjection<Input, Output, Auth, false,
|
|
103
|
+
export declare function func<const Config extends FunctionInput, Input = ValueInputOf<Config['input']>, Output = CallableOutputOf<Config['output']>, Auth extends FunctionAuth = Config['auth']>(config: Config): Function<CallableProjection<Input, Output, Auth, false, false>>;
|
|
97
104
|
export declare function method<const Config extends MethodInput, Input = ValueInputOf<Config['input']>, Output = CallableOutputOf<Config['output']>, Auth extends FunctionAuth = Config['auth'], Static extends boolean = Config extends {
|
|
98
105
|
readonly static: true;
|
|
99
|
-
} ? true : false,
|
|
100
|
-
readonly
|
|
101
|
-
} ? Value :
|
|
106
|
+
} ? true : false, Abstract extends boolean = Config extends {
|
|
107
|
+
readonly abstract: infer Value extends boolean;
|
|
108
|
+
} ? Value : false>(config: Config): Method<CallableProjection<Input, Output, Auth, Static, Abstract>>;
|
|
102
109
|
export declare function callableInputField(name: string): Extract<PolicyCheckObject, {
|
|
103
110
|
readonly kind: 'input';
|
|
104
111
|
}>;
|
|
@@ -16,7 +16,7 @@ export function func(config) {
|
|
|
16
16
|
'auth',
|
|
17
17
|
'policy',
|
|
18
18
|
'static',
|
|
19
|
-
'
|
|
19
|
+
'abstract',
|
|
20
20
|
]);
|
|
21
21
|
const value = authoringValue('function', {});
|
|
22
22
|
callableInputs.set(value, { ...config });
|
|
@@ -30,7 +30,7 @@ export function method(config) {
|
|
|
30
30
|
'auth',
|
|
31
31
|
'policy',
|
|
32
32
|
'static',
|
|
33
|
-
'
|
|
33
|
+
'abstract',
|
|
34
34
|
]);
|
|
35
35
|
const value = authoringValue('method', {});
|
|
36
36
|
callableInputs.set(value, { ...config });
|
|
@@ -139,16 +139,32 @@ type OwnMethodProjectionsOf<Input, Static extends boolean> = {
|
|
|
139
139
|
[Name in keyof MethodFieldsOf<Input>]: MethodFieldsOf<Input>[Name] extends infer Value extends Method ? MethodIsStatic<Value> extends Static ? AuthoredMethodProjection<Value, Extract<Name, string>> : never : never;
|
|
140
140
|
}[keyof MethodFieldsOf<Input>];
|
|
141
141
|
type BindMethodOwners<Entries, Owner> = Entries extends AuthoredMethodProjection<infer Value, infer Name, infer ExistingOwner> ? AuthoredMethodProjection<Value, Name, ExistingOwner extends 'self' ? Owner : ExistingOwner> : Entries;
|
|
142
|
-
type
|
|
143
|
-
readonly
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
type AbstractMethodEntries<Entry> = Entry extends AuthoredMethodProjection<infer Value> ? ProjectionOfCallable<Value> extends {
|
|
143
|
+
readonly abstract: true;
|
|
144
|
+
} ? Entry : never : Entry extends {
|
|
145
|
+
readonly abstract: true;
|
|
146
|
+
} ? Entry : never;
|
|
147
|
+
type AbstractMethodsOfReference<Value, Static extends boolean, Seen = never> = Value extends Seen ? never : ResolveCompactLazy<Value> extends infer Resolved ? Resolved extends NodeClass<infer Config> ? Config extends {
|
|
148
|
+
readonly abstractInstanceMethods: infer InstanceMethods;
|
|
149
|
+
readonly abstractStaticMethods: infer StaticMethods;
|
|
150
|
+
} ? BindMethodOwners<Static extends true ? StaticMethods : InstanceMethods, ClassOwnerProjectionOf<Resolved>> : never : Resolved extends ResolvedClass<'node'> ? ResolvedAbstractMethods<Resolved, Static, Seen | Value> : never : never;
|
|
151
|
+
type ResolvedOwnAbstractMethods<Value extends ResolvedClass<'node'>, Static extends boolean> = AbstractMethodEntries<Static extends true ? Value['self']['static']['methods']['byKey'][keyof Value['self']['static']['methods']['byKey']] : Value['self']['methods']['byKey'][keyof Value['self']['methods']['byKey']]>;
|
|
152
|
+
type ParentTupleMembers<Parents> = Parents extends readonly [infer Parent, ...infer Rest] ? Parent | ParentTupleMembers<Rest> : never;
|
|
153
|
+
type ResolvedParents<Value> = Value extends {
|
|
154
|
+
readonly extends: infer Parents;
|
|
155
|
+
} ? ParentTupleMembers<Parents> : never;
|
|
156
|
+
type ResolvedAbstractMethods<Value extends ResolvedClass<'node'>, Static extends boolean, Seen> = ResolvedOwnAbstractMethods<Value, Static> | WithoutMethodNames<AbstractMethodsOfReference<ResolvedParents<Value>, Static, Seen>, MethodNames<ResolvedOwnAbstractMethods<Value, Static>>>;
|
|
146
157
|
type ParentMethodProjectionsOf<Input, Static extends boolean> = Input extends {
|
|
147
158
|
readonly extends?: readonly (infer Parent)[];
|
|
148
|
-
} ?
|
|
149
|
-
type
|
|
159
|
+
} ? AbstractMethodsOfReference<Parent, Static> : never;
|
|
160
|
+
type MethodNames<Entry> = Entry extends {
|
|
150
161
|
readonly name: infer Name;
|
|
151
|
-
} ? Name
|
|
162
|
+
} ? Name : never;
|
|
163
|
+
type WithoutMethodNames<Entry, Names> = Entry extends {
|
|
164
|
+
readonly name: infer Name;
|
|
165
|
+
} ? Name extends Names ? never : Entry : never;
|
|
166
|
+
type AbstractMethodProjectionsOf<Input, Static extends boolean> = AbstractMethodEntries<OwnMethodProjectionsOf<Input, Static>> | WithoutMethodNames<ParentMethodProjectionsOf<Input, Static>, AbstractMethodEntries<OwnMethodProjectionsOf<Input, Static>>['name']>;
|
|
167
|
+
type EffectiveMethodProjectionsOf<Input, Static extends boolean> = OwnMethodProjectionsOf<Input, Static> | WithoutMethodNames<ParentMethodProjectionsOf<Input, Static>, keyof MethodFieldsOf<Input>>;
|
|
152
168
|
type ClassDepthOf<Value> = ResolveCompactLazy<Value> extends infer Resolved ? Resolved extends NodeClass<infer Config> ? Config extends {
|
|
153
169
|
readonly depth: infer Depth extends number;
|
|
154
170
|
} ? Depth : 0 : Resolved extends DirectedEdgeClass<infer Config> | UndirectedEdgeClass<infer Config> ? Config extends {
|
|
@@ -222,13 +238,13 @@ type OwnerErasedPropertyProjection<Entry> = Entry extends AuthoredPropertyProjec
|
|
|
222
238
|
readonly key: Key;
|
|
223
239
|
readonly state?: State;
|
|
224
240
|
} : never;
|
|
225
|
-
type OwnerErasedMethodProjection<Entry> = Entry extends AuthoredMethodProjection<infer Value, infer Name, unknown> ? AuthoredMethodProjection<Value, Name, unknown> : Entry extends ResolvedMethod<infer Input, infer Output, infer Static, infer
|
|
241
|
+
type OwnerErasedMethodProjection<Entry> = Entry extends AuthoredMethodProjection<infer Value, infer Name, unknown> ? AuthoredMethodProjection<Value, Name, unknown> : Entry extends ResolvedMethod<infer Input, infer Output, infer Static, infer Abstract, infer Auth, infer _Policy, infer Key> & {
|
|
226
242
|
readonly name: infer Name;
|
|
227
243
|
} ? {
|
|
228
244
|
readonly input: Input;
|
|
229
245
|
readonly output: Output;
|
|
230
246
|
readonly static: Static;
|
|
231
|
-
readonly
|
|
247
|
+
readonly abstract: Abstract;
|
|
232
248
|
readonly auth: Auth;
|
|
233
249
|
readonly name: Name;
|
|
234
250
|
readonly key: Key;
|
|
@@ -274,6 +290,8 @@ type CompactNodeInput<Input extends NodeClassInput> = {
|
|
|
274
290
|
readonly methods?: infer Value;
|
|
275
291
|
} ? NonNullable<Value> : Readonly<Record<never, never>> : Readonly<Record<never, never>>;
|
|
276
292
|
readonly effectiveProperties: EffectivePropertyProjectionsOf<Input>;
|
|
293
|
+
readonly abstractInstanceMethods: AbstractMethodProjectionsOf<Input, false>;
|
|
294
|
+
readonly abstractStaticMethods: AbstractMethodProjectionsOf<Input, true>;
|
|
277
295
|
readonly effectiveInstanceMethods: EffectiveMethodProjectionsOf<Input, false>;
|
|
278
296
|
readonly effectiveStaticMethods: EffectiveMethodProjectionsOf<Input, true>;
|
|
279
297
|
};
|
|
@@ -20,12 +20,12 @@ export class V1CallableCompiler {
|
|
|
20
20
|
fail('BUILDER_CALLABLE_POLICY_AUTH', `${placement.pointer}/policy`, 'Only an authorized Function or Method may declare a Policy check.');
|
|
21
21
|
}
|
|
22
22
|
const method = placement.kind !== 'function';
|
|
23
|
-
if (!method && (config.static !== undefined || config.
|
|
24
|
-
fail('BUILDER_CALLABLE_PLACEMENT', placement.pointer, 'A standalone Function cannot declare Method staticness or
|
|
23
|
+
if (!method && (config.static !== undefined || config.abstract !== undefined)) {
|
|
24
|
+
fail('BUILDER_CALLABLE_PLACEMENT', placement.pointer, 'A standalone Function cannot declare Method staticness or abstractness.');
|
|
25
25
|
}
|
|
26
26
|
const isStatic = method ? (config.static ?? false) : undefined;
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
27
|
+
const isAbstract = method ? (config.abstract ?? false) : undefined;
|
|
28
|
+
if (isAbstract === true && config.policy !== undefined) {
|
|
29
29
|
fail('BUILDER_ABSTRACT_POLICY', `${placement.pointer}/policy`, 'An abstract Method cannot declare a Policy check.');
|
|
30
30
|
}
|
|
31
31
|
const input = this.values.schema(config.input, 'callable-input', `${placement.pointer}/input`);
|
|
@@ -46,7 +46,7 @@ export class V1CallableCompiler {
|
|
|
46
46
|
return {
|
|
47
47
|
...contract,
|
|
48
48
|
static: isStatic,
|
|
49
|
-
|
|
49
|
+
abstract: isAbstract,
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
compileOutput(value, pointer) {
|
|
@@ -260,7 +260,7 @@ function hasPolicyObject(schema, ref) {
|
|
|
260
260
|
case 'method': {
|
|
261
261
|
const owner = schema.classes[ref.owner.name];
|
|
262
262
|
const method = owner?.kind === 'node' ? owner.methods[ref.name] : undefined;
|
|
263
|
-
return method !== undefined && method.
|
|
263
|
+
return method !== undefined && !method.abstract;
|
|
264
264
|
}
|
|
265
265
|
case 'class':
|
|
266
266
|
return Object.hasOwn(schema.classes, ref.name);
|
|
@@ -13,11 +13,11 @@ export interface PropertyProjection<Value = unknown, Required extends boolean =
|
|
|
13
13
|
readonly key: Key;
|
|
14
14
|
readonly state?: State;
|
|
15
15
|
}
|
|
16
|
-
export interface MethodProjection<Input = unknown, Output = unknown, Static extends boolean = boolean,
|
|
16
|
+
export interface MethodProjection<Input = unknown, Output = unknown, Static extends boolean = boolean, Abstract extends boolean = boolean, Auth extends language.FunctionAuth = language.FunctionAuth, Name extends string = string, Key extends language.MethodKey = language.MethodKey> {
|
|
17
17
|
readonly input: Input;
|
|
18
18
|
readonly output: Output;
|
|
19
19
|
readonly static: Static;
|
|
20
|
-
readonly
|
|
20
|
+
readonly abstract: Abstract;
|
|
21
21
|
readonly auth: Auth;
|
|
22
22
|
readonly name: Name;
|
|
23
23
|
readonly key: Key;
|
|
@@ -251,20 +251,20 @@ type CallableOutputValue<Value extends Method | Function> = CallableConfig<Value
|
|
|
251
251
|
type CallableStaticOf<Value extends Method> = CallableConfig<Value> extends {
|
|
252
252
|
readonly static: true;
|
|
253
253
|
} ? true : false;
|
|
254
|
-
type
|
|
255
|
-
readonly
|
|
256
|
-
} ?
|
|
254
|
+
type CallableAbstractOf<Value extends Method> = CallableConfig<Value> extends {
|
|
255
|
+
readonly abstract: infer Abstract extends boolean;
|
|
256
|
+
} ? Abstract : false;
|
|
257
257
|
type CallableAuthOf<Value extends Method | Function> = CallableConfig<Value>['auth'];
|
|
258
|
-
type MethodProjectionOfEntry<Origin extends string, OwnerName extends string, Entry> = Entry extends language.ResolvedMethod ? MethodProjectionOfResolved<Entry> : Entry extends AuthoredMethodEntry<unknown, infer Name, infer Value> ? MethodProjection<CallableInputValue<Value>, CallableOutputValue<Value>, CallableStaticOf<Value>,
|
|
258
|
+
type MethodProjectionOfEntry<Origin extends string, OwnerName extends string, Entry> = Entry extends language.ResolvedMethod ? MethodProjectionOfResolved<Entry> : Entry extends AuthoredMethodEntry<unknown, infer Name, infer Value> ? MethodProjection<CallableInputValue<Value>, CallableOutputValue<Value>, CallableStaticOf<Value>, CallableAbstractOf<Value>, CallableAuthOf<Value>, Name, language.MethodKey<Origin, OwnerName, Name>> : never;
|
|
259
259
|
type MethodProjectionOfEntries<Origin extends string, OwnerName extends string, Entries> = Entries extends unknown ? MethodProjectionOfEntry<Origin, OwnerName, Entries> : never;
|
|
260
260
|
type MethodCollectionOperation = 'byKey' | 'get';
|
|
261
261
|
type PropertyProjectionOfResolved<Value> = Value extends language.ResolvedProperty<infer PropertyValue, infer Required, infer State> & {
|
|
262
262
|
readonly name: infer Name extends string;
|
|
263
263
|
readonly key: infer Key extends language.PropertyKey;
|
|
264
264
|
} ? PropertyProjection<PropertyValue, Required, Name, Key, State> : never;
|
|
265
|
-
type MethodProjectionOfResolved<Value> = Value extends language.ResolvedMethod<infer Input, infer Output, infer Static, infer
|
|
265
|
+
type MethodProjectionOfResolved<Value> = Value extends language.ResolvedMethod<infer Input, infer Output, infer Static, infer Abstract, infer Auth, infer _Policy, infer Key> & {
|
|
266
266
|
readonly name: infer Name extends string;
|
|
267
|
-
} ? MethodProjection<Input, Output, Static,
|
|
267
|
+
} ? MethodProjection<Input, Output, Static, Abstract, Auth, Name, Key> : never;
|
|
268
268
|
type PropertiesOfCollection<Value> = Value extends {
|
|
269
269
|
readonly byKey: infer ByKey extends Readonly<Record<language.PropertyKey, language.ResolvedProperty<unknown, boolean>>>;
|
|
270
270
|
} ? PropertyProjectionOfResolved<ByKey[keyof ByKey]> : never;
|
|
@@ -303,7 +303,7 @@ type EffectiveMethodEntriesOfClass<Value, Static extends boolean> = ClassInputOf
|
|
|
303
303
|
readonly effectiveInstanceMethods: infer InstanceMethods;
|
|
304
304
|
readonly effectiveStaticMethods: infer StaticMethods;
|
|
305
305
|
} ? Static extends true ? StaticMethods : InstanceMethods : never;
|
|
306
|
-
type EffectiveMethodProjectionOfEntry<Origin extends string, CurrentName extends string, Root extends DomainSchemaInput, Entry> = Entry extends language.ResolvedMethod ? MethodProjectionOfResolved<Entry> : Entry extends AuthoredMethodProjection<infer Value, infer MethodName, infer Owner> ? OwnerNameOf<Root, CurrentName, Owner> extends infer OwnerName extends string ? MethodProjection<CallableInputValue<Value>, CallableOutputValue<Value>, CallableStaticOf<Value>,
|
|
306
|
+
type EffectiveMethodProjectionOfEntry<Origin extends string, CurrentName extends string, Root extends DomainSchemaInput, Entry> = Entry extends language.ResolvedMethod ? MethodProjectionOfResolved<Entry> : Entry extends AuthoredMethodProjection<infer Value, infer MethodName, infer Owner> ? OwnerNameOf<Root, CurrentName, Owner> extends infer OwnerName extends string ? MethodProjection<CallableInputValue<Value>, CallableOutputValue<Value>, CallableStaticOf<Value>, CallableAbstractOf<Value>, CallableAuthOf<Value>, MethodName, language.MethodKey<Origin, OwnerName, MethodName>> : never : never;
|
|
307
307
|
type EffectiveMethodProjectionsOf<Origin extends string, Name extends string, Value, Root extends DomainSchemaInput, Static extends boolean> = EffectiveMethodEntriesOfClass<Value, Static> extends infer Entry ? Entry extends unknown ? EffectiveMethodProjectionOfEntry<Origin, Name, Root, Entry> : never : never;
|
|
308
308
|
type ShallowClassProjectionOfAuthoring<Origin extends string, Name extends string, Value, Root extends DomainSchemaInput> = Value extends NodeClass<infer _Config> ? ClassProjection<'node', Extract<ProjectedClassDefinition<Origin, Name, Value>, language.NodeClassDefinition>, language.ClassRef<Name> & {
|
|
309
309
|
readonly origin: Origin;
|
|
@@ -335,7 +335,7 @@ type ProjectionPropertyByName<Entries extends PropertyProjection> = Readonly<{
|
|
|
335
335
|
}>>;
|
|
336
336
|
}>;
|
|
337
337
|
type PropertyCollectionOfProjection<Entries extends PropertyProjection> = language.ResolvedPropertyCollection<ProjectionPropertyByKey<Entries>, ProjectionPropertyByName<Entries>>;
|
|
338
|
-
type ResolvedMethodOfProjection<Value> = Value extends MethodProjection<infer Input, infer Output, infer Static, infer
|
|
338
|
+
type ResolvedMethodOfProjection<Value> = Value extends MethodProjection<infer Input, infer Output, infer Static, infer Abstract, infer Auth, infer Name, infer Key> ? language.ResolvedMethod<Input, Output, Static, Abstract, Auth, language.PolicyCheckExpression, Key> & {
|
|
339
339
|
readonly name: Name;
|
|
340
340
|
} : never;
|
|
341
341
|
type ProjectionMethodByKey<Entries extends MethodProjection> = Readonly<{
|
|
@@ -237,23 +237,19 @@ function compiledProperty(input, values, node, path) {
|
|
|
237
237
|
function compiledCallable(input, values, method, path) {
|
|
238
238
|
const value = object(input, 'Compiled Callable', path);
|
|
239
239
|
exact(value, method
|
|
240
|
-
? ['name', 'auth', 'static', '
|
|
240
|
+
? ['name', 'auth', 'static', 'abstract', 'input', 'output']
|
|
241
241
|
: ['ref', 'auth', 'input', 'output'], 'Compiled Callable', path, ['policy', 'description', 'fields']);
|
|
242
242
|
if (method) {
|
|
243
243
|
memberName(value.name, 'Method name', `${path}/name`);
|
|
244
244
|
boolean(value.static, 'Method static', `${path}/static`);
|
|
245
|
-
|
|
246
|
-
value.inheritance !== 'default' &&
|
|
247
|
-
value.inheritance !== 'sealed') {
|
|
248
|
-
invalid('Method inheritance', undefined, `${path}/inheritance`);
|
|
249
|
-
}
|
|
245
|
+
boolean(value.abstract, 'Method abstract', `${path}/abstract`);
|
|
250
246
|
}
|
|
251
247
|
if (value.auth !== 'anonymous' && value.auth !== 'authenticated' && value.auth !== 'authorized') {
|
|
252
248
|
invalid('Callable auth', undefined, `${path}/auth`);
|
|
253
249
|
}
|
|
254
250
|
description(value.description, 'Callable description', `${path}/description`);
|
|
255
251
|
if (value.policy !== undefined) {
|
|
256
|
-
if (value.auth !== 'authorized' || (method && value.
|
|
252
|
+
if (value.auth !== 'authorized' || (method && value.abstract === true)) {
|
|
257
253
|
invalid('Callable Policy placement', undefined, `${path}/policy`);
|
|
258
254
|
}
|
|
259
255
|
policyCheck(value.policy, 0, `${path}/policy`);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { Key, PropertyKey as PropertyKeyValue } from '../../addressing/index.js';
|
|
3
3
|
import { compareUnicode } from '../../addressing/ordering.js';
|
|
4
|
+
import { composeMethods } from '../../language/callable/composition.js';
|
|
4
5
|
import { policyObjectOrigin, policyVariableClass } from '../../language/index.js';
|
|
5
6
|
import { branchesOf, branchProduct, capBranches, isConnected, targetMode, termKey, } from '../../language/policy/analysis/branches.js';
|
|
6
7
|
import { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks, } from '../../language/policy/analysis/budgets.js';
|
|
7
|
-
import {
|
|
8
|
+
import { normalizePolicyExpression } from '../../language/policy/normalization/normalize.js';
|
|
8
9
|
import { validateValueProgram } from '../../language/value/evaluate/program.js';
|
|
9
10
|
import { GRAPH_NODE_PATH_SCHEMA_ID } from '../../language/value/syntax/node-path/schema.js';
|
|
10
11
|
import { invalidCompiled } from '../diagnostic.js';
|
|
@@ -104,55 +105,22 @@ function analyzeClasses(classes) {
|
|
|
104
105
|
fail(`${located.pointer}/data`, 'Data cannot redeclare an inherited contract.');
|
|
105
106
|
dataOwner = located.value.ref;
|
|
106
107
|
}
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
for (const parent of inheritedMethods) {
|
|
124
|
-
if (parent.declaration.inheritance === 'sealed') {
|
|
125
|
-
fail(`${located.pointer}/methods`, `Method ${own.name} overrides a sealed Method.`);
|
|
126
|
-
}
|
|
127
|
-
if (!sameMethodSignature(located.entry, own, parent.entry, parent.declaration)) {
|
|
128
|
-
fail(`${located.pointer}/methods`, `Method ${own.name} changes its inherited signature.`);
|
|
129
|
-
}
|
|
130
|
-
if (parent.declaration.inheritance !== 'abstract' &&
|
|
131
|
-
policyKey(parent.declaration.policy) !== policyKey(own.policy)) {
|
|
132
|
-
fail(`${located.pointer}/methods`, `Method ${own.name} changes an executable inherited Policy.`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
methods.set(own.name, {
|
|
136
|
-
key: `${Key.of(located.value.ref)}.method.${own.name}`,
|
|
137
|
-
owner: located.value.ref,
|
|
138
|
-
declaration: own,
|
|
139
|
-
entry: located.entry,
|
|
140
|
-
});
|
|
141
|
-
inherited.delete(own.name);
|
|
142
|
-
}
|
|
143
|
-
for (const [name, values] of inherited) {
|
|
144
|
-
if (values.length > 1)
|
|
145
|
-
fail(`${located.pointer}/methods`, `Inherited Method ${name} is ambiguous.`);
|
|
146
|
-
}
|
|
147
|
-
if (!located.value.abstract) {
|
|
148
|
-
for (const [name, method] of methods) {
|
|
149
|
-
if (method.declaration.inheritance === 'abstract') {
|
|
150
|
-
fail(`${located.pointer}/methods`, `Concrete Class leaves Method ${name} abstract.`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
const analysis = { properties, methods, ...(dataOwner === undefined ? {} : { dataOwner }) };
|
|
108
|
+
const own = new Map((located.value.kind === 'node' ? located.value.methods : []).map((method) => [
|
|
109
|
+
method.name,
|
|
110
|
+
{
|
|
111
|
+
key: `${classKey}.method.${method.name}`,
|
|
112
|
+
name: method.name,
|
|
113
|
+
owner: located.value.ref,
|
|
114
|
+
declaration: method,
|
|
115
|
+
entry: located.entry,
|
|
116
|
+
},
|
|
117
|
+
]));
|
|
118
|
+
const composition = composeMethods(parents, own, located.value.abstract, (left, right) => sameMethodSignature(left.entry, left.declaration, right.entry, right.declaration), (message) => fail(`${located.pointer}/methods`, message));
|
|
119
|
+
const analysis = {
|
|
120
|
+
properties,
|
|
121
|
+
...composition,
|
|
122
|
+
...(dataOwner === undefined ? {} : { dataOwner }),
|
|
123
|
+
};
|
|
156
124
|
analyses.set(classKey, analysis);
|
|
157
125
|
visiting.delete(classKey);
|
|
158
126
|
return analysis;
|
|
@@ -173,9 +141,6 @@ function outputKey(entry, output) {
|
|
|
173
141
|
function programKey(entry, value) {
|
|
174
142
|
return json.serialize(entry.values[value]);
|
|
175
143
|
}
|
|
176
|
-
function policyKey(value) {
|
|
177
|
-
return json.serialize((value === undefined ? null : normalizePolicyChecks(value)));
|
|
178
|
-
}
|
|
179
144
|
function validateClassFacets(context) {
|
|
180
145
|
for (const located of context.classes.values()) {
|
|
181
146
|
if (located.value.abstract &&
|
|
@@ -195,7 +160,7 @@ function validateClassFacets(context) {
|
|
|
195
160
|
}
|
|
196
161
|
if (located.value.kind === 'node') {
|
|
197
162
|
for (const [index, method] of located.value.methods.entries()) {
|
|
198
|
-
if (method.
|
|
163
|
+
if (method.abstract === true && method.policy !== undefined) {
|
|
199
164
|
fail(`${located.pointer}/methods/${index}/policy`, 'An abstract Method cannot have a Policy.');
|
|
200
165
|
}
|
|
201
166
|
}
|
|
@@ -398,7 +363,7 @@ function validatePolicyTerm(context, term, path, scope) {
|
|
|
398
363
|
function validatePolicyMethod(context, ref, path) {
|
|
399
364
|
const owner = context.classes.get(Key.of(ref.owner))?.value;
|
|
400
365
|
const method = owner?.kind === 'node' ? owner.methods.find((method) => method.name === ref.name) : undefined;
|
|
401
|
-
if (method === undefined || method.
|
|
366
|
+
if (method === undefined || method.abstract)
|
|
402
367
|
fail(path, 'Policy Method ref must name an exact concrete Method declaration.');
|
|
403
368
|
}
|
|
404
369
|
function validatePolicyCycles(entry, local, entryIndex) {
|
|
@@ -70,7 +70,7 @@ function compileEntry(schema, lookup) {
|
|
|
70
70
|
...(method.policy === undefined ? {} : { policy: method.policy }),
|
|
71
71
|
...(method.description === undefined ? {} : { description: method.description }),
|
|
72
72
|
static: method.static,
|
|
73
|
-
|
|
73
|
+
abstract: method.abstract,
|
|
74
74
|
input,
|
|
75
75
|
...(fields === undefined ? {} : { fields }),
|
|
76
76
|
output: output(method.output, `${pointer}/methods/${name}/output`),
|
|
@@ -8,6 +8,7 @@ export function createClasses(definitions, validation) {
|
|
|
8
8
|
.map((value) => [Key.of(value.ref), value]));
|
|
9
9
|
const resolved = new Map();
|
|
10
10
|
const visiting = new Set();
|
|
11
|
+
const frontiers = new Map();
|
|
11
12
|
const resolve = (ref) => {
|
|
12
13
|
const key = Key.of(ref);
|
|
13
14
|
const cached = resolved.get(key);
|
|
@@ -25,19 +26,18 @@ export function createClasses(definitions, validation) {
|
|
|
25
26
|
}
|
|
26
27
|
const shell = {};
|
|
27
28
|
const effectiveProperties = new Map();
|
|
28
|
-
const
|
|
29
|
+
const frontier = new Map();
|
|
29
30
|
let inheritedData;
|
|
30
31
|
for (const parent of parents) {
|
|
31
32
|
for (const property of parent.properties) {
|
|
32
33
|
effectiveProperties.set(property.key, property);
|
|
33
34
|
}
|
|
34
|
-
for (const method of
|
|
35
|
-
|
|
36
|
-
effectiveMethods.set(method.name, method);
|
|
37
|
-
}
|
|
35
|
+
for (const [name, method] of frontiers.get(parent.key))
|
|
36
|
+
frontier.set(name, method);
|
|
38
37
|
if (parent.kind === 'node' && inheritedData === undefined)
|
|
39
38
|
inheritedData = parent.data;
|
|
40
39
|
}
|
|
40
|
+
const effectiveMethods = new Map(frontier);
|
|
41
41
|
const ownProperties = [];
|
|
42
42
|
for (const [name, propertyDefinition] of Object.entries(definition.properties)) {
|
|
43
43
|
const propertyKey = PropertyKeyValue.of(ref, name);
|
|
@@ -72,13 +72,15 @@ export function createClasses(definitions, validation) {
|
|
|
72
72
|
auth: methodDefinition.auth,
|
|
73
73
|
policy: methodDefinition.policy,
|
|
74
74
|
static: methodDefinition.static,
|
|
75
|
-
|
|
75
|
+
abstract: methodDefinition.abstract,
|
|
76
76
|
...(inputFields === undefined ? {} : { inputFields }),
|
|
77
77
|
definition: methodDefinition,
|
|
78
78
|
validateInput: (input) => validation.callableInput(methodKey, input),
|
|
79
79
|
validateInputField: (field, input) => validation.callableInputField(methodKey, field, input),
|
|
80
80
|
validateOutput: (output) => validation.callableOutput(methodKey, output),
|
|
81
81
|
});
|
|
82
|
+
if (method.abstract)
|
|
83
|
+
frontier.set(name, method);
|
|
82
84
|
effectiveMethods.set(name, method);
|
|
83
85
|
(methodDefinition.static ? ownStaticMethods : ownInstanceMethods).push(method);
|
|
84
86
|
}
|
|
@@ -119,6 +121,7 @@ export function createClasses(definitions, validation) {
|
|
|
119
121
|
},
|
|
120
122
|
});
|
|
121
123
|
const value = Object.freeze(shell);
|
|
124
|
+
frontiers.set(key, frontier);
|
|
122
125
|
resolved.set(key, value);
|
|
123
126
|
visiting.delete(key);
|
|
124
127
|
return value;
|
package/dist/v1/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { stateMachine } from './language/state/index.js';
|
|
|
4
4
|
export type { EventOf, StateMachine, StateOf } from './language/state/index.js';
|
|
5
5
|
export { PropertyValues } from './language/index.js';
|
|
6
6
|
export * as metadata from './language/definition/metadata.js';
|
|
7
|
-
export type { AnyResolvedClass, BoundMethodOf, BoundMethodShortcutsOf, CallableResultOf, CallableInputOf, CallableOutputOf, ClassSatisfactionKeysOf, ClassDefinition, CoreDefinition, CoreEdge, FunctionAuth, FunctionDefinition, MethodDefinition,
|
|
7
|
+
export type { AnyResolvedClass, BoundMethodOf, BoundMethodShortcutsOf, CallableResultOf, CallableInputOf, CallableOutputOf, ClassSatisfactionKeysOf, ClassDefinition, CoreDefinition, CoreEdge, FunctionAuth, FunctionDefinition, MethodDefinition, MethodOwnerKeyOf, PolicyCheckExpression, PolicyDefinition, PropertyInputOf, PropertyAccessOf, PropertyOutputOf, PropertyShortcutsOf, PropertyValuesOf, ResolvedClass, ResolvedCoreDefinition, ResolvedData, ResolvedFunction, ResolvedMethod, ResolvedMethodCollection, ResolvedPolicy, ResolvedProperty, ResolvedView, MethodAccessOf, SelfMethodsOf, SelfPropertiesOf, SelfStaticMethodsOf, StaticMethodsOf, ViewDefinition, } from './language/index.js';
|
|
8
8
|
export { acceptDefinitionRef, callableAddress, ClassKey, equalDefinitionRefs, isDefinitionRef, Key, MethodKey, PropertyKey, } from './addressing/index.js';
|
|
9
9
|
export type { ClassRef, CoreRef, DefinitionBase, DefinitionKey, DefinitionRef, FunctionRef, MethodAddress, PolicyRef, Revision, ViewRef, } from './addressing/index.js';
|
|
10
10
|
export type { Definition } from './language/index.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface MethodMember {
|
|
2
|
+
readonly key: string;
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly declaration: {
|
|
5
|
+
readonly abstract: boolean;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export interface MethodComposition<Method> {
|
|
9
|
+
readonly requirements: ReadonlyMap<string, Method>;
|
|
10
|
+
readonly frontier: ReadonlyMap<string, Method>;
|
|
11
|
+
readonly methods: ReadonlyMap<string, Method>;
|
|
12
|
+
}
|
|
13
|
+
/** Concrete implementations never discharge a descendant's abstract obligations. */
|
|
14
|
+
export declare function composeMethods<Method extends MethodMember>(parents: readonly MethodComposition<Method>[], own: ReadonlyMap<string, Method>, abstract: boolean, sameSignature: (left: Method, right: Method) => boolean, report: (message: string) => void): MethodComposition<Method>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Concrete implementations never discharge a descendant's abstract obligations. */
|
|
2
|
+
export function composeMethods(parents, own, abstract, sameSignature, report) {
|
|
3
|
+
const requirements = new Map(parents.flatMap((parent) => [...parent.requirements]));
|
|
4
|
+
const frontier = new Map(parents.flatMap((parent) => [...parent.frontier]));
|
|
5
|
+
for (const required of requirements.values()) {
|
|
6
|
+
const local = own.get(required.name);
|
|
7
|
+
if (local !== undefined && !sameSignature(local, required)) {
|
|
8
|
+
report(`Method ${local.key} does not exactly preserve abstract signature ${required.key}.`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
for (const local of own.values()) {
|
|
12
|
+
if (!local.declaration.abstract)
|
|
13
|
+
continue;
|
|
14
|
+
requirements.set(local.key, local);
|
|
15
|
+
for (const [key, inherited] of frontier) {
|
|
16
|
+
if (inherited.name === local.name)
|
|
17
|
+
frontier.delete(key);
|
|
18
|
+
}
|
|
19
|
+
frontier.set(local.key, local);
|
|
20
|
+
}
|
|
21
|
+
const methods = new Map();
|
|
22
|
+
for (const inherited of frontier.values()) {
|
|
23
|
+
const prior = methods.get(inherited.name);
|
|
24
|
+
if (prior !== undefined && !own.has(inherited.name)) {
|
|
25
|
+
report(`Inherited Method ${inherited.name} is ambiguous: ${prior.key}, ${inherited.key}.`);
|
|
26
|
+
}
|
|
27
|
+
methods.set(inherited.name, inherited);
|
|
28
|
+
}
|
|
29
|
+
for (const [name, local] of own)
|
|
30
|
+
methods.set(name, local);
|
|
31
|
+
if (!abstract) {
|
|
32
|
+
const missing = new Set();
|
|
33
|
+
for (const required of requirements.values()) {
|
|
34
|
+
const local = own.get(required.name);
|
|
35
|
+
if ((local === undefined || local.declaration.abstract) && !missing.has(required.name)) {
|
|
36
|
+
missing.add(required.name);
|
|
37
|
+
report(`Concrete Class requires a local concrete implementation of ${required.key}.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { requirements, frontier, methods };
|
|
42
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { DefinitionBase, FunctionRef } from '../../../addressing/index.js';
|
|
2
2
|
import type { PolicyCheckExpression } from '../../policy/model/policy.js';
|
|
3
3
|
import type { JsonSchema } from '../../value/model/schema.js';
|
|
4
|
-
export type MethodInheritance = 'abstract' | 'default' | 'sealed';
|
|
5
4
|
export type FunctionAuth = 'anonymous' | 'authenticated' | 'authorized';
|
|
6
5
|
export type CallableOutput = {
|
|
7
6
|
readonly mode: 'value';
|
|
@@ -28,6 +27,6 @@ export type CallableDefinition = CallableDefinitionBase & ({
|
|
|
28
27
|
export type FunctionDefinition = DefinitionBase<FunctionRef> & CallableDefinition;
|
|
29
28
|
export type MethodDefinition = CallableDefinition & {
|
|
30
29
|
readonly static: boolean;
|
|
31
|
-
readonly
|
|
30
|
+
readonly abstract: boolean;
|
|
32
31
|
};
|
|
33
32
|
export type MethodRecord = Readonly<Record<string, MethodDefinition>>;
|
|
@@ -11,7 +11,7 @@ type CallableDefinitionOf<Definition extends FunctionDefinition | MethodDefiniti
|
|
|
11
11
|
} : {
|
|
12
12
|
readonly policy?: never;
|
|
13
13
|
}) : never);
|
|
14
|
-
export interface ResolvedMethod<out Input = unknown, out Output = unknown, out Static extends boolean = boolean, out
|
|
14
|
+
export interface ResolvedMethod<out Input = unknown, out Output = unknown, out Static extends boolean = boolean, out Abstract extends MethodDefinition['abstract'] = MethodDefinition['abstract'], out Auth extends FunctionAuth = FunctionAuth, out Policy extends PolicyCheckExpression = PolicyCheckExpression, out ExactKey extends MethodKey = MethodKey> {
|
|
15
15
|
readonly owner: ResolvedClass<'node'> & {
|
|
16
16
|
readonly key: MethodOwnerKeyOfKey<ExactKey>;
|
|
17
17
|
};
|
|
@@ -21,14 +21,14 @@ export interface ResolvedMethod<out Input = unknown, out Output = unknown, out S
|
|
|
21
21
|
readonly input: MethodDefinition['input'];
|
|
22
22
|
readonly output: MethodDefinition['output'];
|
|
23
23
|
readonly auth: Auth;
|
|
24
|
-
readonly policy?: Auth extends 'authorized' ? Policy : never;
|
|
24
|
+
readonly policy?: Abstract extends true ? never : Auth extends 'authorized' ? Policy : never;
|
|
25
25
|
readonly static: Static;
|
|
26
|
-
readonly
|
|
26
|
+
readonly abstract: Abstract;
|
|
27
27
|
/** Present only when the callable input is a safely enumerable closed object. */
|
|
28
28
|
readonly inputFields?: readonly string[];
|
|
29
|
-
readonly definition: CallableDefinitionOf<MethodDefinition, Auth, Policy> & {
|
|
29
|
+
readonly definition: CallableDefinitionOf<MethodDefinition, Auth, Abstract extends true ? never : Policy> & {
|
|
30
30
|
readonly static: Static;
|
|
31
|
-
readonly
|
|
31
|
+
readonly abstract: Abstract;
|
|
32
32
|
};
|
|
33
33
|
validateInput(input: unknown): readonly Diagnostic[];
|
|
34
34
|
validateInputField(field: string, input: unknown): readonly Diagnostic[];
|
|
@@ -8,6 +8,7 @@ import type { JsonSchema } from '../../language/value/model/schema.js';
|
|
|
8
8
|
import type { SchemaDiagnostic } from '../diagnostic.js';
|
|
9
9
|
import type { DomainResolutionContext } from '../resolution/context.js';
|
|
10
10
|
import { PropertyKey } from '../../addressing/index.js';
|
|
11
|
+
import { type MethodComposition } from '../../language/callable/composition.js';
|
|
11
12
|
export interface EffectiveField {
|
|
12
13
|
readonly identity: PropertyKey;
|
|
13
14
|
readonly owner: ClassRef;
|
|
@@ -18,7 +19,7 @@ export interface EffectiveField {
|
|
|
18
19
|
readonly state?: StateTopology;
|
|
19
20
|
}
|
|
20
21
|
export interface EffectiveMethod {
|
|
21
|
-
readonly
|
|
22
|
+
readonly key: string;
|
|
22
23
|
readonly owner: ClassRef;
|
|
23
24
|
readonly name: string;
|
|
24
25
|
readonly declaration: MethodDefinition;
|
|
@@ -28,12 +29,11 @@ export interface EffectiveData {
|
|
|
28
29
|
readonly owner: ClassRef;
|
|
29
30
|
readonly declaration: DataDefinition;
|
|
30
31
|
}
|
|
31
|
-
export interface DefinitionAnalysis {
|
|
32
|
+
export interface DefinitionAnalysis extends MethodComposition<EffectiveMethod> {
|
|
32
33
|
readonly ref: ClassRef;
|
|
33
34
|
readonly declaration: ClassDefinition;
|
|
34
35
|
readonly properties: ReadonlyMap<PropertyKey, EffectiveField>;
|
|
35
36
|
readonly data?: EffectiveData;
|
|
36
|
-
readonly methods: ReadonlyMap<string, EffectiveMethod>;
|
|
37
37
|
}
|
|
38
38
|
export interface DefinitionAnalysisResult {
|
|
39
39
|
readonly definitions: ReadonlyMap<string, DefinitionAnalysis>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { Key, PropertyKey } from '../../addressing/index.js';
|
|
3
3
|
import { definitionRefIndex } from '../../addressing/ordering.js';
|
|
4
|
-
import {
|
|
4
|
+
import { composeMethods } from '../../language/callable/composition.js';
|
|
5
5
|
import { canonicalizeValueSchema } from '../../language/value/profile/canonicalize.js';
|
|
6
6
|
import { diagnostic } from '../diagnostic.js';
|
|
7
7
|
const formatDefinitionRef = Key.of;
|
|
@@ -41,7 +41,7 @@ export function analyzeDefinitions(environment) {
|
|
|
41
41
|
declaration,
|
|
42
42
|
properties,
|
|
43
43
|
...(data === undefined ? {} : { data }),
|
|
44
|
-
methods,
|
|
44
|
+
...methods,
|
|
45
45
|
};
|
|
46
46
|
result.set(key, analysis);
|
|
47
47
|
visiting.delete(key);
|
|
@@ -107,57 +107,11 @@ function mergeData(owner, declaration, parents, diagnostics) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
function mergeMethods(owner, declaration, parents, diagnostics) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const entries = inherited.get(name) ?? [];
|
|
116
|
-
if (!entries.some((entry) => entry.identity === method.identity))
|
|
117
|
-
entries.push(method);
|
|
118
|
-
inherited.set(name, entries);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
const result = new Map();
|
|
122
|
-
for (const [name, entries] of inherited) {
|
|
123
|
-
if (entries.length === 1)
|
|
124
|
-
result.set(name, entries[0]);
|
|
125
|
-
}
|
|
126
|
-
for (const [name, ownMethod] of Object.entries(declaration.methods)) {
|
|
127
|
-
const parentsForName = inherited.get(name) ?? [];
|
|
128
|
-
for (const parent of parentsForName) {
|
|
129
|
-
if (parent.declaration.inheritance === 'sealed') {
|
|
130
|
-
diagnostics.push(diagnostic('DV_METHOD_COMPOSITION', '', `Method ${name} cannot override sealed ${formatDefinitionRef(parent.owner)}.${name}.`));
|
|
131
|
-
}
|
|
132
|
-
if (!sameMethodSignature(ownMethod, parent.declaration)) {
|
|
133
|
-
diagnostics.push(diagnostic('DV_METHOD_COMPOSITION', '', `Method ${name} does not exactly preserve its inherited signature.`));
|
|
134
|
-
}
|
|
135
|
-
if (parent.declaration.inheritance !== 'abstract' &&
|
|
136
|
-
canonicalPolicy(parent.declaration.policy) !== canonicalPolicy(ownMethod.policy)) {
|
|
137
|
-
diagnostics.push(diagnostic('DV_METHOD_COMPOSITION', '', `Method ${name} changes an executable inherited Policy.`));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
result.set(name, {
|
|
141
|
-
identity: `${definitionRefIndex(owner)}\u0000method\u0000${name}`,
|
|
142
|
-
owner,
|
|
143
|
-
name,
|
|
144
|
-
declaration: ownMethod,
|
|
145
|
-
});
|
|
146
|
-
inherited.delete(name);
|
|
147
|
-
}
|
|
148
|
-
for (const [name, entries] of inherited) {
|
|
149
|
-
if (entries.length > 1) {
|
|
150
|
-
diagnostics.push(diagnostic('DV_METHOD_COMPOSITION', '', `Inherited method ${name} is ambiguous and has no local override.`));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (declaration.kind === 'node' && !declaration.abstract) {
|
|
154
|
-
for (const [name, method] of result) {
|
|
155
|
-
if (method.declaration.inheritance === 'abstract') {
|
|
156
|
-
diagnostics.push(diagnostic('DV_METHOD_COMPOSITION', '', `Concrete Class ${owner.name} leaves method ${name} abstract.`));
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return result;
|
|
110
|
+
const own = new Map(Object.entries('methods' in declaration ? declaration.methods : {}).map(([name, method]) => [
|
|
111
|
+
name,
|
|
112
|
+
{ key: `${Key.of(owner)}.method.${name}`, owner, name, declaration: method },
|
|
113
|
+
]));
|
|
114
|
+
return composeMethods(parents, own, declaration.abstract, (left, right) => sameMethodSignature(left.declaration, right.declaration), (message) => diagnostics.push(diagnostic('DV_METHOD_COMPOSITION', '', message, Key.of(owner))));
|
|
161
115
|
}
|
|
162
116
|
function sameMethodSignature(left, right) {
|
|
163
117
|
return (json.serialize(canonicalizeValueSchema(left.input, 'callable-input')) ===
|
|
@@ -176,6 +130,3 @@ function canonicalMethodOutput(value) {
|
|
|
176
130
|
return 'binary';
|
|
177
131
|
}
|
|
178
132
|
}
|
|
179
|
-
function canonicalPolicy(value) {
|
|
180
|
-
return json.serialize(value === undefined ? null : normalizePolicyChecks(value));
|
|
181
|
-
}
|
|
@@ -71,7 +71,7 @@ function validateFacet(environment, declaration, pointer, diagnostics) {
|
|
|
71
71
|
return;
|
|
72
72
|
for (const [name, method] of Object.entries(declaration.methods)) {
|
|
73
73
|
const methodPointer = `${pointer}/methods/${escapePointer(name)}`;
|
|
74
|
-
if (method.
|
|
74
|
+
if (method.abstract === true && method.policy !== undefined) {
|
|
75
75
|
diagnostics.push(diagnostic('DV_METHOD_ABSTRACT', `${methodPointer}/policy`, 'Abstract Methods cannot have a Policy.'));
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -188,9 +188,9 @@ const callableShape = {
|
|
|
188
188
|
const methodDeclarationSchema = strictObject({
|
|
189
189
|
...callableShape,
|
|
190
190
|
static: boolean(),
|
|
191
|
-
|
|
191
|
+
abstract: boolean(),
|
|
192
192
|
}).superRefine((method, context) => {
|
|
193
|
-
if (method.
|
|
193
|
+
if (method.abstract === true && method.policy !== undefined) {
|
|
194
194
|
context.addIssue({
|
|
195
195
|
code: 'custom',
|
|
196
196
|
path: ['policy'],
|
|
@@ -26,7 +26,7 @@ export function referenceContracts(validation, ref, pointer) {
|
|
|
26
26
|
case 'method': {
|
|
27
27
|
const declaringClass = owner.classes[ref.owner.name];
|
|
28
28
|
const method = declaringClass?.kind === 'node' ? declaringClass.methods[ref.name] : undefined;
|
|
29
|
-
return method === undefined || method.
|
|
29
|
+
return method === undefined || method.abstract
|
|
30
30
|
? invalidPolicyObjectRef(validation, ref, pointer)
|
|
31
31
|
: [{ kind: 'exact', ref: PROJECTION_FUNCTION }];
|
|
32
32
|
}
|