@lumencast/compiler 0.1.0
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 +201 -0
- package/README.md +75 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/canonicalize.d.ts +9 -0
- package/dist/canonicalize.d.ts.map +1 -0
- package/dist/canonicalize.js +59 -0
- package/dist/canonicalize.js.map +1 -0
- package/dist/compile.d.ts +10 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +270 -0
- package/dist/compile.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/lsml-types.d.ts +152 -0
- package/dist/lsml-types.d.ts.map +1 -0
- package/dist/lsml-types.js +4 -0
- package/dist/lsml-types.js.map +1 -0
- package/package.json +45 -0
- package/src/canonicalize.ts +64 -0
- package/src/compile.ts +273 -0
- package/src/index.ts +20 -0
- package/src/lsml-types.ts +156 -0
package/src/compile.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// LSML 1.0 → flat RenderBundle compiler.
|
|
2
|
+
//
|
|
3
|
+
// LSML lets authors write idiomatic primitives with inline `bind: { value: "path" }`,
|
|
4
|
+
// CSS-style `style.fontSize`, `repeat.template`, `animate` directives. The runtime
|
|
5
|
+
// expects a flat shape: per-node `bindings` map, primitive-specific prop names
|
|
6
|
+
// (Solar lineage: `text.size`, `text.colour`), and `repeat` whose template is its
|
|
7
|
+
// only child.
|
|
8
|
+
//
|
|
9
|
+
// This compiler bridges the two formats. It does NOT execute the bundle — it
|
|
10
|
+
// produces a JSON the runtime then renders.
|
|
11
|
+
|
|
12
|
+
import type { RenderBundle, RenderNode } from "@lumencast/runtime";
|
|
13
|
+
import type {
|
|
14
|
+
LSMLAnimateDirective,
|
|
15
|
+
LSMLBundle,
|
|
16
|
+
LSMLNode,
|
|
17
|
+
LSMLRepeat,
|
|
18
|
+
LSMLText,
|
|
19
|
+
} from "./lsml-types.js";
|
|
20
|
+
|
|
21
|
+
export interface CompileOptions {
|
|
22
|
+
/** When true, throws on any unrecognized LSML extension. Default false (warn-only). */
|
|
23
|
+
strict?: boolean;
|
|
24
|
+
/** Optional warn collector — receives each warning string. */
|
|
25
|
+
onWarn?: (message: string) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function compileBundle(lsml: LSMLBundle, options: CompileOptions = {}): RenderBundle {
|
|
29
|
+
if (lsml.lsml !== "1.0") {
|
|
30
|
+
throw new Error(`compiler: only LSML 1.0 is supported, got ${lsml.lsml}`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
scene_version: lsml.scene_version,
|
|
34
|
+
root: compileNode(lsml.layout, options),
|
|
35
|
+
...(lsml.operator_inputs
|
|
36
|
+
? {
|
|
37
|
+
operator_inputs: lsml.operator_inputs.map((oi) => ({
|
|
38
|
+
path: oi.path,
|
|
39
|
+
label: oi.label,
|
|
40
|
+
type: oi.type as never,
|
|
41
|
+
writable_by: oi.writable_by,
|
|
42
|
+
...(oi.group !== undefined ? { group: oi.group } : {}),
|
|
43
|
+
...(oi.constraints ?? {}),
|
|
44
|
+
})),
|
|
45
|
+
}
|
|
46
|
+
: {}),
|
|
47
|
+
...(lsml.external_adapters
|
|
48
|
+
? {
|
|
49
|
+
external_adapters: lsml.external_adapters as RenderBundle["external_adapters"],
|
|
50
|
+
}
|
|
51
|
+
: {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function compileNode(node: LSMLNode, opts: CompileOptions): RenderNode {
|
|
56
|
+
if (node.kind === "repeat") {
|
|
57
|
+
return compileRepeat(node, opts);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const props: Record<string, unknown> = {};
|
|
61
|
+
const bindings: Record<string, string> = {};
|
|
62
|
+
|
|
63
|
+
// Common: bind.value/src → bindings
|
|
64
|
+
if (node.bind?.value !== undefined) bindings["value"] = node.bind.value;
|
|
65
|
+
if (node.bind?.src !== undefined) bindings["src"] = node.bind.src;
|
|
66
|
+
if (node.bindStyle) {
|
|
67
|
+
for (const [k, v] of Object.entries(node.bindStyle)) bindings[k] = v;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
switch (node.kind) {
|
|
71
|
+
case "stack":
|
|
72
|
+
if (node.direction !== undefined) props["direction"] = node.direction;
|
|
73
|
+
if (node.gap !== undefined) props["gap"] = node.gap;
|
|
74
|
+
if (node.align !== undefined) props["align"] = mapAlign(node.align);
|
|
75
|
+
if (node.justify !== undefined) props["justify"] = mapJustify(node.justify);
|
|
76
|
+
if (node.padding !== undefined) props["padding"] = node.padding;
|
|
77
|
+
if (node.rtl !== undefined) props["rtl"] = node.rtl;
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
case "grid":
|
|
81
|
+
if (node.columns !== undefined) props["columns"] = node.columns;
|
|
82
|
+
if (node.rows !== undefined) props["rows"] = node.rows;
|
|
83
|
+
if (node.gap !== undefined) props["gap"] = node.gap;
|
|
84
|
+
if (node.padding !== undefined) props["padding"] = node.padding;
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case "frame":
|
|
88
|
+
if (node.size !== undefined) {
|
|
89
|
+
props["width"] = node.size.w;
|
|
90
|
+
props["height"] = node.size.h;
|
|
91
|
+
}
|
|
92
|
+
if (node.position !== undefined) {
|
|
93
|
+
props["x"] = node.position.x;
|
|
94
|
+
props["y"] = node.position.y;
|
|
95
|
+
}
|
|
96
|
+
if (node.background !== undefined) props["background"] = node.background;
|
|
97
|
+
break;
|
|
98
|
+
|
|
99
|
+
case "text":
|
|
100
|
+
mapTextStyle(node, props);
|
|
101
|
+
if (node.format !== undefined) props["format"] = node.format;
|
|
102
|
+
if (node.maxLines !== undefined) props["maxLines"] = node.maxLines;
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case "image":
|
|
106
|
+
props["alt"] = node.alt;
|
|
107
|
+
props["width"] = node.size.w;
|
|
108
|
+
props["height"] = node.size.h;
|
|
109
|
+
if (node.fit !== undefined) props["fit"] = node.fit;
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case "shape":
|
|
113
|
+
props["geometry"] = node.geometry;
|
|
114
|
+
if (node.size !== undefined) {
|
|
115
|
+
props["width"] = node.size.w;
|
|
116
|
+
props["height"] = node.size.h;
|
|
117
|
+
}
|
|
118
|
+
if (node.pathData !== undefined) props["pathData"] = node.pathData;
|
|
119
|
+
if (node.fill !== undefined) props["fill"] = node.fill;
|
|
120
|
+
if (node.stroke !== undefined) props["stroke"] = node.stroke;
|
|
121
|
+
if (node.cornerRadius !== undefined) props["cornerRadius"] = node.cornerRadius;
|
|
122
|
+
if (node.ariaLabel !== undefined) props["ariaLabel"] = node.ariaLabel;
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case "media":
|
|
126
|
+
props["kind_hint"] = node.kind_hint;
|
|
127
|
+
if (node.controls !== undefined) props["controls"] = node.controls;
|
|
128
|
+
if (node.autoplay !== undefined) props["autoplay"] = node.autoplay;
|
|
129
|
+
if (node.muted !== undefined) props["muted"] = node.muted;
|
|
130
|
+
if (node.loop !== undefined) props["loop"] = node.loop;
|
|
131
|
+
if (node.size !== undefined) {
|
|
132
|
+
props["width"] = node.size.w;
|
|
133
|
+
props["height"] = node.size.h;
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const children = node.children?.map((c) => compileNode(c, opts));
|
|
139
|
+
|
|
140
|
+
const out: RenderNode = { kind: node.kind };
|
|
141
|
+
if (node.id !== undefined) out.id = node.id;
|
|
142
|
+
if (Object.keys(props).length > 0) out.props = props;
|
|
143
|
+
if (Object.keys(bindings).length > 0) out.bindings = bindings;
|
|
144
|
+
if (children && children.length > 0) out.children = children;
|
|
145
|
+
|
|
146
|
+
// Animate directive → transitions on the listed prop keys.
|
|
147
|
+
if (node.animate) {
|
|
148
|
+
const tx = compileAnimate(node.animate);
|
|
149
|
+
if (tx) {
|
|
150
|
+
const transitions: Record<string, ReturnType<typeof compileAnimate>> = {};
|
|
151
|
+
if (node.animate.opacity !== undefined) transitions["opacity"] = tx;
|
|
152
|
+
if (node.animate.transform?.scale !== undefined) transitions["scale"] = tx;
|
|
153
|
+
if (node.animate.transform?.rotate !== undefined) transitions["rotate"] = tx;
|
|
154
|
+
if (node.animate.transform?.translate !== undefined) {
|
|
155
|
+
transitions["x"] = tx;
|
|
156
|
+
transitions["y"] = tx;
|
|
157
|
+
}
|
|
158
|
+
// Type assertion: RenderNode.transitions matches Transition shape; the
|
|
159
|
+
// cast keeps the compiler self-contained without re-importing the runtime
|
|
160
|
+
// Transition type.
|
|
161
|
+
if (Object.keys(transitions).length > 0) {
|
|
162
|
+
out.transitions = transitions as RenderNode["transitions"];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function compileRepeat(node: LSMLRepeat, opts: CompileOptions): RenderNode {
|
|
171
|
+
if (!node.bind?.items) {
|
|
172
|
+
throw new Error(`compiler: repeat node "${node.id ?? "<anon>"}" missing bind.items`);
|
|
173
|
+
}
|
|
174
|
+
const compiledTemplate = compileNode(node.template, opts);
|
|
175
|
+
const out: RenderNode = {
|
|
176
|
+
kind: "repeat",
|
|
177
|
+
bindings: { items: node.bind.items },
|
|
178
|
+
children: [compiledTemplate],
|
|
179
|
+
};
|
|
180
|
+
if (node.id !== undefined) out.id = node.id;
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function mapTextStyle(node: LSMLText, props: Record<string, unknown>): void {
|
|
185
|
+
if (!node.style) return;
|
|
186
|
+
const s = node.style;
|
|
187
|
+
// Solar's Text primitive consumes size/weight/colour (UK), not CSS-style names.
|
|
188
|
+
if (s.fontSize !== undefined) props["size"] = s.fontSize;
|
|
189
|
+
if (s.fontWeight !== undefined) props["weight"] = s.fontWeight;
|
|
190
|
+
if (s.color !== undefined) props["colour"] = s.color;
|
|
191
|
+
if (s.textAlign !== undefined) props["align"] = mapTextAlign(s.textAlign);
|
|
192
|
+
if (s.lineHeight !== undefined) props["lineHeight"] = s.lineHeight;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function mapAlign(a: NonNullable<Extract<LSMLNode, { kind: "stack" }>["align"]>): string {
|
|
196
|
+
// LSML uses CSS-grid vocabulary; Solar's Stack consumes flexbox vocabulary.
|
|
197
|
+
switch (a) {
|
|
198
|
+
case "start":
|
|
199
|
+
return "flex-start";
|
|
200
|
+
case "center":
|
|
201
|
+
return "center";
|
|
202
|
+
case "end":
|
|
203
|
+
return "flex-end";
|
|
204
|
+
case "stretch":
|
|
205
|
+
return "stretch";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function mapJustify(j: NonNullable<Extract<LSMLNode, { kind: "stack" }>["justify"]>): string {
|
|
210
|
+
switch (j) {
|
|
211
|
+
case "start":
|
|
212
|
+
return "flex-start";
|
|
213
|
+
case "center":
|
|
214
|
+
return "center";
|
|
215
|
+
case "end":
|
|
216
|
+
return "flex-end";
|
|
217
|
+
case "space-between":
|
|
218
|
+
return "space-between";
|
|
219
|
+
case "space-around":
|
|
220
|
+
return "space-around";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function mapTextAlign(a: NonNullable<NonNullable<LSMLText["style"]>["textAlign"]>): string {
|
|
225
|
+
switch (a) {
|
|
226
|
+
case "start":
|
|
227
|
+
return "left";
|
|
228
|
+
case "end":
|
|
229
|
+
return "right";
|
|
230
|
+
default:
|
|
231
|
+
return a;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function compileAnimate(a: LSMLAnimateDirective):
|
|
236
|
+
| {
|
|
237
|
+
kind: "tween";
|
|
238
|
+
duration_ms: number;
|
|
239
|
+
ease?: "linear" | "cubic-in" | "cubic-out" | "cubic-in-out";
|
|
240
|
+
}
|
|
241
|
+
| { kind: "spring"; stiffness?: number; damping?: number }
|
|
242
|
+
| undefined {
|
|
243
|
+
const t = a.transition;
|
|
244
|
+
if (!t) return undefined;
|
|
245
|
+
if (t.easing === "spring") {
|
|
246
|
+
const out: { kind: "spring"; stiffness?: number; damping?: number } = { kind: "spring" };
|
|
247
|
+
if (t.stiffness !== undefined) out.stiffness = t.stiffness;
|
|
248
|
+
if (t.damping !== undefined) out.damping = t.damping;
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
kind: "tween",
|
|
253
|
+
duration_ms: t.duration ?? 200,
|
|
254
|
+
ease: mapEase(t.easing),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function mapEase(
|
|
259
|
+
e: "linear" | "ease-in" | "ease-out" | "ease-in-out" | "spring" | undefined,
|
|
260
|
+
): "linear" | "cubic-in" | "cubic-out" | "cubic-in-out" | undefined {
|
|
261
|
+
switch (e) {
|
|
262
|
+
case "linear":
|
|
263
|
+
return "linear";
|
|
264
|
+
case "ease-in":
|
|
265
|
+
return "cubic-in";
|
|
266
|
+
case "ease-out":
|
|
267
|
+
return "cubic-out";
|
|
268
|
+
case "ease-in-out":
|
|
269
|
+
return "cubic-in-out";
|
|
270
|
+
default:
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Public surface of @lumencast/compiler.
|
|
2
|
+
|
|
3
|
+
export { compileBundle, type CompileOptions } from "./compile.js";
|
|
4
|
+
export { canonicalize, hashBundle, ZERO_HASH } from "./canonicalize.js";
|
|
5
|
+
export type {
|
|
6
|
+
LSMLBundle,
|
|
7
|
+
LSMLNode,
|
|
8
|
+
LSMLPrimitiveKind,
|
|
9
|
+
LSMLBindObject,
|
|
10
|
+
LSMLAnimateDirective,
|
|
11
|
+
LSMLStack,
|
|
12
|
+
LSMLGrid,
|
|
13
|
+
LSMLFrame,
|
|
14
|
+
LSMLText,
|
|
15
|
+
LSMLImage,
|
|
16
|
+
LSMLShape,
|
|
17
|
+
LSMLMedia,
|
|
18
|
+
LSMLRepeat,
|
|
19
|
+
LSMLOperatorInput,
|
|
20
|
+
} from "./lsml-types.js";
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// LSML 1.0 input types — what authors write.
|
|
2
|
+
// Reference: lumencast-protocol/spec/LSML-1.md
|
|
3
|
+
|
|
4
|
+
export type LSMLPrimitiveKind =
|
|
5
|
+
| "stack"
|
|
6
|
+
| "grid"
|
|
7
|
+
| "frame"
|
|
8
|
+
| "text"
|
|
9
|
+
| "image"
|
|
10
|
+
| "shape"
|
|
11
|
+
| "media"
|
|
12
|
+
| "repeat";
|
|
13
|
+
|
|
14
|
+
export interface LSMLBindObject {
|
|
15
|
+
/** Most primitives bind a `value` to a leaf path. */
|
|
16
|
+
value?: string;
|
|
17
|
+
/** image / media bind a `src`. */
|
|
18
|
+
src?: string;
|
|
19
|
+
/** repeat binds `items`. */
|
|
20
|
+
items?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LSMLAnimateDirective {
|
|
24
|
+
transition?: {
|
|
25
|
+
duration?: number;
|
|
26
|
+
easing?: "linear" | "ease-in" | "ease-out" | "ease-in-out" | "spring";
|
|
27
|
+
stiffness?: number;
|
|
28
|
+
damping?: number;
|
|
29
|
+
};
|
|
30
|
+
transform?: {
|
|
31
|
+
translate?: [number, number];
|
|
32
|
+
scale?: number | [number, number];
|
|
33
|
+
rotate?: number;
|
|
34
|
+
};
|
|
35
|
+
opacity?: number;
|
|
36
|
+
filter?: {
|
|
37
|
+
blur?: number;
|
|
38
|
+
brightness?: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface LSMLBaseNode {
|
|
43
|
+
kind: LSMLPrimitiveKind;
|
|
44
|
+
id?: string;
|
|
45
|
+
bind?: LSMLBindObject;
|
|
46
|
+
bindStyle?: Record<string, string>;
|
|
47
|
+
animate?: LSMLAnimateDirective;
|
|
48
|
+
children?: LSMLNode[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface LSMLStack extends LSMLBaseNode {
|
|
52
|
+
kind: "stack";
|
|
53
|
+
direction?: "horizontal" | "vertical";
|
|
54
|
+
gap?: number;
|
|
55
|
+
align?: "start" | "center" | "end" | "stretch";
|
|
56
|
+
justify?: "start" | "center" | "end" | "space-between" | "space-around";
|
|
57
|
+
padding?: number | [number, number, number, number];
|
|
58
|
+
rtl?: "auto" | boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LSMLGrid extends LSMLBaseNode {
|
|
62
|
+
kind: "grid";
|
|
63
|
+
columns: number | unknown[];
|
|
64
|
+
rows?: number | unknown[];
|
|
65
|
+
gap?: number | [number, number];
|
|
66
|
+
padding?: number | unknown[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface LSMLFrame extends LSMLBaseNode {
|
|
70
|
+
kind: "frame";
|
|
71
|
+
size?: { w: number; h: number };
|
|
72
|
+
position?: { x: number; y: number };
|
|
73
|
+
background?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface LSMLText extends LSMLBaseNode {
|
|
77
|
+
kind: "text";
|
|
78
|
+
style?: {
|
|
79
|
+
fontSize?: number | string;
|
|
80
|
+
fontWeight?: number;
|
|
81
|
+
color?: string;
|
|
82
|
+
textAlign?: "start" | "center" | "end" | "left" | "right";
|
|
83
|
+
lineHeight?: number;
|
|
84
|
+
};
|
|
85
|
+
format?: { kind: string; [extra: string]: unknown };
|
|
86
|
+
maxLines?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface LSMLImage extends LSMLBaseNode {
|
|
90
|
+
kind: "image";
|
|
91
|
+
alt: string;
|
|
92
|
+
size: { w: number; h: number };
|
|
93
|
+
fit?: "contain" | "cover" | "fill" | "none";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface LSMLShape extends LSMLBaseNode {
|
|
97
|
+
kind: "shape";
|
|
98
|
+
geometry: "rect" | "circle" | "path";
|
|
99
|
+
size?: { w: number; h: number };
|
|
100
|
+
pathData?: string;
|
|
101
|
+
fill?: string;
|
|
102
|
+
stroke?: { color: string; width: number };
|
|
103
|
+
cornerRadius?: number;
|
|
104
|
+
ariaLabel?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface LSMLMedia extends LSMLBaseNode {
|
|
108
|
+
kind: "media";
|
|
109
|
+
kind_hint: "video" | "audio";
|
|
110
|
+
controls?: boolean;
|
|
111
|
+
autoplay?: boolean;
|
|
112
|
+
muted?: boolean;
|
|
113
|
+
loop?: boolean;
|
|
114
|
+
size?: { w: number; h: number };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface LSMLRepeat extends LSMLBaseNode {
|
|
118
|
+
kind: "repeat";
|
|
119
|
+
scope: string;
|
|
120
|
+
key?: string;
|
|
121
|
+
template: LSMLNode;
|
|
122
|
+
limit?: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type LSMLNode =
|
|
126
|
+
| LSMLStack
|
|
127
|
+
| LSMLGrid
|
|
128
|
+
| LSMLFrame
|
|
129
|
+
| LSMLText
|
|
130
|
+
| LSMLImage
|
|
131
|
+
| LSMLShape
|
|
132
|
+
| LSMLMedia
|
|
133
|
+
| LSMLRepeat;
|
|
134
|
+
|
|
135
|
+
export interface LSMLOperatorInput {
|
|
136
|
+
path: string;
|
|
137
|
+
label: string;
|
|
138
|
+
type: string;
|
|
139
|
+
constraints?: Record<string, unknown>;
|
|
140
|
+
writable_by: string[];
|
|
141
|
+
group?: string;
|
|
142
|
+
[extra: string]: unknown;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface LSMLBundle {
|
|
146
|
+
lsml: "1.0";
|
|
147
|
+
scene_id: string;
|
|
148
|
+
scene_version: string;
|
|
149
|
+
layout: LSMLNode;
|
|
150
|
+
operator_inputs?: LSMLOperatorInput[];
|
|
151
|
+
external_adapters?: unknown[];
|
|
152
|
+
defaults?: Record<string, unknown>;
|
|
153
|
+
assets?: { allowedHosts?: string[]; fonts?: unknown[]; preload?: string[] };
|
|
154
|
+
i18n?: { default_locale?: string; locales?: Record<string, Record<string, string>> };
|
|
155
|
+
metadata?: Record<string, unknown>;
|
|
156
|
+
}
|