@field/cascade 0.2.22 → 0.3.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/AGENTS.md +47 -7
- package/ARCHITECTURE.md +30 -2
- package/CHANGELOG.md +107 -0
- package/DESIGN.md +10 -5
- package/README.md +103 -7
- package/dist/assets/CodeEditor-D22fVi-4.js +623 -0
- package/dist/assets/CodePanel-DPNEYfcR.js +2 -0
- package/dist/assets/CustomNodeDialog-DeHCAvgb.js +1 -0
- package/dist/assets/{ExportDialog-DWN4nhXI.js → ExportDialog-BJu2Bpvv.js} +2 -2
- package/dist/assets/ProjectSettings-DWpVtWEx.js +2 -0
- package/dist/assets/SettingsDialog-za00IS3L.js +1 -0
- package/dist/assets/VersionHistoryDialog-DTMGhI0_.js +3 -0
- package/dist/assets/{core-Bji4aZUU.js → core-JsyKMO1k.js} +29 -38
- package/dist/assets/{cssMode-B6Z_Q1Kh.js → cssMode-emdRheFK.js} +1 -1
- package/dist/assets/{editor.api-jMoW_43X.js → editor.api-xHX-fMSo.js} +1 -1
- package/dist/assets/{freemarker2-DvvPV6m5.js → freemarker2-C3M8_Gs3.js} +1 -1
- package/dist/assets/geo-BAR6lQp5.js +1 -0
- package/dist/assets/{handlebars-B8rfuSIa.js → handlebars-CBMurtFk.js} +1 -1
- package/dist/assets/{html-DyHm6JCp.js → html-xbLQwknw.js} +1 -1
- package/dist/assets/{htmlMode-E9XLWE5u.js → htmlMode-2wKdDTon.js} +1 -1
- package/dist/assets/{image-BUcQC7PG.js → image-i1j6nWKe.js} +26 -40
- package/dist/assets/index-BDV5xGBZ.css +1 -0
- package/dist/assets/index-CMcH8AgP.js +212 -0
- package/dist/assets/index-client-BoJgsN9M.js +1 -0
- package/dist/assets/{javascript-C_bM5PCN.js → javascript-DJaOo_2I.js} +1 -1
- package/dist/assets/{jsonMode-BdyVS7G4.js → jsonMode-Bheas8_0.js} +1 -1
- package/dist/assets/{legacy-DIu3XRwl.js → legacy-CAj2_7QE.js} +2 -2
- package/dist/assets/{liquid-CDkn3spA.js → liquid-BCj_BGbM.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-AB5DMAsr.js → lspLanguageFeatures-BfIsx_xz.js} +1 -1
- package/dist/assets/{mdx-BvH_NjDN.js → mdx-DwiQDiVO.js} +1 -1
- package/dist/assets/{nodeTypeUtils-CweA3NHd.js → nodeTypeUtils-BBA1JqHx.js} +1 -1
- package/dist/assets/{python-B7uii4fi.js → python-BjrB5nqj.js} +1 -1
- package/dist/assets/{razor-Z48fWdJc.js → razor-CfoBmGDs.js} +1 -1
- package/dist/assets/{tsMode-BDgacVG4.js → tsMode-BcEoMSxp.js} +1 -1
- package/dist/assets/{typescript-Derb6Oqa.js → typescript-DIg7TEmQ.js} +1 -1
- package/dist/assets/{workers-BtoMX_pm.js → workers-vXYuwtPw.js} +1 -1
- package/dist/assets/{xml-B1CaFSrG.js → xml-DexLQIQy.js} +1 -1
- package/dist/assets/{yaml-HmIeA0KH.js → yaml-DUnD_1dE.js} +1 -1
- package/dist/cli/index.js +16467 -10200
- package/dist/cli/runtime/gpu.ts +76 -0
- package/dist/cli/runtime/io.ts +93 -7
- package/dist/contracts/animation.d.ts +40 -0
- package/dist/contracts/animation.js +14 -0
- package/dist/contracts/capabilities.d.ts +69 -6
- package/dist/contracts/definition.d.ts +60 -2
- package/dist/contracts/document.d.ts +15 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/schema.js +8 -5
- package/dist/contracts/validate.d.ts +10 -1
- package/dist/contracts/validate.js +20 -3
- package/dist/gpu.d.ts +71 -0
- package/dist/gpu.js +53 -0
- package/dist/graphs/default.cascade +1 -1
- package/dist/index.html +5 -5
- package/dist/io.d.ts +196 -0
- package/dist/io.js +303 -0
- package/dist/net.d.ts +1 -0
- package/dist/net.js +28 -0
- package/dist/runtime/animation/channel.d.ts +108 -0
- package/dist/runtime/animation/channel.js +204 -0
- package/dist/runtime/animation/frameRange.d.ts +78 -0
- package/dist/runtime/animation/frameRange.js +76 -0
- package/dist/runtime/animation/index.d.ts +10 -0
- package/dist/runtime/animation/index.js +8 -0
- package/dist/runtime/builtins/core/random.d.ts +1 -1
- package/dist/runtime/builtins/core/remap.d.ts +1 -1
- package/dist/runtime/builtins/geo/copyToPoints.d.ts +1 -1
- package/dist/runtime/builtins/geo/copyToPoints.js +1 -1
- package/dist/runtime/builtins/geo/generators.d.ts +1 -1
- package/dist/runtime/builtins/geo/merge.d.ts +1 -1
- package/dist/runtime/builtins/geo/svgExport.d.ts +1 -1
- package/dist/runtime/builtins/geo/svgExport.js +1 -1
- package/dist/runtime/builtins/geo/transform.d.ts +1 -1
- package/dist/runtime/builtins/geo/transform.js +1 -1
- package/dist/runtime/definition/architecture.d.ts +1 -1
- package/dist/runtime/definition/extract.d.ts +1 -1
- package/dist/runtime/definition/extract.js +1 -1
- package/dist/runtime/error.d.ts +1 -1
- package/dist/runtime/expressions/evaluator.d.ts +53 -0
- package/dist/runtime/expressions/evaluator.js +193 -0
- package/dist/runtime/expressions/index.d.ts +15 -0
- package/dist/runtime/expressions/index.js +12 -0
- package/dist/runtime/expressions/math.d.ts +75 -0
- package/dist/runtime/expressions/math.js +140 -0
- package/dist/runtime/expressions/preprocess.d.ts +33 -0
- package/dist/runtime/expressions/preprocess.js +102 -0
- package/dist/runtime/expressions/scope.d.ts +27 -0
- package/dist/runtime/expressions/scope.js +10 -0
- package/dist/runtime/expressions/time.d.ts +54 -0
- package/dist/runtime/expressions/time.js +71 -0
- package/dist/runtime/expressions/types.d.ts +66 -0
- package/dist/runtime/expressions/types.js +9 -0
- package/dist/runtime/geometry/attributes.d.ts +2 -2
- package/dist/runtime/geometry/attributes.js +1 -1
- package/dist/runtime/geometry/builder.d.ts +1 -1
- package/dist/runtime/geometry/builder.js +1 -1
- package/dist/runtime/geometry/copy.d.ts +1 -1
- package/dist/runtime/geometry/copy.js +1 -1
- package/dist/runtime/geometry/generate.d.ts +1 -1
- package/dist/runtime/geometry/groups.d.ts +1 -1
- package/dist/runtime/geometry/groups.js +1 -1
- package/dist/runtime/geometry/matrix.d.ts +1 -1
- package/dist/runtime/geometry/merge.d.ts +1 -1
- package/dist/runtime/geometry/merge.js +1 -1
- package/dist/runtime/geometry/primitives.d.ts +1 -1
- package/dist/runtime/geometry/primitives.js +1 -1
- package/dist/runtime/geometry/promote.d.ts +1 -1
- package/dist/runtime/geometry/promote.js +1 -1
- package/dist/runtime/geometry/resample.d.ts +1 -1
- package/dist/runtime/geometry/resample.js +1 -1
- package/dist/runtime/geometry/svg.d.ts +1 -1
- package/dist/runtime/geometry/svg.js +1 -1
- package/dist/runtime/geometry/transform.d.ts +1 -1
- package/dist/runtime/geometry/transform.js +1 -1
- package/dist/runtime/index.d.ts +4 -1
- package/dist/runtime/index.js +4 -1
- package/dist/runtime/params/animator.d.ts +74 -0
- package/dist/runtime/params/animator.js +137 -0
- package/dist/runtime/params/index.d.ts +11 -0
- package/dist/runtime/params/index.js +9 -0
- package/dist/runtime/params/resolve.d.ts +73 -0
- package/dist/runtime/params/resolve.js +62 -0
- package/dist/runtime/runtime.d.ts +42 -0
- package/dist/runtime/runtime.js +351 -29
- package/dist/runtime/types.d.ts +32 -6
- package/dist/shell.d.ts +3 -3
- package/doc/NODE_REFERENCE.md +395 -0
- package/doc/PROJECT_AUTHORING.md +148 -6
- package/package.json +30 -3
- package/dist/assets/CodeEditor-DoaNEUaM.js +0 -641
- package/dist/assets/CodePanel-B0_2XJ-t.js +0 -2
- package/dist/assets/CustomNodeDialog-026lzecD.js +0 -1
- package/dist/assets/ProjectSettings-BzT164Yx.js +0 -2
- package/dist/assets/SettingsDialog-DmHb1V0r.js +0 -1
- package/dist/assets/VersionHistoryDialog-hH6VsXLX.js +0 -3
- package/dist/assets/geo-CXmCtzMM.js +0 -2
- package/dist/assets/index-BXYXSdgh.js +0 -224
- package/dist/assets/index-CrNke7SW.css +0 -1
- package/dist/assets/index-client-DN_FvjN8.js +0 -1
- package/dist/assets/quill-DjZ69wL_.js +0 -121
- package/dist/cli/index.js.map +0 -7
- package/dist/contracts/capabilities.d.ts.map +0 -1
- package/dist/contracts/capabilities.js.map +0 -1
- package/dist/contracts/color.d.ts.map +0 -1
- package/dist/contracts/color.js.map +0 -1
- package/dist/contracts/definition.d.ts.map +0 -1
- package/dist/contracts/definition.js.map +0 -1
- package/dist/contracts/document.d.ts.map +0 -1
- package/dist/contracts/document.js.map +0 -1
- package/dist/contracts/geometry.d.ts.map +0 -1
- package/dist/contracts/geometry.js.map +0 -1
- package/dist/contracts/index.d.ts.map +0 -1
- package/dist/contracts/index.js.map +0 -1
- package/dist/contracts/schema.d.ts.map +0 -1
- package/dist/contracts/schema.js.map +0 -1
- package/dist/contracts/validate.d.ts.map +0 -1
- package/dist/contracts/validate.js.map +0 -1
- package/dist/contracts/values.d.ts.map +0 -1
- package/dist/contracts/values.js.map +0 -1
- package/dist/graphs/default-orig.cascade +0 -128
- package/dist/runtime/browser.d.ts.map +0 -1
- package/dist/runtime/browser.js.map +0 -1
- package/dist/runtime/builtins/core/index.d.ts.map +0 -1
- package/dist/runtime/builtins/core/index.js.map +0 -1
- package/dist/runtime/builtins/core/random.d.ts.map +0 -1
- package/dist/runtime/builtins/core/random.js.map +0 -1
- package/dist/runtime/builtins/core/remap.d.ts.map +0 -1
- package/dist/runtime/builtins/core/remap.js.map +0 -1
- package/dist/runtime/builtins/core/routing.d.ts.map +0 -1
- package/dist/runtime/builtins/core/routing.js.map +0 -1
- package/dist/runtime/builtins/core/structural.d.ts.map +0 -1
- package/dist/runtime/builtins/core/structural.js.map +0 -1
- package/dist/runtime/builtins/geo/copyToPoints.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/copyToPoints.js.map +0 -1
- package/dist/runtime/builtins/geo/definitions.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/definitions.js.map +0 -1
- package/dist/runtime/builtins/geo/generators.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/generators.js.map +0 -1
- package/dist/runtime/builtins/geo/index.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/index.js.map +0 -1
- package/dist/runtime/builtins/geo/merge.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/merge.js.map +0 -1
- package/dist/runtime/builtins/geo/namespace.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/namespace.js.map +0 -1
- package/dist/runtime/builtins/geo/svgExport.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/svgExport.js.map +0 -1
- package/dist/runtime/builtins/geo/transform.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/transform.js.map +0 -1
- package/dist/runtime/builtins/index.d.ts.map +0 -1
- package/dist/runtime/builtins/index.js.map +0 -1
- package/dist/runtime/definition/architecture.d.ts.map +0 -1
- package/dist/runtime/definition/architecture.js.map +0 -1
- package/dist/runtime/definition/extract.d.ts.map +0 -1
- package/dist/runtime/definition/extract.js.map +0 -1
- package/dist/runtime/error.d.ts.map +0 -1
- package/dist/runtime/error.js.map +0 -1
- package/dist/runtime/geometry/attributes.d.ts.map +0 -1
- package/dist/runtime/geometry/attributes.js.map +0 -1
- package/dist/runtime/geometry/builder.d.ts.map +0 -1
- package/dist/runtime/geometry/builder.js.map +0 -1
- package/dist/runtime/geometry/copy.d.ts.map +0 -1
- package/dist/runtime/geometry/copy.js.map +0 -1
- package/dist/runtime/geometry/generate.d.ts.map +0 -1
- package/dist/runtime/geometry/generate.js.map +0 -1
- package/dist/runtime/geometry/groups.d.ts.map +0 -1
- package/dist/runtime/geometry/groups.js.map +0 -1
- package/dist/runtime/geometry/index.d.ts.map +0 -1
- package/dist/runtime/geometry/index.js.map +0 -1
- package/dist/runtime/geometry/matrix.d.ts.map +0 -1
- package/dist/runtime/geometry/matrix.js.map +0 -1
- package/dist/runtime/geometry/merge.d.ts.map +0 -1
- package/dist/runtime/geometry/merge.js.map +0 -1
- package/dist/runtime/geometry/primitives.d.ts.map +0 -1
- package/dist/runtime/geometry/primitives.js.map +0 -1
- package/dist/runtime/geometry/promote.d.ts.map +0 -1
- package/dist/runtime/geometry/promote.js.map +0 -1
- package/dist/runtime/geometry/resample.d.ts.map +0 -1
- package/dist/runtime/geometry/resample.js.map +0 -1
- package/dist/runtime/geometry/svg.d.ts.map +0 -1
- package/dist/runtime/geometry/svg.js.map +0 -1
- package/dist/runtime/geometry/transform.d.ts.map +0 -1
- package/dist/runtime/geometry/transform.js.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/node.d.ts.map +0 -1
- package/dist/runtime/node.js.map +0 -1
- package/dist/runtime/runtime.d.ts.map +0 -1
- package/dist/runtime/runtime.js.map +0 -1
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/types.js.map +0 -1
- package/dist/shell.js.map +0 -7
- package/dist/stage.js.map +0 -7
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cascade/gpu` — WebGPU's flag namespaces as plain values.
|
|
3
|
+
*
|
|
4
|
+
* Small, and it was load-bearing. TypeScript's DOM library declares the WebGPU
|
|
5
|
+
* *interfaces* but not the constant objects, so the only way to reach
|
|
6
|
+
* `GPUTextureUsage.RENDER_ATTACHMENT` was off `globalThis` — which
|
|
7
|
+
* `architecture/ambient-state` correctly forbids inside a node module, because
|
|
8
|
+
* an ambient read makes a node's inputs invisible to the static reader that
|
|
9
|
+
* lets `cascade check` say anything without cooking.
|
|
10
|
+
*
|
|
11
|
+
* So a sketch had to choose between a rule and a working shader, and it chose
|
|
12
|
+
* a project library file to hide the read in. Both stay honest now: the rule
|
|
13
|
+
* keeps its teeth and a node imports these instead. That is the whole reason
|
|
14
|
+
* this file exists, and it is why it had to land with the capability rather
|
|
15
|
+
* than after it — a rule whose only workaround is removed at the same moment
|
|
16
|
+
* is a rule; one enforced a release earlier is a nuisance.
|
|
17
|
+
*
|
|
18
|
+
* The values are fixed by the WebGPU specification, not by an implementation,
|
|
19
|
+
* which is what makes writing them down safe rather than a guess. They are
|
|
20
|
+
* checked against a real browser's own globals in `tests/gpu-usage-flags.test.ts`
|
|
21
|
+
* — the numbers below were verified against Chrome rather than recalled.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** `GPUBufferUsage`. */
|
|
25
|
+
export const BufferUsage = {
|
|
26
|
+
MAP_READ: 0x0001,
|
|
27
|
+
MAP_WRITE: 0x0002,
|
|
28
|
+
COPY_SRC: 0x0004,
|
|
29
|
+
COPY_DST: 0x0008,
|
|
30
|
+
INDEX: 0x0010,
|
|
31
|
+
VERTEX: 0x0020,
|
|
32
|
+
UNIFORM: 0x0040,
|
|
33
|
+
STORAGE: 0x0080,
|
|
34
|
+
INDIRECT: 0x0100,
|
|
35
|
+
QUERY_RESOLVE: 0x0200,
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
/** `GPUTextureUsage`. */
|
|
39
|
+
export const TextureUsage = {
|
|
40
|
+
COPY_SRC: 0x01,
|
|
41
|
+
COPY_DST: 0x02,
|
|
42
|
+
TEXTURE_BINDING: 0x04,
|
|
43
|
+
STORAGE_BINDING: 0x08,
|
|
44
|
+
RENDER_ATTACHMENT: 0x10,
|
|
45
|
+
/**
|
|
46
|
+
* Chrome 147 has this and it is newer than the rest — verified present
|
|
47
|
+
* 2026-09-08 alongside the others, but do not assume every implementation
|
|
48
|
+
* carries it. Included rather than omitted deliberately: leaving it out
|
|
49
|
+
* would send a node that wants it back to `globalThis`, which is the exact
|
|
50
|
+
* ambient read this file exists to remove, so an honest constant with a
|
|
51
|
+
* caveat beats an omission that forces a rule violation.
|
|
52
|
+
*/
|
|
53
|
+
TRANSIENT_ATTACHMENT: 0x20,
|
|
54
|
+
} as const;
|
|
55
|
+
|
|
56
|
+
/** `GPUShaderStage`, for a bind group layout entry's `visibility`. */
|
|
57
|
+
export const ShaderStage = {
|
|
58
|
+
VERTEX: 0x1,
|
|
59
|
+
FRAGMENT: 0x2,
|
|
60
|
+
COMPUTE: 0x4,
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
/** `GPUMapMode`, for `buffer.mapAsync`. */
|
|
64
|
+
export const MapMode = {
|
|
65
|
+
READ: 0x1,
|
|
66
|
+
WRITE: 0x2,
|
|
67
|
+
} as const;
|
|
68
|
+
|
|
69
|
+
/** `GPUColorWrite`, for a colour target's `writeMask`. */
|
|
70
|
+
export const ColorWrite = {
|
|
71
|
+
RED: 0x1,
|
|
72
|
+
GREEN: 0x2,
|
|
73
|
+
BLUE: 0x4,
|
|
74
|
+
ALPHA: 0x8,
|
|
75
|
+
ALL: 0xf,
|
|
76
|
+
} as const;
|
package/dist/cli/runtime/io.ts
CHANGED
|
@@ -43,6 +43,63 @@ export function mediaUrl(path: string, options: MediaOptions = {}): string {
|
|
|
43
43
|
return `/api/media/${encoded}${query ? `?${query}` : ''}`;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* The transport a host installs so `cascade/io` can reach project files.
|
|
48
|
+
*
|
|
49
|
+
* Same shape of decision as `cascade/stage`: the page posts to the server,
|
|
50
|
+
* because that is the only door a browser has, and a headless host installs a
|
|
51
|
+
* local transport before it runs the graph. Without one, every read here is a
|
|
52
|
+
* `fetch('/api/media/...')` against a relative URL with no origin to resolve it
|
|
53
|
+
* — which is exactly how a Canvas 2D node came to render in Studio and fail
|
|
54
|
+
* under `cascade run`.
|
|
55
|
+
*
|
|
56
|
+
* Paths stay project-relative on both sides. The bridge, not the node, decides
|
|
57
|
+
* what that resolves to.
|
|
58
|
+
*/
|
|
59
|
+
export interface IoBridge {
|
|
60
|
+
/** Read a project file's bytes. */
|
|
61
|
+
read(path: string, options?: MediaOptions): Promise<Uint8Array>;
|
|
62
|
+
/** Write bytes into the project cache. Returns the stored project-relative path. */
|
|
63
|
+
write(path: string, data: Uint8Array): Promise<string>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const IO_BRIDGE_KEY = '__cascadeIoBridge';
|
|
67
|
+
|
|
68
|
+
/** Install the local transport. Returns a disposer, so a test can take it down. */
|
|
69
|
+
export function installIoBridge(bridge: IoBridge | null): () => void {
|
|
70
|
+
const globals = globalThis as Record<string, unknown>;
|
|
71
|
+
const previous = globals[IO_BRIDGE_KEY];
|
|
72
|
+
globals[IO_BRIDGE_KEY] = bridge ?? undefined;
|
|
73
|
+
return () => {
|
|
74
|
+
globals[IO_BRIDGE_KEY] = previous;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function localIo(): IoBridge | null {
|
|
79
|
+
const bridge = (globalThis as Record<string, unknown>)[IO_BRIDGE_KEY] as IoBridge | undefined;
|
|
80
|
+
return bridge && typeof bridge.read === 'function' && typeof bridge.write === 'function' ? bridge : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A project file's bytes, over whichever transport this host has. Every read in
|
|
85
|
+
* this module goes through here so the two hosts differ in one place only.
|
|
86
|
+
*/
|
|
87
|
+
export async function loadBytes(path: string, options: MediaOptions = { raw: true }): Promise<ArrayBuffer> {
|
|
88
|
+
const bridge = localIo();
|
|
89
|
+
if (bridge) {
|
|
90
|
+
const bytes = await bridge.read(path, options);
|
|
91
|
+
// A Buffer is usually a view into a larger pool, and `loadFloats` builds a
|
|
92
|
+
// typed-array VIEW onto whatever comes back — so hand on the exact bytes.
|
|
93
|
+
const exact = bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength
|
|
94
|
+
? bytes.buffer
|
|
95
|
+
: bytes.slice().buffer;
|
|
96
|
+
return exact as ArrayBuffer;
|
|
97
|
+
}
|
|
98
|
+
const response = await fetch(mediaUrl(path, options));
|
|
99
|
+
if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
|
|
100
|
+
return await response.arrayBuffer();
|
|
101
|
+
}
|
|
102
|
+
|
|
46
103
|
/**
|
|
47
104
|
* Load a project image as an <img>. Defaults to `raw` — a node is processing
|
|
48
105
|
* these pixels, not showing them, so a re-encoded copy would be the wrong thing
|
|
@@ -50,14 +107,21 @@ export function mediaUrl(path: string, options: MediaOptions = {}): string {
|
|
|
50
107
|
*/
|
|
51
108
|
export async function loadImage(path: string, options: MediaOptions = { raw: true }): Promise<HTMLImageElement> {
|
|
52
109
|
const image = new Image();
|
|
53
|
-
|
|
54
|
-
|
|
110
|
+
const bridge = localIo();
|
|
111
|
+
if (bridge) {
|
|
112
|
+
// No origin to resolve a URL against; hand the decoder the bytes.
|
|
113
|
+
(image as unknown as { src: unknown }).src = new Uint8Array(await loadBytes(path, options));
|
|
114
|
+
} else {
|
|
115
|
+
image.crossOrigin = 'anonymous';
|
|
116
|
+
image.src = mediaUrl(path, options);
|
|
117
|
+
}
|
|
55
118
|
await image.decode();
|
|
56
119
|
return image;
|
|
57
120
|
}
|
|
58
121
|
|
|
59
122
|
/** Same, as an ImageBitmap — the right input for `texImage2D`. */
|
|
60
123
|
export async function loadBitmap(path: string, options: MediaOptions = { raw: true }): Promise<ImageBitmap> {
|
|
124
|
+
if (localIo()) return createImageBitmap(new Blob([await loadBytes(path, options)]));
|
|
61
125
|
const response = await fetch(mediaUrl(path, options));
|
|
62
126
|
if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
|
|
63
127
|
return createImageBitmap(await response.blob());
|
|
@@ -80,6 +144,17 @@ export interface SaveOptions {
|
|
|
80
144
|
quality?: number;
|
|
81
145
|
}
|
|
82
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Anything that can produce encoded pixels. The two browser canvases are the
|
|
149
|
+
* usual case; a headless host's canvas offers Skia's own encoder instead, and
|
|
150
|
+
* `saveImage` takes whichever it is handed rather than making a node care.
|
|
151
|
+
*/
|
|
152
|
+
export interface ImageSource {
|
|
153
|
+
convertToBlob?(options?: { type?: string; quality?: number }): Promise<Blob>;
|
|
154
|
+
encode?(format: string, quality?: number): Promise<Uint8Array>;
|
|
155
|
+
toBuffer?(mime: string): Uint8Array;
|
|
156
|
+
}
|
|
157
|
+
|
|
83
158
|
/**
|
|
84
159
|
* Write pixels into the project and get back the path to hand downstream.
|
|
85
160
|
* Confined to the cache directory, like every other node output.
|
|
@@ -88,7 +163,7 @@ export interface SaveOptions {
|
|
|
88
163
|
* node.out('path').setValue(out);
|
|
89
164
|
*/
|
|
90
165
|
export async function saveImage(
|
|
91
|
-
source: HTMLCanvasElement | OffscreenCanvas | Blob,
|
|
166
|
+
source: HTMLCanvasElement | OffscreenCanvas | Blob | ImageSource,
|
|
92
167
|
path: string,
|
|
93
168
|
options: SaveOptions = {}
|
|
94
169
|
): Promise<string> {
|
|
@@ -98,8 +173,14 @@ export async function saveImage(
|
|
|
98
173
|
|
|
99
174
|
if (source instanceof Blob) {
|
|
100
175
|
blob = source;
|
|
101
|
-
} else if (
|
|
176
|
+
} else if (typeof (source as OffscreenCanvas).convertToBlob === 'function') {
|
|
102
177
|
blob = await (source as OffscreenCanvas).convertToBlob({ type, quality: options.quality });
|
|
178
|
+
} else if (typeof (source as ImageSource).encode === 'function') {
|
|
179
|
+
// A headless canvas that was handed over unwrapped — Skia's own encoder,
|
|
180
|
+
// rather than a browser API it does not have.
|
|
181
|
+
blob = new Blob([(await (source as ImageSource).encode!(format, options.quality)) as BlobPart], { type });
|
|
182
|
+
} else if (typeof (source as ImageSource).toBuffer === 'function') {
|
|
183
|
+
blob = new Blob([(source as ImageSource).toBuffer!(type) as BlobPart], { type });
|
|
103
184
|
} else {
|
|
104
185
|
blob = await new Promise<Blob>((resolve, reject) => {
|
|
105
186
|
(source as HTMLCanvasElement).toBlob(
|
|
@@ -116,6 +197,13 @@ export async function saveImage(
|
|
|
116
197
|
/** Write any bytes into the project cache and return the path. */
|
|
117
198
|
export async function saveBytes(data: Blob | ArrayBuffer | Uint8Array, path: string): Promise<string> {
|
|
118
199
|
const clean = path.replace(/^\.?\//, '');
|
|
200
|
+
const bridge = localIo();
|
|
201
|
+
if (bridge) {
|
|
202
|
+
const bytes = data instanceof Uint8Array
|
|
203
|
+
? data
|
|
204
|
+
: new Uint8Array(data instanceof Blob ? await data.arrayBuffer() : data);
|
|
205
|
+
return await bridge.write(clean, bytes);
|
|
206
|
+
}
|
|
119
207
|
const response = await fetch(`/api/media/${clean.split('/').map(encodeURIComponent).join('/')}`, {
|
|
120
208
|
method: 'PUT',
|
|
121
209
|
headers: { 'Content-Type': 'application/octet-stream' },
|
|
@@ -204,9 +292,7 @@ function parseNpyHeader(buffer: ArrayBuffer): NpyHeader {
|
|
|
204
292
|
* still loads — it just costs a pass.
|
|
205
293
|
*/
|
|
206
294
|
export async function loadFloats(path: string): Promise<FloatImage> {
|
|
207
|
-
const
|
|
208
|
-
if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
|
|
209
|
-
const buffer = await response.arrayBuffer();
|
|
295
|
+
const buffer = await loadBytes(path, { raw: true });
|
|
210
296
|
const header = parseNpyHeader(buffer);
|
|
211
297
|
|
|
212
298
|
if (header.fortran) throw new Error('cascade/io: Fortran-ordered .npy is not supported');
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation as it appears in a document.
|
|
3
|
+
*
|
|
4
|
+
* A parameter can be a literal, an expression, or a keyframe channel. All
|
|
5
|
+
* three serialise in one place — the node's `props` map — because a channel
|
|
6
|
+
* that lived in a timeline object would need a registry of targets, and that
|
|
7
|
+
* registry goes stale the moment a node is renamed.
|
|
8
|
+
*
|
|
9
|
+
* A prop with neither an expression nor a channel is still written as a bare
|
|
10
|
+
* value, which is what every existing file on disk contains. The object form
|
|
11
|
+
* appears only when there is something to say beyond the value.
|
|
12
|
+
*/
|
|
13
|
+
export type CascadeInterpolation = "constant" | "linear" | "smooth";
|
|
14
|
+
/** One key. `frame` may be fractional. Interpolation governs the segment that
|
|
15
|
+
* LEAVES this key, and is omitted when it is the default (`smooth`). */
|
|
16
|
+
export interface CascadeSerializedKeyframe {
|
|
17
|
+
readonly frame: number;
|
|
18
|
+
readonly value: number;
|
|
19
|
+
readonly interpolation?: CascadeInterpolation;
|
|
20
|
+
}
|
|
21
|
+
/** Keys are written in frame order and hold at most one key per frame. A
|
|
22
|
+
* reader must tolerate neither being true and normalise. */
|
|
23
|
+
export interface CascadeSerializedChannel {
|
|
24
|
+
readonly keys: readonly CascadeSerializedKeyframe[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The object form of a prop. Written when the parameter carries an expression,
|
|
28
|
+
* a channel, or both.
|
|
29
|
+
*
|
|
30
|
+
* Both is legal, and the resolution order is fixed: **the channel wins and the
|
|
31
|
+
* expression is kept but inert.** Discarding an author's expression because
|
|
32
|
+
* they keyed the parameter is the worse failure of the two — the expression is
|
|
33
|
+
* still there to go back to, and deleting the channel restores it.
|
|
34
|
+
*/
|
|
35
|
+
export interface CascadeSerializedProp {
|
|
36
|
+
readonly value: unknown;
|
|
37
|
+
readonly expression?: string;
|
|
38
|
+
readonly channel?: CascadeSerializedChannel;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=animation.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation as it appears in a document.
|
|
3
|
+
*
|
|
4
|
+
* A parameter can be a literal, an expression, or a keyframe channel. All
|
|
5
|
+
* three serialise in one place — the node's `props` map — because a channel
|
|
6
|
+
* that lived in a timeline object would need a registry of targets, and that
|
|
7
|
+
* registry goes stale the moment a node is renamed.
|
|
8
|
+
*
|
|
9
|
+
* A prop with neither an expression nor a channel is still written as a bare
|
|
10
|
+
* value, which is what every existing file on disk contains. The object form
|
|
11
|
+
* appears only when there is something to say beyond the value.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=animation.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AssetRef, ImageRef, JsonValue
|
|
1
|
+
import type { AssetRef, ImageRef, JsonValue } from "./values.js";
|
|
2
2
|
export interface CascadeAbortSignal {
|
|
3
3
|
readonly aborted: boolean;
|
|
4
4
|
readonly reason?: unknown;
|
|
@@ -66,10 +66,73 @@ export interface PythonCapability {
|
|
|
66
66
|
progress: ProgressReporter;
|
|
67
67
|
}): Promise<JsonValue | AssetRef | ImageRef>;
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
/**
|
|
70
|
+
* A GPU a node can compute on, and the state that has to outlive one cook.
|
|
71
|
+
*
|
|
72
|
+
* This replaces `WebGLCapability`, which nothing implemented, nothing declared
|
|
73
|
+
* and no host installed — its one method was WebGL2-shaped, and keeping both
|
|
74
|
+
* names would have meant two devices in one page. Two devices cannot exchange
|
|
75
|
+
* a texture: an allocation made on one is not bindable, sampleable or copyable
|
|
76
|
+
* on the other, and the failure is a validation error at bind time rather than
|
|
77
|
+
* anything a graph author would recognise. So a shared device is not a
|
|
78
|
+
* preference, it is the precondition for a chain of GPU nodes at all.
|
|
79
|
+
*
|
|
80
|
+
* **Stage 0 deliberately carries three members and no textures.** Ports still
|
|
81
|
+
* pass `image`, so every picture is byte-identical to before; what this buys is
|
|
82
|
+
* one device instead of one per node module, an owner for `device.lost`, and
|
|
83
|
+
* somewhere legitimate to keep a pipeline — which is what lets the
|
|
84
|
+
* `architecture/ambient-state` rule stay strict. The texture store, the
|
|
85
|
+
* generation-stamped handles and the readback are Stage 1, and the shape of
|
|
86
|
+
* this interface is meant to be lived with for a few days first.
|
|
87
|
+
*
|
|
88
|
+
* The `GPUDevice` type comes from `@webgpu/types`, added to this package's
|
|
89
|
+
* `tsconfig.json` as its only `types` entry. Contracts compiles with
|
|
90
|
+
* `lib: ["ES2022"]` and no DOM on purpose — it is the vocabulary both hosts
|
|
91
|
+
* share, so pulling in the whole browser would let a browser-only type reach a
|
|
92
|
+
* portable definition by accident. A hand-written stub was the alternative and
|
|
93
|
+
* would have been worse than a dependency: it would shadow the genuine
|
|
94
|
+
* `GPUDevice` at the node's own call site, so every real method call would
|
|
95
|
+
* stop typechecking.
|
|
96
|
+
*
|
|
97
|
+
* The device is raw on purpose. A wrapped pipeline API would be a second thing
|
|
98
|
+
* to learn, would lag the spec, and would fight the reason to reach for WebGPU
|
|
99
|
+
* at all, which is that you write WGSL and encode passes. The capability wraps
|
|
100
|
+
* only what carries lifetime or identity, because those are the parts a host
|
|
101
|
+
* has to be able to account for.
|
|
102
|
+
*/
|
|
103
|
+
export interface GpuCapability {
|
|
104
|
+
/**
|
|
105
|
+
* The one device for this page. Shared by every node declaring `gpu`, which
|
|
106
|
+
* is the whole point — see the note above on why two would be useless.
|
|
107
|
+
*/
|
|
108
|
+
readonly device: GPUDevice;
|
|
109
|
+
/**
|
|
110
|
+
* Who is actually rendering, recorded so a difference between two runs of
|
|
111
|
+
* the same graph is attributable rather than a mystery. WebGPU output can
|
|
112
|
+
* differ across adapters, and today there is no way to answer "which one".
|
|
113
|
+
*/
|
|
114
|
+
readonly adapterInfo: Readonly<{
|
|
115
|
+
vendor: string;
|
|
116
|
+
architecture: string;
|
|
117
|
+
device: string;
|
|
118
|
+
description: string;
|
|
119
|
+
}>;
|
|
120
|
+
/** What this adapter will actually allow — `maxTextureDimension3D` and the
|
|
121
|
+
* rest. Read at runtime rather than assumed; the 3D default is far below
|
|
122
|
+
* the 2D one and a hard-coded guess would be wrong on some machines. */
|
|
123
|
+
readonly limits: Readonly<Record<string, number>>;
|
|
124
|
+
/**
|
|
125
|
+
* A resource that outlives one cook — a compiled pipeline, a sampler, a
|
|
126
|
+
* bind group layout. Keyed per node by the host, so two instances of one
|
|
127
|
+
* module do not share a cache entry and a module-level `Map` is never the
|
|
128
|
+
* right answer again.
|
|
129
|
+
*
|
|
130
|
+
* `create` runs once per key. `destroy` is called when the entry is dropped:
|
|
131
|
+
* when the device is lost, or when the graph disposes. This is the member
|
|
132
|
+
* that makes `architecture/module-state` keepable — it was previously
|
|
133
|
+
* forbidding the only available way to hold a pipeline.
|
|
134
|
+
*/
|
|
135
|
+
cache<T>(key: string, create: (device: GPUDevice) => T, destroy?: (value: T) => void): T;
|
|
73
136
|
}
|
|
74
137
|
export interface ShellRunResult {
|
|
75
138
|
readonly stdout: string;
|
|
@@ -102,7 +165,7 @@ export interface CapabilityMap {
|
|
|
102
165
|
readonly assets: AssetCapability;
|
|
103
166
|
readonly media: MediaCapability;
|
|
104
167
|
readonly python: PythonCapability;
|
|
105
|
-
readonly
|
|
168
|
+
readonly gpu: GpuCapability;
|
|
106
169
|
readonly shell: ShellCapability;
|
|
107
170
|
}
|
|
108
171
|
export type NodeCapabilityName = keyof CapabilityMap;
|
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
import type { CapabilityMap, CascadeAbortSignal, NodeCapabilityName, ProgressReporter } from "./capabilities.js";
|
|
2
2
|
import type { CascadeType, CoreType, JsonValue, NamespacedType, SerializableValueForType, ValueForType } from "./values.js";
|
|
3
3
|
export type RuntimeEnvironment = "portable" | "browser" | "server";
|
|
4
|
-
export type CapabilityForEnvironment<E extends RuntimeEnvironment> = E extends "portable" ? "assets" | "media" : E extends "browser" ? "assets" | "media" | "
|
|
4
|
+
export type CapabilityForEnvironment<E extends RuntimeEnvironment> = E extends "portable" ? "assets" | "media" : E extends "browser" ? "assets" | "media" | "gpu" : "files" | "assets" | "media" | "python" | "shell";
|
|
5
5
|
export type DataTypeForEnvironment<E extends RuntimeEnvironment> = E extends "browser" ? CascadeType : Exclude<CascadeType, "texture">;
|
|
6
6
|
type ControlFor<T extends CascadeType> = T extends "float" ? "number" | "slider" | "range" : T extends "int" ? "int" | "slider" | "range" : T extends "bool" ? "boolean" : T extends "string" ? "text" | "textarea" | "select" : T extends "vec2" | "vec3" | "vec4" | "vec2i" | "vec3i" | "vec4i" ? "vector" : T extends "mat2" | "mat3" | "mat4" ? "matrix" : T extends "color" ? "color" : T extends "image" ? "image" : T extends "asset" ? "asset" | "select" : T extends "texture" ? never : "select";
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* `min`, `max` and `step`, for the types whose components are numbers.
|
|
9
|
+
*
|
|
10
|
+
* The vectors are included, and were not: they typed all three as `never`, so
|
|
11
|
+
* the six vector types could not carry a range at all. Nothing exercised it
|
|
12
|
+
* because no builtin vec prop has one — but Marcus's standing rule is that
|
|
13
|
+
* anything with an x and a y is one `vec2` rather than two floats, and two
|
|
14
|
+
* floats *can* carry a range while the `vec2` they became could not. So
|
|
15
|
+
* following the convention cost the Inspector its clamp and its drag
|
|
16
|
+
* granularity every time: `field-io-gradient-logo.offset` lost −1..1 step
|
|
17
|
+
* 0.005, `offset.offset` lost −2..2, `ripple.centre` lost −1..2, and every
|
|
18
|
+
* remaining vector candidate across the sketches carries a range too.
|
|
19
|
+
*
|
|
20
|
+
* One range for all components, not one per component. A per-component tuple
|
|
21
|
+
* is a real design question — an `x` and a `y` with different limits is a
|
|
22
|
+
* legitimate thing to want — but it is a second shape for this field and every
|
|
23
|
+
* reader would have to handle both, so it waits for a case that needs it
|
|
24
|
+
* rather than being guessed at now.
|
|
25
|
+
*
|
|
26
|
+
* Matrices stay excluded. Their control is a grid of components with an
|
|
27
|
+
* identity button, and a range across a transform's cells is not a thing
|
|
28
|
+
* anybody has asked for.
|
|
29
|
+
*/
|
|
30
|
+
type NumericMetadata<T extends CascadeType> = T extends "float" | "int" | "vec2" | "vec3" | "vec4" | "vec2i" | "vec3i" | "vec4i" ? {
|
|
8
31
|
readonly min?: number;
|
|
9
32
|
readonly max?: number;
|
|
10
33
|
readonly step?: number;
|
|
@@ -57,6 +80,26 @@ export type PropDefinition<T extends PropDataType> = {
|
|
|
57
80
|
readonly default: SerializableValueForType<T>;
|
|
58
81
|
readonly label?: string;
|
|
59
82
|
readonly description?: string;
|
|
83
|
+
/**
|
|
84
|
+
* An expression the prop starts with, so a node can come out of the box
|
|
85
|
+
* already moving — `sine-oscillator` and `ripple` baked in `$T` as dynamic
|
|
86
|
+
* nodes and shipped a static 0 once converted, which is what this restores.
|
|
87
|
+
* Marcus's decision, 2026-09-08, asked as a taste question about his own
|
|
88
|
+
* tool: a node like that should self-animate when dropped.
|
|
89
|
+
*
|
|
90
|
+
* **It is a default, so it loses to a stored value like any other.** A
|
|
91
|
+
* document that saved a plain number for this prop keeps that number and
|
|
92
|
+
* does not silently start animating on load. That is the mirror of the
|
|
93
|
+
* dropped-`params` fault: whatever the author set has to win, and the test
|
|
94
|
+
* for this asserts the losing case rather than the winning one.
|
|
95
|
+
*
|
|
96
|
+
* Applied only when the document stored nothing at all for the prop. It is a
|
|
97
|
+
* real expression once applied, not a hidden fallback — visible in the
|
|
98
|
+
* Inspector, editable, and deletable, which is how a stored expression
|
|
99
|
+
* already reads and the only version that does not lie about where the
|
|
100
|
+
* number came from.
|
|
101
|
+
*/
|
|
102
|
+
readonly expression?: string;
|
|
60
103
|
} & NumericMetadata<T> & AcceptMetadata<T> & SelectMetadata<T>;
|
|
61
104
|
export type AnyPropDefinition = {
|
|
62
105
|
[T in PropDataType]: PropDefinition<T>;
|
|
@@ -100,6 +143,21 @@ type OutputValue<O> = O extends TriggerOutputDefinition ? {
|
|
|
100
143
|
} : never;
|
|
101
144
|
export type DeclaredCapabilities<D extends NodeDefinition> = Readonly<Pick<CapabilityMap, D["capabilities"] extends readonly NodeCapabilityName[] ? D["capabilities"][number] : never>>;
|
|
102
145
|
export interface NodeExecutionContext<D extends NodeDefinition> {
|
|
146
|
+
/**
|
|
147
|
+
* This node instance's id, stable for the life of the graph.
|
|
148
|
+
*
|
|
149
|
+
* It exists for one reason: naming a scratch file so that two instances of
|
|
150
|
+
* the same module do not overwrite each other. `field-logo` runs one module
|
|
151
|
+
* three times at three sizes, and `cloud-plots` runs `height-field` twice
|
|
152
|
+
* with opposite mode toggles — a literal path silently loses one of each
|
|
153
|
+
* pair. Pass it to `cachePath(context.nodeId, '.png')` and nothing else.
|
|
154
|
+
*
|
|
155
|
+
* An id is deliberately all a node gets. It names a namespace, not a
|
|
156
|
+
* position: there is no way to reach a parent, a sibling or the graph
|
|
157
|
+
* through it, which is what keeps a definition-v1 node a pure function of
|
|
158
|
+
* its own inputs. Do not parse it.
|
|
159
|
+
*/
|
|
160
|
+
readonly nodeId: string;
|
|
103
161
|
readonly inputs: Readonly<{
|
|
104
162
|
[K in keyof Entries<D, "inputs">]: InputValue<Entries<D, "inputs">[K]>;
|
|
105
163
|
}>;
|
|
@@ -10,6 +10,21 @@ export interface CascadeDocumentNode {
|
|
|
10
10
|
value?: unknown;
|
|
11
11
|
}>[] | Readonly<Record<string, unknown>>;
|
|
12
12
|
readonly props?: Readonly<Record<string, unknown>>;
|
|
13
|
+
/**
|
|
14
|
+
* The dynamic engine's stored parameter values, and dead weight here.
|
|
15
|
+
*
|
|
16
|
+
* The deterministic runtime reads `props` and nothing else, so a document
|
|
17
|
+
* that still carries `params` loses every value in it and reports nothing —
|
|
18
|
+
* a converted sketch silently reverts to its defaults and draws something
|
|
19
|
+
* different. Declared so that the runtime can recognise the key and warn
|
|
20
|
+
* about it, which is the only reason it appears in a v1 type.
|
|
21
|
+
*
|
|
22
|
+
* @deprecated Migrate to `props`.
|
|
23
|
+
*/
|
|
24
|
+
readonly params?: readonly Readonly<{
|
|
25
|
+
name: string;
|
|
26
|
+
value?: unknown;
|
|
27
|
+
}>[] | Readonly<Record<string, unknown>>;
|
|
13
28
|
}
|
|
14
29
|
export interface CascadeDocumentAnnotation {
|
|
15
30
|
readonly id: string;
|
package/dist/contracts/index.js
CHANGED
package/dist/contracts/schema.js
CHANGED
|
@@ -41,10 +41,13 @@ const dataProperties = {
|
|
|
41
41
|
const data = object(dataProperties, ["kind", "type"]);
|
|
42
42
|
const output = object({ kind: dataProperties.kind, type: dataProperties.type, description }, ["kind", "type"]);
|
|
43
43
|
const { kind: _kind, variadic: _variadic, ...propProperties } = dataProperties;
|
|
44
|
-
const prop = object({
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
44
|
+
const prop = object({
|
|
45
|
+
...propProperties,
|
|
46
|
+
label: { type: "string" },
|
|
47
|
+
// Props only, not inputs: an input's value comes from whatever is wired to
|
|
48
|
+
// it, so a default expression there would be describing the wrong thing.
|
|
49
|
+
expression: { type: "string", minLength: 1 },
|
|
50
|
+
}, ["type", "default"]);
|
|
48
51
|
function environmentCapabilities(runsOn, capabilities) {
|
|
49
52
|
return {
|
|
50
53
|
if: { properties: { runsOn: { const: runsOn } } },
|
|
@@ -72,7 +75,7 @@ export const nodeDefinitionSchema = {
|
|
|
72
75
|
}, ["apiVersion", "runsOn"]),
|
|
73
76
|
allOf: [
|
|
74
77
|
environmentCapabilities("portable", ["assets", "media"]),
|
|
75
|
-
environmentCapabilities("browser", ["assets", "media", "
|
|
78
|
+
environmentCapabilities("browser", ["assets", "media", "gpu"]),
|
|
76
79
|
environmentCapabilities("server", [
|
|
77
80
|
"files",
|
|
78
81
|
"assets",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NodeCapabilityName } from "./capabilities.js";
|
|
2
|
+
import type { NodeDefinition, RuntimeEnvironment } from "./definition.js";
|
|
2
3
|
export interface Diagnostic {
|
|
3
4
|
readonly phase: string;
|
|
4
5
|
readonly code: string;
|
|
@@ -8,6 +9,14 @@ export interface Diagnostic {
|
|
|
8
9
|
readonly line?: number;
|
|
9
10
|
readonly column?: number;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* The capabilities a definition may *declare* for each environment. Declaring
|
|
14
|
+
* one is not the same as a host installing it — `MediaCapability` is declared
|
|
15
|
+
* here and implemented by nobody — so anything deciding whether a graph can
|
|
16
|
+
* actually run must ask the host, and use this only to classify which host a
|
|
17
|
+
* capability belongs to.
|
|
18
|
+
*/
|
|
19
|
+
export declare const CAPABILITIES_BY_ENVIRONMENT: Record<RuntimeEnvironment, readonly NodeCapabilityName[]>;
|
|
11
20
|
export declare function validateNodeDefinition(value: unknown): readonly Diagnostic[];
|
|
12
21
|
export declare function assertNodeDefinition(value: unknown): asserts value is NodeDefinition;
|
|
13
22
|
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -5,16 +5,24 @@ const NAMESPACED = /^[a-z][a-z0-9-]*(?:\.[A-Za-z][A-Za-z0-9_]*)+$/;
|
|
|
5
5
|
const RESERVED = new Set(["__proto__", "prototype", "constructor"]);
|
|
6
6
|
const CORE = new Set(CORE_TYPES);
|
|
7
7
|
const CONTROLS = new Set(["number", "slider", "range", "int", "boolean", "text", "textarea", "select", "vector", "matrix", "color", "image", "asset"]);
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The capabilities a definition may *declare* for each environment. Declaring
|
|
10
|
+
* one is not the same as a host installing it — `MediaCapability` is declared
|
|
11
|
+
* here and implemented by nobody — so anything deciding whether a graph can
|
|
12
|
+
* actually run must ask the host, and use this only to classify which host a
|
|
13
|
+
* capability belongs to.
|
|
14
|
+
*/
|
|
15
|
+
export const CAPABILITIES_BY_ENVIRONMENT = {
|
|
9
16
|
portable: ["assets", "media"],
|
|
10
|
-
browser: ["assets", "media", "
|
|
17
|
+
browser: ["assets", "media", "gpu"],
|
|
11
18
|
server: ["files", "assets", "media", "python", "shell"],
|
|
12
19
|
};
|
|
20
|
+
const ALLOWED = CAPABILITIES_BY_ENVIRONMENT;
|
|
13
21
|
const fields = (names) => new Set(names.split(" "));
|
|
14
22
|
const TOP_FIELDS = fields("apiVersion runsOn container capabilities label description icon inputs outputs props");
|
|
15
23
|
const DATA_FIELDS = fields("kind type default description variadic min max step accept control options");
|
|
16
24
|
const OUTPUT_FIELDS = fields("kind type description");
|
|
17
|
-
const PROP_FIELDS = fields("type default label description min max step accept control options");
|
|
25
|
+
const PROP_FIELDS = fields("type default label description min max step accept control options expression");
|
|
18
26
|
function diagnostic(code, message, path) {
|
|
19
27
|
return { phase: "definition", code: `definition/${code}`, message, path };
|
|
20
28
|
}
|
|
@@ -186,6 +194,15 @@ export function validateNodeDefinition(value) {
|
|
|
186
194
|
!Number.isFinite(raw.step) ||
|
|
187
195
|
raw.step <= 0))
|
|
188
196
|
add("invalid-step", "step must be greater than zero", `${path}.step`);
|
|
197
|
+
if (section === "props" &&
|
|
198
|
+
"expression" in raw &&
|
|
199
|
+
(typeof raw.expression !== "string" || raw.expression.length === 0))
|
|
200
|
+
add("invalid-expression", "expression must be a non-empty string", `${path}.expression`);
|
|
201
|
+
for (const field of ["min", "max"]) {
|
|
202
|
+
if (field in raw &&
|
|
203
|
+
(typeof raw[field] !== "number" || !Number.isFinite(raw[field])))
|
|
204
|
+
add(`invalid-${field}`, `${field} must be a finite number`, `${path}.${field}`);
|
|
205
|
+
}
|
|
189
206
|
if (typeof raw.min === "number" &&
|
|
190
207
|
typeof raw.max === "number" &&
|
|
191
208
|
raw.min > raw.max)
|