@needle-tools/addressing 0.3.3 → 0.3.4
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 +2 -2
- package/dist/address.d.ts +19 -4
- package/dist/address.js +32 -12
- package/dist/gltf.d.ts +5 -28
- package/dist/gltf.js +5 -44
- package/dist/id.d.ts +60 -0
- package/dist/id.js +107 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/resolve.d.ts +5 -5
- package/dist/resolve.js +22 -20
- package/dist/target.d.ts +29 -0
- package/dist/target.js +112 -0
- package/package.json +1 -1
- package/src/address.ts +46 -13
- package/src/gltf.ts +5 -63
- package/src/id.ts +141 -1
- package/src/index.ts +14 -2
- package/src/resolve.ts +24 -22
- package/src/target.ts +126 -0
package/src/target.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { NeedleAddress } from "./address.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The canonical STRING form of a document target — the ONE print/parse pair
|
|
5
|
+
* for `{ ref?, address? }` (plans/one-address-vocabulary.md: one vocabulary;
|
|
6
|
+
* this is a second wire FORM of it, never a second dialect — no other code
|
|
7
|
+
* may format or parse these tokens).
|
|
8
|
+
*
|
|
9
|
+
* Grammar (rungs optional, fixed order):
|
|
10
|
+
*
|
|
11
|
+
* "@" [ref] [ "/" [type ":"] [id] ["#" anchor] ["~" name] ["!" path] ]
|
|
12
|
+
*
|
|
13
|
+
* - `@` leads every token — the RETIRED session grammars ("<uuid>/<path>"
|
|
14
|
+
* handles, bare hierarchy ids) can never half-parse as a target; a stale
|
|
15
|
+
* token fails loud.
|
|
16
|
+
* - the `node` type is implicit (omitted on print, re-minted on parse) —
|
|
17
|
+
* the canonical form normalizes a type-less address to `type: "node"`.
|
|
18
|
+
* - ref/id/anchor/name are token-escaped; the PATH rung is last and
|
|
19
|
+
* verbatim (it is already the stored escaped form from joinPath and may
|
|
20
|
+
* contain any character).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export type AddressTarget = { ref?: string; address?: NeedleAddress };
|
|
24
|
+
|
|
25
|
+
// the six characters the grammar reserves (+ "%" as the escape lead) — encoded
|
|
26
|
+
// with %xx so decodeURIComponent inverts; everything else stays raw and human-
|
|
27
|
+
// readable ("$scene" must print as "$scene", not "%24scene")
|
|
28
|
+
function encodeToken(value: string): string {
|
|
29
|
+
return value
|
|
30
|
+
.replace(/%/g, "%25")
|
|
31
|
+
.replace(/\//g, "%2F")
|
|
32
|
+
.replace(/:/g, "%3A")
|
|
33
|
+
.replace(/#/g, "%23")
|
|
34
|
+
.replace(/~/g, "%7E")
|
|
35
|
+
.replace(/!/g, "%21")
|
|
36
|
+
.replace(/@/g, "%40");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function decodeToken(value: string): string | null {
|
|
40
|
+
try {
|
|
41
|
+
return decodeURIComponent(value);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null; // a lone "%" — not a token we printed
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** print a target as its canonical token — null when there is nothing to name */
|
|
49
|
+
export function targetToString(target: AddressTarget): string | null {
|
|
50
|
+
const address = target.address;
|
|
51
|
+
const hasAddress = !!address && (address.id !== undefined || address.anchor !== undefined || address.name !== undefined || address.path !== undefined || address.type !== undefined);
|
|
52
|
+
if (!target.ref && !hasAddress) return null;
|
|
53
|
+
let token = `@${target.ref ? encodeToken(target.ref) : ""}`;
|
|
54
|
+
if (hasAddress) {
|
|
55
|
+
token += "/";
|
|
56
|
+
const type = address.type ?? "node";
|
|
57
|
+
if (type !== "node") token += `${encodeToken(type)}:`;
|
|
58
|
+
if (address.id !== undefined) token += encodeToken(address.id);
|
|
59
|
+
if (address.anchor !== undefined) token += `#${encodeToken(address.anchor)}`;
|
|
60
|
+
if (address.name !== undefined) token += `~${encodeToken(address.name)}`;
|
|
61
|
+
if (address.path !== undefined) token += `!${address.path}`;
|
|
62
|
+
}
|
|
63
|
+
return token;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** parse a canonical token back to the target — null for anything this module
|
|
67
|
+
* did not print (retired grammars, garbage): a loud miss, never a half-parse */
|
|
68
|
+
export function targetFromString(token: string): AddressTarget | null {
|
|
69
|
+
if (typeof token !== "string" || !token.startsWith("@") || token.length < 2) return null;
|
|
70
|
+
const body = token.slice(1);
|
|
71
|
+
const slash = body.indexOf("/");
|
|
72
|
+
const refRaw = slash === -1 ? body : body.slice(0, slash);
|
|
73
|
+
const addrRaw = slash === -1 ? undefined : body.slice(slash + 1);
|
|
74
|
+
const ref = refRaw.length > 0 ? decodeToken(refRaw) : undefined;
|
|
75
|
+
if (refRaw.length > 0 && ref === null) return null;
|
|
76
|
+
|
|
77
|
+
if (addrRaw === undefined || addrRaw.length === 0) {
|
|
78
|
+
return ref ? { ref } : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// consume rungs back to front: path (verbatim remainder), name, anchor
|
|
82
|
+
let rest = addrRaw;
|
|
83
|
+
let path: string | undefined;
|
|
84
|
+
const bang = rest.indexOf("!");
|
|
85
|
+
if (bang !== -1) {
|
|
86
|
+
path = rest.slice(bang + 1);
|
|
87
|
+
rest = rest.slice(0, bang);
|
|
88
|
+
}
|
|
89
|
+
let name: string | undefined;
|
|
90
|
+
const tilde = rest.indexOf("~");
|
|
91
|
+
if (tilde !== -1) {
|
|
92
|
+
const decoded = decodeToken(rest.slice(tilde + 1));
|
|
93
|
+
if (decoded === null) return null;
|
|
94
|
+
name = decoded;
|
|
95
|
+
rest = rest.slice(0, tilde);
|
|
96
|
+
}
|
|
97
|
+
let anchor: string | undefined;
|
|
98
|
+
const hash = rest.indexOf("#");
|
|
99
|
+
if (hash !== -1) {
|
|
100
|
+
const decoded = decodeToken(rest.slice(hash + 1));
|
|
101
|
+
if (decoded === null) return null;
|
|
102
|
+
anchor = decoded;
|
|
103
|
+
rest = rest.slice(0, hash);
|
|
104
|
+
}
|
|
105
|
+
let type = "node";
|
|
106
|
+
const colon = rest.indexOf(":");
|
|
107
|
+
if (colon !== -1) {
|
|
108
|
+
const decoded = decodeToken(rest.slice(0, colon));
|
|
109
|
+
if (decoded === null || decoded.length === 0) return null;
|
|
110
|
+
type = decoded;
|
|
111
|
+
rest = rest.slice(colon + 1);
|
|
112
|
+
}
|
|
113
|
+
let id: string | undefined;
|
|
114
|
+
if (rest.length > 0) {
|
|
115
|
+
const decoded = decodeToken(rest);
|
|
116
|
+
if (decoded === null) return null;
|
|
117
|
+
id = decoded;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const address: NeedleAddress = { type };
|
|
121
|
+
if (id !== undefined) address.id = id;
|
|
122
|
+
if (anchor !== undefined) address.anchor = anchor;
|
|
123
|
+
if (name !== undefined) address.name = name;
|
|
124
|
+
if (path !== undefined) address.path = path;
|
|
125
|
+
return ref ? { ref, address } : { address };
|
|
126
|
+
}
|