@needle-tools/addressing 0.1.0 → 0.1.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/Readme.md +66 -89
- package/package.json +1 -1
- package/src/anchor.ts +1 -16
package/Readme.md
CHANGED
|
@@ -1,89 +1,66 @@
|
|
|
1
|
-
# @needle-tools/addressing
|
|
2
|
-
|
|
3
|
-
Persistent addressing for
|
|
4
|
-
`#needle` anchor scheme, and address resolution
|
|
5
|
-
|
|
6
|
-
Zero runtime dependencies, no three.js
|
|
7
|
-
in browsers, node, and workers.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// capture an address while the target is known
|
|
68
|
-
const address = createObjectAddress(sceneRoot, targetObject);
|
|
69
|
-
|
|
70
|
-
// later / elsewhere: resolve — or reconcile when the asset changed
|
|
71
|
-
const hit = resolveInGraph(address, sceneRoot);
|
|
72
|
-
const candidates = hit ? [] : reconcileInGraph(address, sceneRoot);
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Out of scope by design: GLB container parsing (callers own their binary handling — this
|
|
76
|
-
package takes parsed JSON), file I/O, session-scoped editor refs, and any composition/
|
|
77
|
-
override semantics (those live in the project format, one layer above).
|
|
78
|
-
|
|
79
|
-
## Development
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
npm install
|
|
83
|
-
npm test # vitest — includes byte-parity golden tests
|
|
84
|
-
npm run build # tsc → dist/
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Regenerating `test/fixtures/golden.json` requires the reference libraries
|
|
88
|
-
(`js-md5`, `uuid`) and a verbatim copy of needle-cloud's `ensureUUID` — see the
|
|
89
|
-
fixture header comment in `test/id.test.ts`.
|
|
1
|
+
# @needle-tools/addressing
|
|
2
|
+
|
|
3
|
+
Persistent addressing for glTF assets and three.js scene graphs: deterministic
|
|
4
|
+
`needle-id` computation, the `#needle` name-anchor scheme, and address resolution.
|
|
5
|
+
|
|
6
|
+
Zero runtime dependencies, no three.js import (scene graphs are accessed structurally),
|
|
7
|
+
usable in browsers, node, and workers.
|
|
8
|
+
|
|
9
|
+
## Ids
|
|
10
|
+
|
|
11
|
+
Every glTF root-collection element (nodes, scenes, animations, cameras, materials,
|
|
12
|
+
meshes, skins, textures, images, samplers) can carry a UUID in `extras["needle-id"]`:
|
|
13
|
+
|
|
14
|
+
- **Anchored** — the element name ends with `#needle` (case-insensitive): the id derives
|
|
15
|
+
only from the text before the marker → it **survives content changes and re-exports**
|
|
16
|
+
as long as the name stays.
|
|
17
|
+
- **Unanchored**: the id derives from the element's type, name, index and a fingerprint
|
|
18
|
+
of the whole file → stable per asset *version* (same-named elements in different files
|
|
19
|
+
never collide).
|
|
20
|
+
|
|
21
|
+
`ensureIds(gltfJson)` stamps a parsed glTF idempotently — existing ids are never
|
|
22
|
+
overwritten, and stamping the same bytes always mints the same ids, no matter which tool
|
|
23
|
+
does it.
|
|
24
|
+
|
|
25
|
+
## Addresses
|
|
26
|
+
|
|
27
|
+
An address captures every fallback at creation time, while the target is known —
|
|
28
|
+
`{ id, anchor?, type?, name?, namePath? }`. Resolution order is strict:
|
|
29
|
+
|
|
30
|
+
1. `id` — exact match (`extras` in glTF JSON, `userData` in live scene graphs)
|
|
31
|
+
2. `anchor` — survives asset updates
|
|
32
|
+
3. `namePath` — best-effort structural match
|
|
33
|
+
4. **reconciliation** — never automatic: `reconcileInGraph` returns ranked candidates
|
|
34
|
+
with confidence + reasons; the caller decides thresholds and whether to re-anchor.
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import {
|
|
40
|
+
ensureIds, computeElementId, computeFileHash, // write side
|
|
41
|
+
createGltfElementAddress, createObjectAddress, // address records
|
|
42
|
+
resolveInGltf, resolveInGraph, reconcileInGraph, // resolve side
|
|
43
|
+
parseAnchorValue, randomUuid, NEEDLE_ID_KEY,
|
|
44
|
+
} from "@needle-tools/addressing";
|
|
45
|
+
|
|
46
|
+
// stamp a parsed glTF (idempotent, never overwrites existing ids)
|
|
47
|
+
const { fileHash, assigned } = ensureIds(gltfJson);
|
|
48
|
+
|
|
49
|
+
// capture an address while the target is known
|
|
50
|
+
const address = createObjectAddress(sceneRoot, targetObject);
|
|
51
|
+
|
|
52
|
+
// later / elsewhere: resolve — or reconcile when the asset changed
|
|
53
|
+
const hit = resolveInGraph(address, sceneRoot);
|
|
54
|
+
const candidates = hit ? [] : reconcileInGraph(address, sceneRoot);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Out of scope by design: GLB container parsing (this package takes parsed JSON), file
|
|
58
|
+
I/O, and any composition/override semantics.
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
npm install
|
|
64
|
+
npm test # vitest — includes golden-fixture id tests
|
|
65
|
+
npm run build # tsc → dist/
|
|
66
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/addressing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Persistent addressing for Needle assets: deterministic needle-id computation, #needle anchors, and address resolution over glTF JSON and live scene graphs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
package/src/anchor.ts
CHANGED
|
@@ -37,9 +37,7 @@ export function parseAnchor(rawName: string | null | undefined): ParsedAnchor |
|
|
|
37
37
|
const lower = trimmed.toLowerCase();
|
|
38
38
|
for (const marker of byLengthDescending(ANCHOR_MARKERS)) {
|
|
39
39
|
if (!lower.endsWith(marker)) continue;
|
|
40
|
-
const value =
|
|
41
|
-
? legacyNeedleAnchorValue(rawName, trimmed)
|
|
42
|
-
: trimmed.substring(0, trimmed.length - marker.length).trim();
|
|
40
|
+
const value = trimmed.substring(0, trimmed.length - marker.length).trim();
|
|
43
41
|
return value.length > 0 ? { marker, value } : null;
|
|
44
42
|
}
|
|
45
43
|
return null;
|
|
@@ -53,19 +51,6 @@ export function parseAnchorValue(rawName: string | null | undefined): string | n
|
|
|
53
51
|
return parseAnchor(rawName)?.value ?? null;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
/**
|
|
57
|
-
* `#needle` value extraction, bug-compatible with needle-cloud's `ensureUUID`
|
|
58
|
-
* (`gltf.edit.ts`), which computes the cut index from the TRIMMED name but applies it to
|
|
59
|
-
* the RAW name: a name with leading whitespace yields a shifted (shorter) anchor value
|
|
60
|
-
* (`" Trim#NEEDLE"` → `"Tr"`). Anchored ids are the ids that must survive asset
|
|
61
|
-
* updates, so this quirk is frozen into the contract for THIS marker — fixing it would
|
|
62
|
-
* change every anchored id ever minted for such names. Future markers use the clean
|
|
63
|
-
* trimmed parsing in {@link parseAnchor} instead.
|
|
64
|
-
*/
|
|
65
|
-
function legacyNeedleAnchorValue(rawName: string, trimmed: string): string {
|
|
66
|
-
return rawName.substring(0, trimmed.length - ANCHOR_MARKER.length).trim();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
54
|
function byLengthDescending(markers: readonly string[]): readonly string[] {
|
|
70
55
|
return markers.length > 1 ? [...markers].sort((a, b) => b.length - a.length) : markers;
|
|
71
56
|
}
|