@needle-tools/addressing 0.3.4 → 0.4.0-1791982227.dramatic-tempura.8b3a85e

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.
Files changed (58) hide show
  1. package/Readme.md +151 -70
  2. package/dist/address.d.ts +5 -0
  3. package/dist/address.js +25 -3
  4. package/dist/document/assets.d.ts +27 -0
  5. package/dist/document/assets.js +75 -0
  6. package/dist/document/collectionSelector.d.ts +9 -0
  7. package/dist/document/collectionSelector.js +29 -0
  8. package/dist/document/evaluate.d.ts +86 -0
  9. package/dist/document/evaluate.js +1082 -0
  10. package/dist/document/index.d.ts +11 -0
  11. package/dist/document/index.js +11 -0
  12. package/dist/document/inspection.d.ts +30 -0
  13. package/dist/document/inspection.js +130 -0
  14. package/dist/document/loadedDocument.d.ts +18 -0
  15. package/dist/document/loadedDocument.js +35 -0
  16. package/dist/document/references.d.ts +7 -0
  17. package/dist/document/references.js +36 -0
  18. package/dist/document/runtime.d.ts +119 -0
  19. package/dist/document/runtime.js +1 -0
  20. package/dist/document/types.d.ts +240 -0
  21. package/dist/document/types.js +43 -0
  22. package/dist/document/validation.d.ts +36 -0
  23. package/dist/document/validation.js +222 -0
  24. package/dist/document/valueCodecs.d.ts +20 -0
  25. package/dist/document/valueCodecs.js +3 -0
  26. package/dist/document/write.d.ts +31 -0
  27. package/dist/document/write.js +131 -0
  28. package/dist/id.d.ts +2 -0
  29. package/dist/id.js +4 -0
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.js +2 -1
  32. package/dist/loadedGltf.d.ts +32 -0
  33. package/dist/loadedGltf.js +62 -0
  34. package/dist/resolve.js +8 -6
  35. package/package.json +10 -2
  36. package/src/address.ts +169 -144
  37. package/src/anchor.ts +71 -71
  38. package/src/document/assets.ts +77 -0
  39. package/src/document/collectionSelector.ts +37 -0
  40. package/src/document/evaluate.ts +1111 -0
  41. package/src/document/index.ts +11 -0
  42. package/src/document/inspection.ts +123 -0
  43. package/src/document/loadedDocument.ts +48 -0
  44. package/src/document/references.ts +41 -0
  45. package/src/document/runtime.ts +113 -0
  46. package/src/document/schema.json +293 -0
  47. package/src/document/types.ts +265 -0
  48. package/src/document/validation.ts +226 -0
  49. package/src/document/valueCodecs.ts +23 -0
  50. package/src/document/write.ts +132 -0
  51. package/src/gltf.ts +33 -33
  52. package/src/hash.ts +58 -58
  53. package/src/id.ts +206 -201
  54. package/src/index.ts +49 -46
  55. package/src/loadedGltf.ts +93 -0
  56. package/src/resolve.ts +319 -317
  57. package/src/target.ts +126 -126
  58. package/src/uuid.ts +48 -48
