@getaster/elements 0.0.0-stage → 0.1.0-preview.1
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/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +6 -2
- package/bin/aster-theme-check.js +317 -0
- package/dist/base/drag.d.ts +45 -0
- package/dist/base/drag.js +139 -0
- package/dist/base/element.d.ts +51 -0
- package/dist/base/element.js +133 -0
- package/dist/breadcrumbs/breadcrumbs.d.ts +23 -0
- package/dist/breadcrumbs/breadcrumbs.js +51 -0
- package/dist/button/button.d.ts +37 -0
- package/dist/button/button.js +108 -0
- package/dist/caret/caret-group.d.ts +14 -0
- package/dist/caret/caret-group.js +71 -0
- package/dist/carousel/carousel.d.ts +28 -0
- package/dist/carousel/carousel.js +134 -0
- package/dist/component/component.d.ts +73 -0
- package/dist/component/component.js +190 -0
- package/dist/component/echarts.d.ts +46 -0
- package/dist/component/echarts.js +95 -0
- package/dist/composition/authoring.d.ts +157 -0
- package/dist/composition/authoring.js +291 -0
- package/dist/composition/builtins.d.ts +1130 -0
- package/dist/composition/builtins.js +318 -0
- package/dist/composition/command-bar.d.ts +18 -0
- package/dist/composition/command-bar.js +34 -0
- package/dist/composition/commands.d.ts +14 -0
- package/dist/composition/commands.js +59 -0
- package/dist/composition/composite.d.ts +48 -0
- package/dist/composition/composite.js +409 -0
- package/dist/composition/contracts.d.ts +63 -0
- package/dist/composition/contracts.js +97 -0
- package/dist/composition/definition.d.ts +94 -0
- package/dist/composition/definition.js +291 -0
- package/dist/composition/element.d.ts +35 -0
- package/dist/composition/element.js +51 -0
- package/dist/composition/registry.d.ts +83 -0
- package/dist/composition/registry.js +99 -0
- package/dist/composition/schema.d.ts +9 -0
- package/dist/composition/schema.js +139 -0
- package/dist/composition/sessions.d.ts +15 -0
- package/dist/composition/sessions.js +61 -0
- package/dist/dashboard/dashboard.d.ts +26 -0
- package/dist/dashboard/dashboard.js +296 -0
- package/dist/dashboard/layout.d.ts +19 -0
- package/dist/dashboard/layout.js +48 -0
- package/dist/dashboard/widget.d.ts +42 -0
- package/dist/dashboard/widget.js +165 -0
- package/dist/date/calendar.d.ts +34 -0
- package/dist/date/calendar.js +137 -0
- package/dist/date/date-field.d.ts +18 -0
- package/dist/date/date-field.js +81 -0
- package/dist/date/dates.d.ts +22 -0
- package/dist/date/dates.js +80 -0
- package/dist/dialog/dialog.d.ts +23 -0
- package/dist/dialog/dialog.js +70 -0
- package/dist/field/bound-field.d.ts +55 -0
- package/dist/field/bound-field.js +139 -0
- package/dist/field/check.d.ts +11 -0
- package/dist/field/check.js +26 -0
- package/dist/field/composite-field.d.ts +35 -0
- package/dist/field/composite-field.js +88 -0
- package/dist/field/field.d.ts +29 -0
- package/dist/field/field.js +75 -0
- package/dist/field/input.d.ts +15 -0
- package/dist/field/input.js +18 -0
- package/dist/field/number-field.d.ts +33 -0
- package/dist/field/number-field.js +179 -0
- package/dist/field/numeric.d.ts +4 -0
- package/dist/field/numeric.js +57 -0
- package/dist/field/values.d.ts +14 -0
- package/dist/field/values.js +60 -0
- package/dist/filter/filter-builder.d.ts +18 -0
- package/dist/filter/filter-builder.js +305 -0
- package/dist/form/controls.d.ts +20 -0
- package/dist/form/controls.js +34 -0
- package/dist/form/form.d.ts +46 -0
- package/dist/form/form.js +85 -0
- package/dist/format.d.ts +10 -0
- package/dist/format.js +57 -0
- package/dist/grid/filters.d.ts +16 -0
- package/dist/grid/filters.js +66 -0
- package/dist/grid/grid.d.ts +209 -0
- package/dist/grid/grid.js +2587 -0
- package/dist/grid/rows.d.ts +32 -0
- package/dist/grid/rows.js +89 -0
- package/dist/grid/view.d.ts +69 -0
- package/dist/grid/view.js +172 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +61 -0
- package/dist/layout/stack.d.ts +112 -0
- package/dist/layout/stack.js +21 -0
- package/dist/list-edit/list-edit.d.ts +42 -0
- package/dist/list-edit/list-edit.js +220 -0
- package/dist/listbox/listbox.d.ts +45 -0
- package/dist/listbox/listbox.js +187 -0
- package/dist/menu/menu.d.ts +55 -0
- package/dist/menu/menu.js +286 -0
- package/dist/motion.d.ts +8 -0
- package/dist/motion.js +23 -0
- package/dist/overlay/anchor.d.ts +18 -0
- package/dist/overlay/anchor.js +41 -0
- package/dist/picker/options.d.ts +59 -0
- package/dist/picker/options.js +97 -0
- package/dist/picker/picker.d.ts +46 -0
- package/dist/picker/picker.js +492 -0
- package/dist/picker/tree-choices.d.ts +38 -0
- package/dist/picker/tree-choices.js +72 -0
- package/dist/picker/tree-picker.d.ts +29 -0
- package/dist/picker/tree-picker.js +282 -0
- package/dist/progress/policy.d.ts +5 -0
- package/dist/progress/policy.js +28 -0
- package/dist/progress/status.d.ts +20 -0
- package/dist/progress/status.js +38 -0
- package/dist/rule/operators.d.ts +8 -0
- package/dist/rule/operators.js +9 -0
- package/dist/rule/rule-editor.d.ts +57 -0
- package/dist/rule/rule-editor.js +290 -0
- package/dist/section/accordion.d.ts +24 -0
- package/dist/section/accordion.js +90 -0
- package/dist/section/section.d.ts +30 -0
- package/dist/section/section.js +99 -0
- package/dist/select/choice-element.d.ts +20 -0
- package/dist/select/choice-element.js +55 -0
- package/dist/select/choice.d.ts +34 -0
- package/dist/select/choice.js +122 -0
- package/dist/select/many.d.ts +13 -0
- package/dist/select/many.js +20 -0
- package/dist/select/select.d.ts +16 -0
- package/dist/select/select.js +40 -0
- package/dist/sidebar/sidebar.d.ts +47 -0
- package/dist/sidebar/sidebar.js +105 -0
- package/dist/slicer/slicer.d.ts +26 -0
- package/dist/slicer/slicer.js +140 -0
- package/dist/split/split.d.ts +41 -0
- package/dist/split/split.js +237 -0
- package/dist/styles/contract.d.ts +36 -0
- package/dist/styles/contract.js +39 -0
- package/dist/switch/switch.d.ts +22 -0
- package/dist/switch/switch.js +69 -0
- package/dist/tabs/tabs.d.ts +30 -0
- package/dist/tabs/tabs.js +202 -0
- package/dist/tile-grid/scroll.d.ts +9 -0
- package/dist/tile-grid/scroll.js +21 -0
- package/dist/tile-grid/tile-grid.d.ts +64 -0
- package/dist/tile-grid/tile-grid.js +734 -0
- package/dist/toast/toast.d.ts +25 -0
- package/dist/toast/toast.js +92 -0
- package/dist/tree/navigator.d.ts +41 -0
- package/dist/tree/navigator.js +228 -0
- package/dist/window/chrome.d.ts +3 -0
- package/dist/window/chrome.js +7 -0
- package/dist/window/window.d.ts +46 -0
- package/dist/window/window.js +347 -0
- package/dist/window/workspace.d.ts +29 -0
- package/dist/window/workspace.js +137 -0
- package/package.json +45 -4
- package/styles/aster-ui.css +1147 -0
- package/styles/base.css +88 -0
- package/styles/contract.ts +41 -0
- package/styles/elements.css +1058 -0
- package/styles/harbour.css +126 -0
- package/styles/lamplight.css +118 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { type Schema } from "./schema.ts";
|
|
2
|
+
import type { ReadonlySignal } from "@preact/signals-core";
|
|
3
|
+
import type { Command, Publication } from "@getaster/data";
|
|
4
|
+
import { type CompositeResolver, type CompositeComponentType, type CompositeContract, type CompositeModelType, type CompositeRenderer } from "./registry.ts";
|
|
5
|
+
import { type CompositeType, type CompositeTypes } from "./contracts.ts";
|
|
6
|
+
import { type CompositeDefinition, type CompositeSources } from "./definition.ts";
|
|
7
|
+
import type { TemplateResult } from "lit-html";
|
|
8
|
+
import type { CompositePlacement } from "./composite.ts";
|
|
9
|
+
type Value<S> = S extends CompositeType<infer T> ? T : unknown;
|
|
10
|
+
type Prefix<P extends string, C> = {
|
|
11
|
+
[K in keyof C as K extends string ? `${P}.${K}` : never]: Value<C[K]>;
|
|
12
|
+
};
|
|
13
|
+
type Field<M, K extends PropertyKey> = K extends keyof M ? NonNullable<M[K]> : {};
|
|
14
|
+
type References<I, M, D extends CompositeDefinition> = Prefix<"inputs", I> & Prefix<"model", Field<M, "exports">> & Prefix<"model.commands", Field<M, "commands">> & Prefix<"model.publications", Field<M, "publications">> & {
|
|
15
|
+
[K in keyof NonNullable<D["commands"]> as K extends string ? `commands.${K}` : never]: Command;
|
|
16
|
+
};
|
|
17
|
+
type FitsEach<V, S> = V extends Value<S> ? V extends undefined ? true : S extends {
|
|
18
|
+
readonly fields: infer F;
|
|
19
|
+
} ? Exclude<keyof V, keyof F> extends never ? false extends {
|
|
20
|
+
[K in keyof F]: K extends keyof V ? Fits<V[K], F[K]> : F[K] extends {
|
|
21
|
+
optional: true;
|
|
22
|
+
} ? true : false;
|
|
23
|
+
}[keyof F] ? false : true : false : S extends {
|
|
24
|
+
readonly item: infer Item;
|
|
25
|
+
} ? V extends readonly (infer E)[] ? Fits<E, Item> : false : S extends {
|
|
26
|
+
readonly signalValue: infer Inner;
|
|
27
|
+
} ? V extends ReadonlySignal<infer E> ? Fits<E, Inner> : false : true : false;
|
|
28
|
+
type Fits<V, S> = false extends FitsEach<V, S> ? false : true;
|
|
29
|
+
type Compatible<R, T, S = CompositeType<T>> = {
|
|
30
|
+
[K in keyof R]: unknown extends R[K] ? never : R[K] extends T ? Fits<R[K], S> extends true ? K : never : never;
|
|
31
|
+
}[keyof R] & string;
|
|
32
|
+
type Bound<R, T, S> = {
|
|
33
|
+
[K in keyof R]: R[K] extends ReadonlySignal<infer V> ? V extends T ? Fits<V, S> extends true ? K : never : never : never;
|
|
34
|
+
}[keyof R] & string;
|
|
35
|
+
type Renderers<R, T, K extends string> = {
|
|
36
|
+
[N in keyof R]: R[N] extends {
|
|
37
|
+
render: infer F;
|
|
38
|
+
properties: readonly (infer P)[];
|
|
39
|
+
} ? K extends P ? F extends T ? N : never : never : never;
|
|
40
|
+
}[keyof R] & string;
|
|
41
|
+
type Input<T, R, F, K extends string, Depth extends readonly unknown[] = [], S = CompositeType<T>> = {
|
|
42
|
+
readonly ref: Compatible<R, T, S>;
|
|
43
|
+
} | {
|
|
44
|
+
readonly bind: Bound<R, T, S>;
|
|
45
|
+
} | {
|
|
46
|
+
readonly renderer: Renderers<F, T, K>;
|
|
47
|
+
} | (Depth["length"] extends 5 ? never : T extends string | number | boolean | null ? T : T extends (...args: never[]) => unknown ? never : T extends readonly (infer E)[] ? readonly Input<E, R, F, K, [...Depth, 0], S extends {
|
|
48
|
+
readonly item: infer Item;
|
|
49
|
+
} ? Item : CompositeType<E>>[] : T extends object ? {
|
|
50
|
+
readonly [P in keyof T]: Input<T[P], R, F, K, [...Depth, 0], S extends {
|
|
51
|
+
readonly fields: infer Fields;
|
|
52
|
+
} ? P extends keyof Fields ? Fields[P] : CompositeType<T[P]> : CompositeType<T[P]>>;
|
|
53
|
+
} : never);
|
|
54
|
+
type Props<C extends CompositeContract, R, F> = {
|
|
55
|
+
readonly [K in keyof C as C[K] extends {
|
|
56
|
+
optional: true;
|
|
57
|
+
} ? never : K]: Input<Value<C[K]>, R, F, K & string, [], C[K]>;
|
|
58
|
+
} & {
|
|
59
|
+
readonly [K in keyof C as C[K] extends {
|
|
60
|
+
optional: true;
|
|
61
|
+
} ? K : never]?: Input<Value<C[K]>, R, F, K & string, [], C[K]>;
|
|
62
|
+
};
|
|
63
|
+
type CheckedProps<C extends CompositeContract, Given, R, F> = Props<C, R, F> & {
|
|
64
|
+
[K in Exclude<keyof Given, keyof C>]: never;
|
|
65
|
+
};
|
|
66
|
+
type GivenProps<C extends CompositeContract, Given, R, F, K extends string> = {} extends Props<C, R, F> ? {
|
|
67
|
+
readonly [P in K]?: CheckedProps<C, Given, R, F>;
|
|
68
|
+
} : {
|
|
69
|
+
readonly [P in K]: CheckedProps<C, Given, R, F>;
|
|
70
|
+
};
|
|
71
|
+
type CheckedLayout<L, N extends string, R, F> = L extends {
|
|
72
|
+
child: unknown;
|
|
73
|
+
} ? {
|
|
74
|
+
readonly child: N;
|
|
75
|
+
} : L extends {
|
|
76
|
+
type: "stack";
|
|
77
|
+
items: infer Items extends readonly unknown[];
|
|
78
|
+
} ? {
|
|
79
|
+
readonly items: {
|
|
80
|
+
[K in keyof Items]: CheckedLayout<Items[K], N, R, F>;
|
|
81
|
+
};
|
|
82
|
+
} : L extends {
|
|
83
|
+
type: "deck";
|
|
84
|
+
items: infer Items;
|
|
85
|
+
} ? {
|
|
86
|
+
readonly active: Input<string, R, F, "active">;
|
|
87
|
+
readonly items: {
|
|
88
|
+
[K in keyof Items]: CheckedLayout<Items[K], N, R, F>;
|
|
89
|
+
};
|
|
90
|
+
} : L extends {
|
|
91
|
+
type: "outlet";
|
|
92
|
+
content: infer Content;
|
|
93
|
+
} ? {
|
|
94
|
+
readonly active: Input<boolean, R, F, "active">;
|
|
95
|
+
readonly content: CheckedLayout<Content, N, R, F>;
|
|
96
|
+
} : never;
|
|
97
|
+
type Checked<D extends CompositeDefinition, M extends Record<string, CompositeModelType>, C extends Record<string, CompositeComponentType>, F, I> = {
|
|
98
|
+
readonly model: D["model"]["type"] extends keyof M ? {
|
|
99
|
+
readonly type: D["model"]["type"];
|
|
100
|
+
} & GivenProps<M[D["model"]["type"]]["inputs"], NonNullable<D["model"]["inputs"]>, Prefix<"inputs", I>, F, "inputs"> : never;
|
|
101
|
+
readonly children: {
|
|
102
|
+
[K in keyof D["children"]]: D["children"][K]["type"] extends keyof C ? {
|
|
103
|
+
readonly type: D["children"][K]["type"];
|
|
104
|
+
} & GivenProps<C[D["children"][K]["type"]]["props"], NonNullable<D["children"][K]["props"]>, References<I, M[D["model"]["type"]], D>, F, "props"> : never;
|
|
105
|
+
};
|
|
106
|
+
readonly commands?: {
|
|
107
|
+
[K in keyof NonNullable<D["commands"]>]: {
|
|
108
|
+
readonly use: Compatible<References<I, M[D["model"]["type"]], D>, Command>;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
readonly publish?: {
|
|
112
|
+
[K in keyof NonNullable<D["publish"]>]: {
|
|
113
|
+
readonly ref: Compatible<References<I, M[D["model"]["type"]], D>, Publication>;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
readonly layout: CheckedLayout<D["layout"], keyof D["children"] & string, References<I, M[D["model"]["type"]], D>, F>;
|
|
117
|
+
};
|
|
118
|
+
export interface RegistryCatalog {
|
|
119
|
+
readonly inputs?: CompositeTypes;
|
|
120
|
+
readonly models: Readonly<Record<string, CompositeModelType>>;
|
|
121
|
+
readonly components: Readonly<Record<string, CompositeComponentType>>;
|
|
122
|
+
readonly renderers?: Readonly<Record<string, CompositeRenderer>>;
|
|
123
|
+
}
|
|
124
|
+
export interface CompositeDiagnostic {
|
|
125
|
+
readonly path: string;
|
|
126
|
+
readonly message: string;
|
|
127
|
+
}
|
|
128
|
+
export interface CompositeCompletion {
|
|
129
|
+
readonly label: string;
|
|
130
|
+
readonly description: string;
|
|
131
|
+
}
|
|
132
|
+
/** Renderer names, allowed properties and implementation share one declaration. */
|
|
133
|
+
export declare function defineCompositeRenderer<const P extends readonly string[], F extends (...args: never[]) => unknown>(properties: P, render: F): CompositeRenderer & {
|
|
134
|
+
readonly properties: P;
|
|
135
|
+
readonly render: F;
|
|
136
|
+
};
|
|
137
|
+
/** Preserve registry types for static definitions and expose the same contracts to tools. */
|
|
138
|
+
export declare function defineCompositeRegistry<const S extends RegistryCatalog>(spec: S): CompositeAuthoring<S>;
|
|
139
|
+
/** A registry plus pre-mount definition checks and context-sensitive completion. */
|
|
140
|
+
export declare class CompositeAuthoring<S extends RegistryCatalog> {
|
|
141
|
+
#private;
|
|
142
|
+
readonly registry: CompositeResolver<Field<S, "inputs">>;
|
|
143
|
+
readonly catalog: S;
|
|
144
|
+
constructor(spec: S);
|
|
145
|
+
/** Typed JS/TS definitions retain the exact same serializable runtime format. */
|
|
146
|
+
define<const D extends CompositeDefinition>(definition: D & Checked<D, S["models"], S["components"], Field<S, "renderers">, Field<S, "inputs">>): D;
|
|
147
|
+
/** Typed placement of existing children; activation still follows the definition. */
|
|
148
|
+
placement<D extends CompositeDefinition>(_definition: D, template: (place: (child?: keyof D["children"] & string) => HTMLElement) => TemplateResult): CompositePlacement;
|
|
149
|
+
/** Check loaded YAML/JSON without constructing a model or starting data work. */
|
|
150
|
+
validate(definition: unknown, sources?: CompositeSources): CompositeDefinition;
|
|
151
|
+
diagnostics(definition: unknown): readonly CompositeDiagnostic[];
|
|
152
|
+
/** Dotted paths use the same `definition.*` addresses as YAML diagnostics. */
|
|
153
|
+
complete(definition: unknown, path: string): readonly CompositeCompletion[];
|
|
154
|
+
/** Export standard JSON Schema for YAML language-server associations. */
|
|
155
|
+
schema(definition?: CompositeDefinition): Schema;
|
|
156
|
+
}
|
|
157
|
+
export {};
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { planCommands, commandTargets } from "./commands.js";
|
|
2
|
+
import { asSchema, schemaProperty, compatibility, literalSchema } from "./schema.js";
|
|
3
|
+
import { CompositeRegistry, snapshotContract } from "./registry.js";
|
|
4
|
+
import {} from "./contracts.js";
|
|
5
|
+
import { CompositeError, defineComposite, located } from "./definition.js";
|
|
6
|
+
/** Renderer names, allowed properties and implementation share one declaration. */
|
|
7
|
+
export function defineCompositeRenderer(properties, render) {
|
|
8
|
+
const names = Object.freeze([...properties]);
|
|
9
|
+
return Object.freeze({ properties: names, render, accepts: (property) => names.includes(property) });
|
|
10
|
+
}
|
|
11
|
+
/** Preserve registry types for static definitions and expose the same contracts to tools. */
|
|
12
|
+
export function defineCompositeRegistry(spec) {
|
|
13
|
+
return new CompositeAuthoring(spec);
|
|
14
|
+
}
|
|
15
|
+
/** A registry plus pre-mount definition checks and context-sensitive completion. */
|
|
16
|
+
export class CompositeAuthoring {
|
|
17
|
+
registry;
|
|
18
|
+
catalog;
|
|
19
|
+
constructor(spec) {
|
|
20
|
+
const models = Object.fromEntries(Object.entries(spec.models).map(([name, model]) => [name, Object.freeze({ ...model, create: model.create.bind(model), inputs: snapshotContract(model.inputs), exports: snapshotContract(model.exports), ...(model.commands ? { commands: snapshotContract(model.commands) } : {}), ...(model.publications ? { publications: snapshotContract(model.publications) } : {}) })]));
|
|
21
|
+
const components = Object.fromEntries(Object.entries(spec.components).map(([name, component]) => [name, Object.freeze({ ...component, props: snapshotContract(component.props) })]));
|
|
22
|
+
const renderers = Object.fromEntries(Object.entries(spec.renderers ?? {}).map(([name, renderer]) => [name, Object.freeze({ ...renderer, ...(renderer.properties ? { properties: Object.freeze([...renderer.properties]) } : {}) })]));
|
|
23
|
+
this.catalog = Object.freeze({ ...spec, inputs: snapshotContract(spec.inputs ?? {}), models: Object.freeze(models), components: Object.freeze(components), renderers: Object.freeze(renderers) });
|
|
24
|
+
const registry = new CompositeRegistry();
|
|
25
|
+
for (const [name, model] of Object.entries(models))
|
|
26
|
+
registry.model(name, model);
|
|
27
|
+
for (const [name, component] of Object.entries(components))
|
|
28
|
+
registry.component(name, component);
|
|
29
|
+
for (const [name, renderer] of Object.entries(renderers))
|
|
30
|
+
registry.renderer(name, renderer);
|
|
31
|
+
this.registry = registry.seal(this.catalog.inputs ?? {});
|
|
32
|
+
}
|
|
33
|
+
/** Typed JS/TS definitions retain the exact same serializable runtime format. */
|
|
34
|
+
define(definition) {
|
|
35
|
+
return this.validate(definition);
|
|
36
|
+
}
|
|
37
|
+
/** Typed placement of existing children; activation still follows the definition. */
|
|
38
|
+
placement(_definition, template) {
|
|
39
|
+
return template;
|
|
40
|
+
}
|
|
41
|
+
/** Check loaded YAML/JSON without constructing a model or starting data work. */
|
|
42
|
+
validate(definition, sources = {}) {
|
|
43
|
+
const diagnostics = this.diagnostics(definition);
|
|
44
|
+
const error = diagnostics[0];
|
|
45
|
+
if (error)
|
|
46
|
+
throw located(new CompositeError(error.path, error.message), sources);
|
|
47
|
+
return defineComposite(definition);
|
|
48
|
+
}
|
|
49
|
+
diagnostics(definition) {
|
|
50
|
+
let d;
|
|
51
|
+
try {
|
|
52
|
+
d = defineComposite(definition);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return [{ path: error instanceof CompositeError ? error.path : "definition", message: error instanceof Error ? error.message : String(error) }];
|
|
56
|
+
}
|
|
57
|
+
const errors = [];
|
|
58
|
+
const fail = (path, message) => { errors.push({ path, message }); };
|
|
59
|
+
const model = this.catalog.models[d.model.type];
|
|
60
|
+
if (!model) {
|
|
61
|
+
fail("definition.model.type", `Unknown model '${d.model.type}'`);
|
|
62
|
+
return errors;
|
|
63
|
+
}
|
|
64
|
+
const refs = this.#references(d);
|
|
65
|
+
errors.push(...planCommands(d.commands ?? {}, reference => {
|
|
66
|
+
const target = refs[reference];
|
|
67
|
+
return !reference.startsWith("commands.") && !!target && compatibility(target.schema ?? {}, { asterType: "Command" }) !== false;
|
|
68
|
+
}).errors);
|
|
69
|
+
const value = (v, expected, path, property, available = refs) => {
|
|
70
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
71
|
+
const node = v;
|
|
72
|
+
const kind = ["ref", "bind", "renderer"].find(key => Object.hasOwn(node, key));
|
|
73
|
+
if (kind) {
|
|
74
|
+
if (Object.keys(node).length !== 1 || typeof node[kind] !== "string") {
|
|
75
|
+
fail(path, `Invalid ${kind} node`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const name = node[kind];
|
|
79
|
+
if (kind === "renderer") {
|
|
80
|
+
const renderer = this.catalog.renderers?.[name];
|
|
81
|
+
if (!renderer)
|
|
82
|
+
fail(path, `Unknown renderer '${name}'`);
|
|
83
|
+
else if (!renderer.accepts(property))
|
|
84
|
+
fail(path, `Renderer '${name}' does not accept '${property}'`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const target = available[name];
|
|
88
|
+
if (!target)
|
|
89
|
+
fail(path, `Unknown public reference '${name}'`);
|
|
90
|
+
else {
|
|
91
|
+
const schema = target.schema ?? {};
|
|
92
|
+
if (kind === "bind" && schema.asterType !== "signal" && (schema.asterType || schema.type))
|
|
93
|
+
fail(path, "bind requires a declared signal");
|
|
94
|
+
else if (expected && compatibility(kind === "bind" ? asSchema(schema.value) : schema, expected.schema ?? {}) === false)
|
|
95
|
+
fail(path, `Reference '${name}' is incompatible with '${property}'`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (hasNodes(v)) {
|
|
102
|
+
const shape = containerSchema(expected?.schema ?? {});
|
|
103
|
+
if (shape.type && shape.type !== (Array.isArray(v) ? "array" : "object") || shape.asterType) {
|
|
104
|
+
fail(path, "Binding container does not match the property type");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!Array.isArray(v) && shape.type === "object") {
|
|
108
|
+
const fields = asSchema(shape.properties);
|
|
109
|
+
for (const key of Object.keys(v))
|
|
110
|
+
if (shape.additionalProperties === false && !Object.hasOwn(fields, key))
|
|
111
|
+
fail(`${path}.${key}`, "Unknown input/property");
|
|
112
|
+
if (Array.isArray(shape.required))
|
|
113
|
+
for (const key of shape.required)
|
|
114
|
+
if (typeof key === "string" && !Object.hasOwn(v, key))
|
|
115
|
+
fail(`${path}.${key}`, "Required property is missing");
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(v))
|
|
118
|
+
v.forEach((item, i) => value(item, schemaProperty(nestedSchema(expected?.schema ?? {}, "0")), `${path}.${i}`, property, available));
|
|
119
|
+
else
|
|
120
|
+
for (const [key, item] of Object.entries(v))
|
|
121
|
+
value(item, schemaProperty(nestedSchema(expected?.schema ?? {}, key)), `${path}.${key}`, property, available);
|
|
122
|
+
}
|
|
123
|
+
else if (expected && !expected.validate(v))
|
|
124
|
+
fail(path, `Expected ${expected.description ?? "a valid value"}`);
|
|
125
|
+
};
|
|
126
|
+
const props = (given, contract, path, available = refs) => {
|
|
127
|
+
for (const [key, v] of Object.entries(given)) {
|
|
128
|
+
if (!Object.hasOwn(contract, key))
|
|
129
|
+
fail(`${path}.${key}`, "Unknown input/property");
|
|
130
|
+
else
|
|
131
|
+
value(v, contract[key], `${path}.${key}`, key, available);
|
|
132
|
+
}
|
|
133
|
+
for (const [key, field] of Object.entries(contract))
|
|
134
|
+
if (!field.optional && !Object.hasOwn(given, key))
|
|
135
|
+
fail(`${path}.${key}`, "Required property is missing");
|
|
136
|
+
};
|
|
137
|
+
props(d.model.inputs ?? {}, model.inputs, "definition.model.inputs", prefix("inputs", this.catalog.inputs ?? {}));
|
|
138
|
+
for (const [key, child] of Object.entries(d.children)) {
|
|
139
|
+
const component = this.catalog.components[child.type];
|
|
140
|
+
if (!component)
|
|
141
|
+
fail(`definition.children.${key}.type`, `Unknown component '${child.type}'`);
|
|
142
|
+
else if (child.enabled !== false)
|
|
143
|
+
props(child.props ?? {}, component.props, `definition.children.${key}.props`);
|
|
144
|
+
}
|
|
145
|
+
for (const [key, publication] of Object.entries(d.publish ?? {})) {
|
|
146
|
+
const target = refs[publication.ref];
|
|
147
|
+
if (!target || compatibility(target.schema ?? {}, { asterType: "publication" }) === false)
|
|
148
|
+
fail(`definition.publish.${key}.ref`, "Expected a declared publication reference");
|
|
149
|
+
}
|
|
150
|
+
const layout = (node, path) => {
|
|
151
|
+
if ("child" in node)
|
|
152
|
+
return;
|
|
153
|
+
switch (node.type) {
|
|
154
|
+
case "stack":
|
|
155
|
+
node.items.forEach((item, i) => layout(item, `${path}.items.${i}`));
|
|
156
|
+
break;
|
|
157
|
+
case "deck":
|
|
158
|
+
value(node.active, schemaProperty({ type: "string" }), `${path}.active`, "active");
|
|
159
|
+
Object.entries(node.items).forEach(([key, item]) => layout(item, `${path}.items.${key}`));
|
|
160
|
+
break;
|
|
161
|
+
case "outlet":
|
|
162
|
+
value(node.active, schemaProperty({ type: "boolean" }), `${path}.active`, "active");
|
|
163
|
+
layout(node.content, `${path}.content`);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
layout(d.layout, "definition.layout");
|
|
168
|
+
return errors;
|
|
169
|
+
}
|
|
170
|
+
/** Dotted paths use the same `definition.*` addresses as YAML diagnostics. */
|
|
171
|
+
complete(definition, path) {
|
|
172
|
+
const d = definition && typeof definition === "object" ? definition : undefined;
|
|
173
|
+
const result = (entries) => Object.entries(entries).map(([label, spec]) => ({ label, description: "description" in spec && typeof spec.description === "string" ? spec.description : label }));
|
|
174
|
+
if (path === "definition.model.type")
|
|
175
|
+
return result(this.catalog.models);
|
|
176
|
+
if (/^definition\.children\.[^.]+\.type$/.test(path))
|
|
177
|
+
return result(this.catalog.components);
|
|
178
|
+
const parts = path.split(".");
|
|
179
|
+
if (parts[0] !== "definition" || !d)
|
|
180
|
+
return [];
|
|
181
|
+
const model = this.catalog.models[d.model?.type];
|
|
182
|
+
const child = d.children?.[parts[2] ?? ""];
|
|
183
|
+
const contract = parts[1] === "children" ? this.catalog.components[child?.type ?? ""]?.props : parts[1] === "model" && parts[2] === "inputs" ? model?.inputs : undefined;
|
|
184
|
+
const root = parts[1] === "children" ? 4 : 3;
|
|
185
|
+
if (contract && parts.length === root)
|
|
186
|
+
return result(contract);
|
|
187
|
+
const kind = parts.at(-1);
|
|
188
|
+
const refs = parts[1] === "model" ? prefix("inputs", this.catalog.inputs ?? {}) : this.#references(d);
|
|
189
|
+
if (parts[1] === "commands" && kind === "use") {
|
|
190
|
+
const base = Object.keys(filter(refs, field => isCommand(field))).filter(key => !key.startsWith("commands."));
|
|
191
|
+
return result(Object.fromEntries(commandTargets(d.commands ?? {}, base, parts[2]).map(name => [name, refs[name] ?? {}])));
|
|
192
|
+
}
|
|
193
|
+
if (parts[1] === "publish" && kind === "ref")
|
|
194
|
+
return result(filter(refs, field => field.schema?.asterType === "publication"));
|
|
195
|
+
let expected = contract?.[parts[root] ?? ""];
|
|
196
|
+
if (!expected)
|
|
197
|
+
return [];
|
|
198
|
+
for (const segment of parts.slice(root + 1, -1))
|
|
199
|
+
expected = schemaProperty(nestedSchema(expected.schema ?? {}, segment));
|
|
200
|
+
if (kind === "renderer")
|
|
201
|
+
return result(Object.fromEntries(Object.entries(this.catalog.renderers ?? {}).filter(([, renderer]) => renderer.accepts(parts[root] ?? "")).map(([name]) => [name, {}])));
|
|
202
|
+
if (kind !== "ref" && kind !== "bind")
|
|
203
|
+
return [];
|
|
204
|
+
return result(filter(refs, field => {
|
|
205
|
+
const schema = field.schema ?? {};
|
|
206
|
+
if (kind === "bind" && schema.asterType !== "signal")
|
|
207
|
+
return false;
|
|
208
|
+
return compatibility(kind === "bind" ? asSchema(schema.value) : schema, expected.schema ?? {}) === true;
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
/** Export standard JSON Schema for YAML language-server associations. */
|
|
212
|
+
schema(definition) {
|
|
213
|
+
const object = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
|
|
214
|
+
const node = (kind, names) => names.length ? object({ [kind]: { type: "string", enum: names } }, [kind]) : { not: {} };
|
|
215
|
+
const branches = Object.entries(this.catalog.models).map(([modelName, model]) => {
|
|
216
|
+
const d = definition?.model.type === modelName ? definition : { version: 1, name: "Schema", model: { type: modelName }, children: {}, layout: { type: "stack", items: [] } };
|
|
217
|
+
const refs = this.#references(d);
|
|
218
|
+
const value = (field, property, available = refs) => {
|
|
219
|
+
const schema = field.schema ?? {};
|
|
220
|
+
const compatible = (bind) => Object.entries(available).filter(([, target]) => {
|
|
221
|
+
const source = target.schema ?? {};
|
|
222
|
+
if (bind && source.asterType !== "signal")
|
|
223
|
+
return false;
|
|
224
|
+
// Unknown custom checks are offered only by explicit ref/bind nodes; runtime still checks them.
|
|
225
|
+
return compatibility(bind ? asSchema(source.value) : source, schema) !== false;
|
|
226
|
+
}).map(([name]) => name);
|
|
227
|
+
const literal = { ...literalSchema(schema) };
|
|
228
|
+
if (Array.isArray(schema.anyOf))
|
|
229
|
+
literal.anyOf = schema.anyOf.map(inner => value(schemaProperty(asSchema(inner)), property, available));
|
|
230
|
+
if (schema.type === "array")
|
|
231
|
+
literal.items = value(schemaProperty(asSchema(schema.items)), property, available);
|
|
232
|
+
if (schema.type === "object")
|
|
233
|
+
literal.properties = Object.fromEntries(Object.entries(asSchema(schema.properties)).map(([key, inner]) => [key, value(schemaProperty(asSchema(inner)), property, available)]));
|
|
234
|
+
return { description: field.description ?? "", anyOf: [literal, node("ref", compatible(false)), node("bind", compatible(true)), node("renderer", Object.entries(this.catalog.renderers ?? {}).filter(([, renderer]) => renderer.accepts(property)).map(([name]) => name))] };
|
|
235
|
+
};
|
|
236
|
+
const contract = (fields, available = refs) => object(Object.fromEntries(Object.entries(fields).map(([key, field]) => [key, value(field, key, available)])), Object.keys(fields).filter(key => !fields[key].optional));
|
|
237
|
+
const child = Object.entries(this.catalog.components).map(([name, component]) => object({ type: { const: name }, props: contract(component.props), create: { enum: ["eager", "on-demand"] }, retain: { type: "boolean" }, enabled: { type: "boolean" } }, Object.values(component.props).some(field => !field.optional) ? ["type", "props"] : ["type"]));
|
|
238
|
+
const baseCommands = Object.keys(filter(refs, field => isCommand(field))).filter(key => !key.startsWith("commands."));
|
|
239
|
+
const commandSchema = (destination) => {
|
|
240
|
+
const names = commandTargets(d.commands ?? {}, baseCommands, destination);
|
|
241
|
+
return object({ use: names.length ? { enum: names } : { not: {} }, enabledWhen: {}, reason: { type: "string" } }, ["use"]);
|
|
242
|
+
};
|
|
243
|
+
const publications = Object.keys(filter(refs, field => field.schema?.asterType === "publication"));
|
|
244
|
+
return object({
|
|
245
|
+
version: { const: 1 }, name: { type: "string", pattern: "^[a-zA-Z][\\w-]*$" },
|
|
246
|
+
model: object({ type: { const: modelName }, inputs: contract(model.inputs, prefix("inputs", this.catalog.inputs ?? {})) }, Object.values(model.inputs).some(field => !field.optional) ? ["type", "inputs"] : ["type"]),
|
|
247
|
+
children: { type: "object", additionalProperties: child.length ? { oneOf: child } : false },
|
|
248
|
+
commands: { type: "object", properties: Object.fromEntries(Object.keys(d.commands ?? {}).map(key => [key, commandSchema(key)])), additionalProperties: commandSchema() },
|
|
249
|
+
publish: { type: "object", additionalProperties: object({ ref: publications.length ? { enum: publications } : { not: {} } }, ["ref"]) },
|
|
250
|
+
customize: { type: "object" }, layout: { $ref: "#/$defs/layout" },
|
|
251
|
+
}, ["version", "name", "model", "children", "layout"]);
|
|
252
|
+
});
|
|
253
|
+
return { $schema: "https://json-schema.org/draft/2020-12/schema", title: "Aster workspace", ...(branches.length ? { oneOf: branches } : { not: {} }), $defs: { layout: {
|
|
254
|
+
oneOf: [
|
|
255
|
+
{ type: "object", properties: { child: { type: "string", ...(definition ? { enum: Object.keys(definition.children) } : {}) } }, required: ["child"], additionalProperties: false },
|
|
256
|
+
{ type: "object", properties: { type: { const: "stack" }, items: { type: "array", items: { $ref: "#/$defs/layout" } } }, required: ["type", "items"], additionalProperties: false },
|
|
257
|
+
{ type: "object", properties: { type: { const: "deck" }, active: {}, items: { type: "object", additionalProperties: { $ref: "#/$defs/layout" } } }, required: ["type", "active", "items"], additionalProperties: false },
|
|
258
|
+
{ type: "object", properties: { type: { const: "outlet" }, active: {}, content: { $ref: "#/$defs/layout" } }, required: ["type", "active", "content"], additionalProperties: false },
|
|
259
|
+
],
|
|
260
|
+
} } };
|
|
261
|
+
}
|
|
262
|
+
#references(d) {
|
|
263
|
+
const model = this.catalog.models[d.model?.type];
|
|
264
|
+
const refs = { ...prefix("inputs", this.catalog.inputs ?? {}), ...prefix("model", model?.exports ?? {}), ...prefix("model.commands", model?.commands ?? {}), ...prefix("model.publications", model?.publications ?? {}) };
|
|
265
|
+
const plan = planCommands(d.commands ?? {}, reference => !!refs[reference] && compatibility(refs[reference].schema ?? {}, { asterType: "Command" }) !== false);
|
|
266
|
+
for (const name of plan.order)
|
|
267
|
+
refs[`commands.${name}`] = schemaProperty({ asterType: "Command" });
|
|
268
|
+
return refs;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function prefix(name, contract) { return Object.fromEntries(Object.entries(contract).map(([key, spec]) => [`${name}.${key}`, spec])); }
|
|
272
|
+
function filter(values, accept) { return Object.fromEntries(Object.entries(values).filter(([, value]) => accept(value))); }
|
|
273
|
+
function hasNodes(value) { return !!value && typeof value === "object" && (Object.keys(value).some(key => ["ref", "bind", "renderer"].includes(key)) || Object.values(value).some(hasNodes)); }
|
|
274
|
+
function nestedSchema(schema, key) {
|
|
275
|
+
if (Array.isArray(schema.anyOf))
|
|
276
|
+
return { anyOf: schema.anyOf.filter(branch => asSchema(branch).asterType !== "undefined").map(branch => nestedSchema(asSchema(branch), key)) };
|
|
277
|
+
const shape = containerSchema(schema);
|
|
278
|
+
return /^\d+$/.test(key) ? asSchema(shape.items) : asSchema(asSchema(shape.properties)[key]);
|
|
279
|
+
}
|
|
280
|
+
function containerSchema(schema) {
|
|
281
|
+
if (schema.type || schema.asterType)
|
|
282
|
+
return schema;
|
|
283
|
+
for (const key of ["allOf", "anyOf"])
|
|
284
|
+
if (Array.isArray(schema[key])) {
|
|
285
|
+
const found = schema[key].map(item => containerSchema(asSchema(item))).find(item => item.type || item.asterType && item.asterType !== "undefined");
|
|
286
|
+
if (found)
|
|
287
|
+
return found;
|
|
288
|
+
}
|
|
289
|
+
return schema;
|
|
290
|
+
}
|
|
291
|
+
function isCommand(field) { return compatibility(field.schema ?? {}, { asterType: "Command" }) === true; }
|