@@ -0,0 +1,11 @@
1
+ export * from "./types.js";
2
+ export * from "./runtime.js";
3
+ export * from "./evaluate.js";
4
+ export * from "./inspection.js";
5
+ export * from "./validation.js";
6
+ export * from "./write.js";
7
+ export * from "./assets.js";
8
+ export * from "./collectionSelector.js";
9
+ export * from "./references.js";
10
+ export * from './valueCodecs.js';
11
+ export * from './loadedDocument.js';
@@ -0,0 +1,11 @@
1
+ export * from "./types.js";
2
+ export * from "./runtime.js";
3
+ export * from "./evaluate.js";
4
+ export * from "./inspection.js";
5
+ export * from "./validation.js";
6
+ export * from "./write.js";
7
+ export * from "./assets.js";
8
+ export * from "./collectionSelector.js";
9
+ export * from "./references.js";
10
+ export * from './valueCodecs.js';
11
+ export * from './loadedDocument.js';
@@ -0,0 +1,30 @@
1
+ import { type GraphNode, type NeedleAddress } from "../index.js";
2
+ export type ProjectInspectionTarget = {
3
+ ref: string;
4
+ address?: NeedleAddress;
5
+ pathPrefix?: string;
6
+ };
7
+ type Node = GraphNode & {
8
+ parent?: Node | null;
9
+ isObject3D?: boolean;
10
+ };
11
+ /** Reference membership is captured at placement, before parent overs move nodes. */
12
+ export declare class ProjectDocumentInspection {
13
+ private readonly scene;
14
+ private readonly scopes;
15
+ private readonly targets;
16
+ ready: Promise<void>;
17
+ constructor(scene: Node);
18
+ registerReference(ref: string, root: object, elements?: Iterable<object>, referenceRoots?: ReadonlySet<object>): void;
19
+ resolve(target: ProjectInspectionTarget): object | null;
20
+ targetFor(object: object): ProjectInspectionTarget | null;
21
+ private inScene;
22
+ }
23
+ /** Opt in before loading a document; ordinary playback allocates no inspection index. */
24
+ export declare function enableProjectDocumentInspection(): {
25
+ dispose(): void;
26
+ };
27
+ export declare function createProjectDocumentInspection(scene: Node): ProjectDocumentInspection | undefined;
28
+ export declare function getProjectDocumentInspection(context: object): ProjectDocumentInspection | undefined;
29
+ export declare function registerProjectDocumentInspection(context: object, inspection: ProjectDocumentInspection): void;
30
+ export {};
@@ -0,0 +1,130 @@
1
+ import { resolveDocumentCollectionElement } from "./collectionSelector.js";
2
+ import { joinPath, parseAnchorValue, resolveInCollection, resolveInGraph, splitPath } from "../index.js";
3
+ /** Reference membership is captured at placement, before parent overs move nodes. */
4
+ export class ProjectDocumentInspection {
5
+ constructor(scene) {
6
+ this.scene = scene;
7
+ this.scopes = new Map();
8
+ this.targets = new WeakMap();
9
+ this.ready = Promise.resolve();
10
+ }
11
+ registerReference(ref, root, elements = [], referenceRoots) {
12
+ const nodes = new Map();
13
+ const scope = { root: new WeakRef(root), nodes, elements: [] };
14
+ const capture = (node, path) => {
15
+ const weak = new WeakRef(node);
16
+ const graph = {
17
+ get name() { return weak.deref()?.name; },
18
+ get userData() { return weak.deref()?.userData; },
19
+ children: node.children?.filter(child => !referenceRoots?.has(child))
20
+ .map(child => capture(child, [...path, child.name ?? ""])),
21
+ };
22
+ nodes.set(graph, weak);
23
+ const id = node.userData?.["needle-id"];
24
+ const anchor = parseAnchorValue(node.name);
25
+ const address = node === root ? undefined : {
26
+ type: "node", path: joinPath(path), ...(typeof id === "string" ? { id } : {}), ...(anchor ? { anchor } : {}),
27
+ };
28
+ if (node === root || address)
29
+ this.targets.set(node, { ref, ...(address ? { address } : {}) });
30
+ return graph;
31
+ };
32
+ if (root.isObject3D)
33
+ scope.graph = capture(root, []);
34
+ else
35
+ this.targets.set(root, { ref });
36
+ for (const element of elements) {
37
+ const value = element;
38
+ const type = value.isMaterial ? "material" : value.isTexture ? "texture" : value.isAnimationClip ? "animation" : null;
39
+ if (!type)
40
+ continue;
41
+ scope.elements.push(new WeakRef(element));
42
+ const id = value.userData?.["needle-id"];
43
+ if (typeof id === "string")
44
+ this.targets.set(element, { ref, address: { type, id, name: value.name } });
45
+ }
46
+ this.scopes.set(ref, scope);
47
+ }
48
+ resolve(target) {
49
+ const scope = this.scopes.get(target.ref);
50
+ let object = target.ref === "$scene" ? this.scene : scope?.root.deref() ?? null;
51
+ if (target.address) {
52
+ const address = target.address;
53
+ if (address.type && address.type !== "node") {
54
+ const elements = scope?.elements.flatMap(weak => {
55
+ const value = weak.deref();
56
+ const matches = address.type === "material" ? value?.isMaterial : address.type === "texture" ? value?.isTexture : address.type === "animation" ? value?.isAnimationClip : false;
57
+ return value && matches ? [value] : [];
58
+ }) ?? [];
59
+ object = resolveInCollection(address, elements);
60
+ }
61
+ else if (scope?.graph) {
62
+ const graph = resolveInGraph(address, scope.graph);
63
+ object = graph ? scope.nodes.get(graph)?.deref() ?? null : null;
64
+ if (object && !this.inScene(object))
65
+ object = null;
66
+ }
67
+ else
68
+ object = target.ref === "$scene" ? resolveInGraph(address, this.scene) : null;
69
+ }
70
+ if (!object)
71
+ return null;
72
+ if (object.isObject3D && !this.inScene(object))
73
+ return null;
74
+ if (target.pathPrefix) {
75
+ const parts = splitPath(target.pathPrefix.replace(/\/$/, ""));
76
+ for (const part of parts) {
77
+ if (part === "components")
78
+ object = object.userData?.components ?? null;
79
+ else if (Array.isArray(object) && part.startsWith("guid:")) {
80
+ const guid = part.slice(5);
81
+ object = resolveDocumentCollectionElement(object, `guid:${guid}`) ?? null;
82
+ }
83
+ else
84
+ return null;
85
+ if (!object)
86
+ return null;
87
+ }
88
+ }
89
+ return object;
90
+ }
91
+ targetFor(object) {
92
+ if (object === this.scene)
93
+ return { ref: "$scene" };
94
+ const component = object;
95
+ const guid = component.needle_editor_guid || component.guid;
96
+ if (component.gameObject && guid) {
97
+ const host = this.targetFor(component.gameObject);
98
+ return host ? { ...host, pathPrefix: `components/guid:${guid}/` } : null;
99
+ }
100
+ const target = this.targets.get(object);
101
+ return target && this.resolve(target) === object ? structuredClone(target) : null;
102
+ }
103
+ inScene(object) {
104
+ // Liveness only; reference ownership comes from the placement scope above.
105
+ for (let node = object; node; node = node.parent)
106
+ if (node === this.scene)
107
+ return true;
108
+ return false;
109
+ }
110
+ }
111
+ const inspections = new WeakMap();
112
+ let observers = 0;
113
+ /** Opt in before loading a document; ordinary playback allocates no inspection index. */
114
+ export function enableProjectDocumentInspection() {
115
+ observers++;
116
+ let disposed = false;
117
+ return { dispose() { if (!disposed) {
118
+ disposed = true;
119
+ observers--;
120
+ } } };
121
+ }
122
+ export function createProjectDocumentInspection(scene) {
123
+ return observers ? new ProjectDocumentInspection(scene) : undefined;
124
+ }
125
+ export function getProjectDocumentInspection(context) {
126
+ return inspections.get(context);
127
+ }
128
+ export function registerProjectDocumentInspection(context, inspection) {
129
+ inspections.set(context, inspection);
130
+ }
@@ -0,0 +1,18 @@
1
+ import { type ProjectInspectionTarget } from './inspection.js';
2
+ import type { DocumentModel, DocumentNode, DocumentRuntime } from './runtime.js';
3
+ import type { EvaluateProjectDocumentOptions, ProjectDocument, ProjectEvaluationIssue } from './types.js';
4
+ export type ReadonlyDocument<T> = T extends object ? {
5
+ readonly [K in keyof T]: ReadonlyDocument<T[K]>;
6
+ } : T;
7
+ /** Evaluated document data and live address resolution, independent of the host runtime. */
8
+ export interface LoadedProjectDocument {
9
+ readonly document: ReadonlyDocument<ProjectDocument>;
10
+ readonly root: DocumentNode;
11
+ readonly references: ReadonlyMap<string, DocumentNode>;
12
+ readonly models: ReadonlyMap<string, DocumentModel>;
13
+ readonly issues: readonly ProjectEvaluationIssue[];
14
+ resolve(target: ProjectInspectionTarget): object | null;
15
+ addressOf(object: object): ProjectInspectionTarget | null;
16
+ }
17
+ /** The caller owns fetching, cancellation, rendering and disposal of runtime resources. */
18
+ export declare function loadProjectDocument(runtime: DocumentRuntime, document: ProjectDocument, options?: EvaluateProjectDocumentOptions): Promise<LoadedProjectDocument>;
@@ -0,0 +1,35 @@
1
+ import { evaluateProjectDocument } from './evaluate.js';
2
+ import { enableProjectDocumentInspection, getProjectDocumentInspection } from './inspection.js';
3
+ /** The caller owns fetching, cancellation, rendering and disposal of runtime resources. */
4
+ export async function loadProjectDocument(runtime, document, options) {
5
+ const snapshot = structuredClone(document);
6
+ const observation = enableProjectDocumentInspection();
7
+ try {
8
+ const result = await evaluateProjectDocument(runtime, snapshot, options);
9
+ await result.completed;
10
+ const index = getProjectDocumentInspection(runtime.inspectionOwner ?? runtime);
11
+ if (!index)
12
+ throw new Error('Document address index is unavailable');
13
+ await index.ready;
14
+ freezeDocument(snapshot);
15
+ return {
16
+ document: snapshot,
17
+ root: result.root,
18
+ references: new Map(result.references),
19
+ models: new Map(result.models),
20
+ issues: [...result.issues],
21
+ resolve: target => index.resolve(target),
22
+ addressOf: object => index.targetFor(object),
23
+ };
24
+ }
25
+ finally {
26
+ observation.dispose();
27
+ }
28
+ }
29
+ function freezeDocument(value) {
30
+ if (!value || typeof value !== 'object' || Object.isFrozen(value))
31
+ return;
32
+ for (const child of Object.values(value))
33
+ freezeDocument(child);
34
+ Object.freeze(value);
35
+ }
@@ -0,0 +1,7 @@
1
+ import type { ProjectDocumentTarget } from './types.js';
2
+ /** Undefined means ordinary data; null means a recognized reference whose target is missing. */
3
+ export type DocumentNodeReferenceResolver = (target: ProjectDocumentTarget & {
4
+ ref: string;
5
+ }) => object | null | undefined;
6
+ /** Copy-on-write resolution shared by document loading and component snapshot restoration. */
7
+ export declare function resolveDocumentNodeReferences(value: unknown, resolve: DocumentNodeReferenceResolver, issue: (arc: 'def', message: string) => void): unknown;
@@ -0,0 +1,36 @@
1
+ /** Copy-on-write resolution shared by document loading and component snapshot restoration. */
2
+ export function resolveDocumentNodeReferences(value, resolve, issue) {
3
+ if (!value || typeof value !== 'object')
4
+ return value;
5
+ if (Array.isArray(value)) {
6
+ let copy = null;
7
+ for (let i = 0; i < value.length; i++) {
8
+ const resolved = resolveDocumentNodeReferences(value[i], resolve, issue);
9
+ if (resolved !== value[i]) {
10
+ if (!copy)
11
+ copy = [...value];
12
+ copy[i] = resolved;
13
+ }
14
+ }
15
+ return copy ?? value;
16
+ }
17
+ if (typeof value.ref === 'string') {
18
+ const resolved = resolve(value);
19
+ if (resolved === null) {
20
+ issue('def', `node reference did not resolve: ${JSON.stringify(value)}`);
21
+ return undefined;
22
+ }
23
+ if (resolved !== undefined)
24
+ return resolved;
25
+ }
26
+ let copy = null;
27
+ for (const [key, entry] of Object.entries(value)) {
28
+ const resolved = resolveDocumentNodeReferences(entry, resolve, issue);
29
+ if (resolved !== entry) {
30
+ if (!copy)
31
+ copy = { ...value };
32
+ copy[key] = resolved;
33
+ }
34
+ }
35
+ return copy ?? value;
36
+ }
@@ -0,0 +1,119 @@
1
+ import type { GraphNode } from "../address.js";
2
+ import type { GltfAssociation } from "../loadedGltf.js";
3
+ import type { ProjectEvaluationIssue } from "./types.js";
4
+ /** Converts runtime vectors and rotations to and from document arrays. */
5
+ export interface DocumentVector {
6
+ fromArray(value: number[]): unknown;
7
+ toArray(): number[];
8
+ }
9
+ /** The scene operations used by composition, independent of a three.js build. */
10
+ export interface DocumentNode extends GraphNode {
11
+ name: string;
12
+ isObject3D: boolean;
13
+ userData: Record<string, unknown>;
14
+ parent: DocumentNode | null;
15
+ children: DocumentNode[];
16
+ position: DocumentVector;
17
+ quaternion: DocumentVector;
18
+ scale: DocumentVector;
19
+ add(...nodes: DocumentNode[]): unknown;
20
+ removeFromParent(): unknown;
21
+ traverse(visitor: (node: DocumentNode) => void): void;
22
+ }
23
+ /** Loaded asset scenes and metadata used to resolve document targets. */
24
+ export interface DocumentModel {
25
+ /** Default scene used as the template for document references. */
26
+ scene?: DocumentNode;
27
+ /** Loaded scenes used when attaching stable node IDs from glTF metadata. */
28
+ scenes?: DocumentNode[];
29
+ /** Loaded clips available for address resolution; loading does not play them. */
30
+ animations?: object[];
31
+ /** Optional glTF loader metadata used to resolve asset objects and materials. */
32
+ parser?: {
33
+ /** Source glTF data used for stable node IDs and material lookup. */
34
+ json?: Record<string, unknown> & {
35
+ materials?: Array<{
36
+ name?: string;
37
+ }>;
38
+ };
39
+ /** Maps loaded objects to their source glTF indices. */
40
+ associations?: Map<object, GltfAssociation>;
41
+ /** Loads a source resource by kind and index; used here for materials. */
42
+ getDependency?(type: string, index: number): Promise<unknown>;
43
+ };
44
+ }
45
+ /** Reports which document operation could not be applied and why. */
46
+ export type DocumentIssueReporter = (arc: ProjectEvaluationIssue["arc"], message: string) => void;
47
+ /** Scene and component hooks used while applying document definitions. */
48
+ export interface DocumentDefContext {
49
+ /** Runtime scene available to definition readers. */
50
+ scene?: DocumentNode;
51
+ /** Creates an unattached node for a definition's `create` entry. */
52
+ createNode(): DocumentNode;
53
+ /** Legacy reader for component definitions without an extension owner. */
54
+ applyComponent?(node: DocumentNode, data: {
55
+ name: string;
56
+ } & Record<string, unknown>, issue: (arc: "def", message: string) => void): void;
57
+ /** Readers for owned definitions, keyed by document extension ID. */
58
+ readonly extensions?: ReadonlyMap<string, DocumentExtensionProvider>;
59
+ }
60
+ /** Reads one extension's saved data using the declared version and migrations. */
61
+ export interface DocumentExtensionProvider {
62
+ /** Current saved-data version understood by this reader. */
63
+ readonly version: number;
64
+ /** Typed property values supported by this extension version. */
65
+ readonly valueCodecs?: Readonly<Record<string, import('./valueCodecs.js').DocumentValueCodec>>;
66
+ /** Source version to migration function; each function produces the current format. */
67
+ readonly migrations?: Readonly<Record<number, (data: Record<string, unknown>) => Record<string, unknown>>>;
68
+ /** Omit when this extension cannot restore component data. */
69
+ readonly components?: {
70
+ /** Applies one component's resolved data to its target node. */
71
+ apply(node: DocumentNode, data: {
72
+ name: string;
73
+ } & Record<string, unknown>, issue: (arc: "def", message: string) => void): void;
74
+ };
75
+ }
76
+ /** Host operations used by the shared evaluator to load assets and apply document data. */
77
+ export interface DocumentRuntime {
78
+ /** Default parent for composed document content. */
79
+ scene: DocumentNode;
80
+ /** Host renderer targeted by renderer property overrides. */
81
+ renderer: object;
82
+ /** Creates an unattached scene node. */
83
+ createNode(): DocumentNode;
84
+ /** Converts document color channels into the host's color value. */
85
+ createColor(value: number[]): object;
86
+ /** Parses asset data, using `src` as its identity and relative-resource base. */
87
+ parse(data: ArrayBuffer | string, src: string, options?: {
88
+ resolveUrl(url: string): string;
89
+ }): Promise<DocumentModel | null>;
90
+ /** Creates a reference instance from a loaded template. */
91
+ clone(template: DocumentNode, referenceId: string): DocumentNode | null;
92
+ /** Keeps a loaded template inactive while reference instances are composed. */
93
+ parkTemplate(node: DocumentNode): void;
94
+ /** Enables a placed reference instance through the host's lifecycle. */
95
+ activateInstance(node: DocumentNode): void;
96
+ /** Releases a template after cloning without destroying its placed instances. */
97
+ disposeTemplate(node: DocumentNode): void;
98
+ /** Run after template deserialization, before any instance activates its components. */
99
+ schedule(work: () => void): void;
100
+ /** Loads the host's environment resource for a document environment URL. */
101
+ loadEnvironment(url: string, options: {
102
+ format?: "pmrem";
103
+ name?: string;
104
+ }): Promise<object | null>;
105
+ /** Binds definition readers to the composed root and document URL. */
106
+ createDefContext?(root: DocumentNode, baseUrl?: string): DocumentDefContext;
107
+ /** Lists components attached to a node for component-target resolution. */
108
+ getComponents?(node: DocumentNode): object[];
109
+ /** Matches a component against its saved type name. */
110
+ matchesComponentType?(component: object, name: string): boolean;
111
+ /** Matches a component against its saved stable ID. */
112
+ matchesComponentGuid?(component: object, guid: string): boolean;
113
+ /** Derives an instance component ID from its reference and source-file IDs. */
114
+ deriveComponentGuid?(referenceId: string, fileGuid: string): string;
115
+ /** Removes a component through the host's lifecycle; owner may be unresolved. */
116
+ removeComponent?(component: object, owner: DocumentNode | null): void;
117
+ /** Identity used by the optional inspection registry; defaults to the runtime itself. */
118
+ inspectionOwner?: object;
119
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,240 @@
1
+ import type { NeedleAddress } from "../address.js";
2
+ import type { DocumentNode, DocumentModel } from "./runtime.js";
3
+ /** the version discriminator key every project document must carry */
4
+ export declare const PROJECT_DOCUMENT_KEY = "needle:project";
5
+ export type ProjectDocumentAsset = {
6
+ /** where the asset lives — resolved against `EvaluateProjectDocumentOptions.baseUrl` */
7
+ src: string;
8
+ /**
9
+ * Optional PROVIDER IDENTITY for assets whose resources (textures, buffers) live at
10
+ * provider-specific urls that `src` alone can't reach (e.g. PolyHaven). The host recovers
11
+ * the provider's resolver from this identity at load time (see
12
+ * `EvaluateProjectDocumentOptions.resolveAssetUrl`). Self-contained assets omit it and
13
+ * load by `src`. The engine ignores it; only the host (editor) reads it.
14
+ */
15
+ asset?: {
16
+ id: string;
17
+ provider?: string;
18
+ };
19
+ /**
20
+ * Optional SEMANTIC KIND, decided at USE time by the writer ("model", "material",
21
+ * "hdr", "texture", "image", "video", "audio", "code", "text") — the same file can
22
+ * be a texture on a material slot or a plain image in UI, and downstream
23
+ * processing (compression at publish) follows the use, not the extension. The
24
+ * engine ignores it; only hosts/tooling read it.
25
+ */
26
+ kind?: string;
27
+ /**
28
+ * INFORMATIONAL: the server `ETag` observed when this document captured the
29
+ * asset — lets a future reader detect "the upstream file changed since".
30
+ * Never used for loading decisions; the engine ignores it.
31
+ */
32
+ etag?: string;
33
+ };
34
+ export type ProjectDocumentReference = {
35
+ /** project-local id of this reference — the resolution scope (or entity handle) for targets */
36
+ id: string;
37
+ /** key into the document's `assets` map */
38
+ asset: string;
39
+ /**
40
+ * ELEMENT REFERENCE discriminator: when present, this reference instantiates the
41
+ * addressed element INSIDE the asset (e.g. a material) into the composition's ENTITY
42
+ * POOL instead of placing a scene subtree. Entity references are first-class composed
43
+ * entities: overs can target them (`{ ref: <id>, path: "map/repeat" }`) and values can
44
+ * point at them (`{ ref: <id> }` — e.g. a mesh's material assignment). N consumers of
45
+ * the same entity share ONE runtime instance by construction (USD: a Material-prim
46
+ * reference; assignment ≈ material:binding).
47
+ */
48
+ address?: NeedleAddress;
49
+ /** @deprecated (2026-08-03, read-only shim — remove soon) replaced by parent OVERS
50
+ * (`{ target: { ref, path: "parent" }, value: { ref | "$scene", address?, index? } }`)
51
+ * days after shipping; only unreleased documents from that week carry this field.
52
+ * The writer no longer emits it; the evaluator still honors it at placement. */
53
+ parent?: {
54
+ ref: string;
55
+ address?: NeedleAddress;
56
+ };
57
+ };
58
+ /** A host-owned root that document opinions compose onto instead of instantiating. */
59
+ export type ProjectDocumentBinding = {
60
+ /** Document-local reference used by targets. `$`-prefixed ids are reserved. */
61
+ id: string;
62
+ /** The kind of host root required by this document. */
63
+ kind: "scene";
64
+ /** Stable structural identity used by the host to match this root before applying edits. */
65
+ signature: string;
66
+ /** Informational label for diagnostics only; never used as identity. */
67
+ name?: string;
68
+ /** Existing asset-reference ids whose instances belong to this host root. */
69
+ references?: string[];
70
+ };
71
+ export type ProjectDocumentTarget = {
72
+ /** which placed reference the address resolves WITHIN; omitted → the composition root */
73
+ ref?: string;
74
+ /** fat address of the element inside the reference (needle-id / anchor / path) */
75
+ address?: NeedleAddress;
76
+ /**
77
+ * Property path within the resolved element, "/"-separated.
78
+ *
79
+ * A segment landing on a COLLECTION may be a `<field>:<value>` qualifier instead of an
80
+ * index — `components/type:DragControls/enabled`, `components/guid:6f1f…/enabled` —
81
+ * so a document never depends on array order (`userData.components` reorders whenever
82
+ * a component is added or removed). A bare numeric segment on an array is still an
83
+ * index, but it is now an explicit opt-in rather than the only option.
84
+ * `components` is a synthesized segment on a node: the live layout
85
+ * (`userData.components`) stays an implementation detail.
86
+ *
87
+ * Reserved LAST segment: `active` — with `value: false` it removes whatever the rest
88
+ * of the path resolved to (the node itself, or a component) from the composed result.
89
+ */
90
+ path?: string;
91
+ };
92
+ export type ProjectDocumentDef = {
93
+ target: ProjectDocumentTarget;
94
+ /** Owner of all component entries in this def; omitted for legacy data. */
95
+ extension?: string;
96
+ /**
97
+ * When present, the def CREATES a new node (USD-proper: def defines prims) under the
98
+ * resolved target (composition root when the target is empty) and the components
99
+ * apply to IT — e.g. the runtime start camera. Without `create`, components apply to
100
+ * the resolved existing node.
101
+ */
102
+ create?: {
103
+ id?: string;
104
+ name?: string;
105
+ position?: number[];
106
+ quaternion?: number[];
107
+ scale?: number[];
108
+ /** Recreate a hierarchy duplicate from its stable source and seed. */
109
+ copy?: {
110
+ source: ProjectDocumentTarget;
111
+ seed: string;
112
+ reference?: boolean;
113
+ } & ({
114
+ batch: string;
115
+ index: number;
116
+ } | {
117
+ batch?: never;
118
+ index?: never;
119
+ });
120
+ };
121
+ /** VERBATIM NEEDLE_components wire entries ({ name, guid, ...fields }) */
122
+ components?: Array<{
123
+ name: string;
124
+ } & Record<string, unknown>>;
125
+ };
126
+ export type ProjectDocumentOver = {
127
+ target: ProjectDocumentTarget;
128
+ value: unknown;
129
+ };
130
+ /** Saved-data version required from an extension, independent of its package version. */
131
+ export type ProjectDocumentExtension = {
132
+ version: number;
133
+ };
134
+ export type ProjectDocument = {
135
+ [PROJECT_DOCUMENT_KEY]: string;
136
+ /** layer metadata (USD's "layer metadata" concept — e.g. `name`); free-form */
137
+ metadata?: Record<string, unknown>;
138
+ /** Required document extensions, not installed packages or runtime dependencies. */
139
+ extensions?: Record<string, ProjectDocumentExtension>;
140
+ /** named remote origins for `remote://<name>/<path>` srcs — the scheme
141
+ * routes, the AUTHORITY names the remote, this table supplies the base.
142
+ * Resolved per DOCUMENT (a nested document resolves with ITS table), so
143
+ * the document works wherever it travels. Semantics mirror the editor's
144
+ * remoteSrc.ts — keep the two in sync. */
145
+ remotes?: Record<string, string>;
146
+ assets?: Record<string, ProjectDocumentAsset>;
147
+ /** Required host-owned roots. The host resolves these before evaluation. */
148
+ bindings?: ProjectDocumentBinding[];
149
+ references?: ProjectDocumentReference[];
150
+ defs?: ProjectDocumentDef[];
151
+ overs?: ProjectDocumentOver[];
152
+ /** unknown arcs are ignored by evaluation and must be preserved by writers */
153
+ [arc: string]: unknown;
154
+ };
155
+ /**
156
+ * Resolve a `remote://name/path` src via the document's remotes table
157
+ * (`overrides` win for known names — the host's per-environment repoint, e.g.
158
+ * the editor's dev proxy). Non-remote srcs pass through. An UNKNOWN remote
159
+ * name resolves to null — the caller reports it; guessing a base would load
160
+ * the wrong bytes silently. MIRRORS the editor's remoteSrc.ts resolveRemoteSrc.
161
+ */
162
+ export declare function resolveDocumentRemoteSrc(src: string, remotes: Record<string, string> | undefined, overrides?: Record<string, string>): string | null;
163
+ /** structural check for the version discriminator — the opener/sniffing entry point */
164
+ export declare function isProjectDocument(value: unknown): value is ProjectDocument;
165
+ export type ProjectEvaluationIssue = {
166
+ arc: "reference" | "def" | "over";
167
+ message: string;
168
+ };
169
+ /**
170
+ * Report evaluation issues to the console — in PRODUCTION too. Every issue
171
+ * means the document asked for something the runtime could not provide (a
172
+ * dropped def, an unresolved over, a failed asset), i.e. the loaded scene
173
+ * silently differs from what the author saved. The author debugs on the
174
+ * deployed page: gating this behind dev/?debugproject cost a full day of
175
+ * blind hunting when a deployed scene lost a component with a clean console
176
+ * (2026-09-01). A healthy document reports nothing.
177
+ */
178
+ export declare function reportProjectIssues(issues: readonly ProjectEvaluationIssue[], warn?: (message: string) => void): void;
179
+ export type ProjectEvaluationResult = {
180
+ /** the object all non-mounted references were added to */
181
+ root: DocumentNode;
182
+ /** Placed reference roots by reference id. Populated during the host's scheduled
183
+ * composition phase, after template deserialization; await `completed` first. */
184
+ references: Map<string, DocumentNode>;
185
+ /** per-reference Model views by reference id (`scene` is that reference's placed
186
+ * INSTANCE; parser etc. come from the shared template). Populated at `completed`,
187
+ * like `references`. */
188
+ models: Map<string, DocumentModel>;
189
+ /**
190
+ * misses and errors — an issue never aborts evaluation, the arc is skipped.
191
+ * Populated up to `completed` (defs/overs apply in the host's composition phase).
192
+ */
193
+ issues: ProjectEvaluationIssue[];
194
+ /**
195
+ * Resolves once defs/overs have been applied. Hosts schedule composition after
196
+ * asset deserialization and before component activation. Needle's adapter uses
197
+ * its pre-setup phase, requiring a context tick; a vanilla host can run immediately.
198
+ */
199
+ completed: Promise<void>;
200
+ };
201
+ export type EvaluateProjectDocumentOptions = {
202
+ /** Restore components on existing nodes without loading assets or replaying structural edits. */
203
+ componentRecovery?: {
204
+ readonly signal: AbortSignal;
205
+ readonly resolveTarget: (target: ProjectDocumentTarget) => DocumentNode | null;
206
+ };
207
+ /** Resolve extension readers after loading, in the runtime's pre-activation context. */
208
+ extensions?: (context: import('./runtime.js').DocumentDefContext) => ReadonlyMap<string, import('./runtime.js').DocumentExtensionProvider>;
209
+ /** Reuse existing roots as named resolution scopes. Bindings may supply external
210
+ * host roots that have no asset reference in the document. When supplied, every
211
+ * declared scene reference must also be bound; a missing binding is reported
212
+ * instead of loading another instance. */
213
+ referenceBindings?: ReadonlyMap<string, DocumentNode>;
214
+ /** where references are placed; defaults to `context.scene` */
215
+ parent?: DocumentNode;
216
+ /** resolves `assets.src` for the document — tests/tools provide in-memory data here */
217
+ resolveAsset?: (assetId: string, src: string) => Promise<ArrayBuffer | string>;
218
+ /** base for resolving relative `assets.src` urls with the default (fetch) resolver */
219
+ baseUrl?: string;
220
+ /** per-environment overrides for the document's `remotes` table (known
221
+ * names only — the editor maps needle-cloud to its dev proxy); the
222
+ * document's own table is the default */
223
+ remotes?: Record<string, string>;
224
+ /**
225
+ * Rewrites a resource url (texture / external buffer) encountered while parsing an
226
+ * asset — the asset service's `resolveUrl` re-entering at load time. Needed for provider
227
+ * assets (e.g. PolyHaven) whose textures live at urls that plain relative resolution
228
+ * against `assets.src` cannot reach. Passed to the host's parse operation;
229
+ * the editor opener supplies it from `IAssetsService` so this
230
+ * evaluator carries NO provider knowledge.
231
+ */
232
+ resolveAssetUrl?: (assetId: string, url: string) => string;
233
+ /**
234
+ * AGGREGATE download progress across the document's assets: `loaded` bytes summed
235
+ * over all asset downloads, `total` summed over the known Content-Lengths (0 while
236
+ * nothing reported a length). Fired per received chunk — the loader chain adapts it
237
+ * to the ProgressEvent shape the loading UI expects.
238
+ */
239
+ onProgress?: (loaded: number, total: number) => void;
240
+ };