@nextera.one/anchor-routing-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/index.d.mts +131 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +826 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +773 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +36 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ANCHOR_ERROR_CODES: () => ANCHOR_ERROR_CODES,
|
|
24
|
+
ANCHOR_PATH_PREFIX: () => ANCHOR_PATH_PREFIX,
|
|
25
|
+
ANCHOR_SEPARATOR: () => ANCHOR_SEPARATOR,
|
|
26
|
+
ARG_SEPARATOR: () => ARG_SEPARATOR,
|
|
27
|
+
AnchorRouteError: () => AnchorRouteError,
|
|
28
|
+
DOT_SEPARATOR: () => DOT_SEPARATOR,
|
|
29
|
+
RESERVED_ANCHOR_CHARACTERS: () => RESERVED_ANCHOR_CHARACTERS,
|
|
30
|
+
SAFE_LITERAL_CHARACTER: () => SAFE_LITERAL_CHARACTER,
|
|
31
|
+
buildDotAnchor: () => buildDotAnchor,
|
|
32
|
+
buildIntentAnchor: () => buildIntentAnchor,
|
|
33
|
+
buildPageAnchor: () => buildPageAnchor,
|
|
34
|
+
buildParamAnchor: () => buildParamAnchor,
|
|
35
|
+
createDiagnostic: () => createDiagnostic,
|
|
36
|
+
decodeAnchorKey: () => decodeAnchorKey,
|
|
37
|
+
decodeAnchorToken: () => decodeAnchorToken,
|
|
38
|
+
escapeAnchorKey: () => escapeAnchorKey,
|
|
39
|
+
escapeAnchorValue: () => escapeAnchorValue,
|
|
40
|
+
isAnchorRoute: () => isAnchorRoute,
|
|
41
|
+
isAnchorRouteError: () => isAnchorRouteError,
|
|
42
|
+
isHexDigit: () => isHexDigit,
|
|
43
|
+
isReservedAnchorCharacter: () => isReservedAnchorCharacter,
|
|
44
|
+
normalizeAnchor: () => normalizeAnchor,
|
|
45
|
+
parseAnchor: () => parseAnchor,
|
|
46
|
+
resolveAnchor: () => resolveAnchor,
|
|
47
|
+
serializeAnchor: () => serializeAnchor,
|
|
48
|
+
validateAnchor: () => validateAnchor,
|
|
49
|
+
validateEncodedToken: () => validateEncodedToken
|
|
50
|
+
});
|
|
51
|
+
module.exports = __toCommonJS(index_exports);
|
|
52
|
+
|
|
53
|
+
// src/constants.ts
|
|
54
|
+
var ANCHOR_PATH_PREFIX = "/~";
|
|
55
|
+
var ARG_SEPARATOR = ";";
|
|
56
|
+
var ANCHOR_SEPARATOR = "~";
|
|
57
|
+
var DOT_SEPARATOR = ".";
|
|
58
|
+
var RESERVED_ANCHOR_CHARACTERS = ["/", "~", ";", ".", "=", "@", "#", "%"];
|
|
59
|
+
var reservedAnchorCharacterSet = new Set(RESERVED_ANCHOR_CHARACTERS);
|
|
60
|
+
var SAFE_LITERAL_CHARACTER = /^[A-Za-z0-9_-]$/;
|
|
61
|
+
function isReservedAnchorCharacter(character) {
|
|
62
|
+
return reservedAnchorCharacterSet.has(character);
|
|
63
|
+
}
|
|
64
|
+
function isHexDigit(character) {
|
|
65
|
+
return /^[0-9A-Fa-f]$/.test(character);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/errors.ts
|
|
69
|
+
var ANCHOR_ERROR_CODES = {
|
|
70
|
+
ANCHOR_PARSE_EMPTY_INPUT: "ANCHOR_PARSE_EMPTY_INPUT",
|
|
71
|
+
ANCHOR_PARSE_INVALID_FORMAT: "ANCHOR_PARSE_INVALID_FORMAT",
|
|
72
|
+
ANCHOR_PARSE_EMPTY_PAGE: "ANCHOR_PARSE_EMPTY_PAGE",
|
|
73
|
+
ANCHOR_PARSE_EMPTY_SECTION: "ANCHOR_PARSE_EMPTY_SECTION",
|
|
74
|
+
ANCHOR_PARSE_EMPTY_KEY: "ANCHOR_PARSE_EMPTY_KEY",
|
|
75
|
+
ANCHOR_PARSE_INVALID_DOT_KEY: "ANCHOR_PARSE_INVALID_DOT_KEY",
|
|
76
|
+
ANCHOR_PARSE_INVALID_INTENT_PAIR: "ANCHOR_PARSE_INVALID_INTENT_PAIR",
|
|
77
|
+
ANCHOR_PARSE_DUPLICATE_NAMED_ARG: "ANCHOR_PARSE_DUPLICATE_NAMED_ARG",
|
|
78
|
+
ANCHOR_ESCAPE_INVALID_SEQUENCE: "ANCHOR_ESCAPE_INVALID_SEQUENCE",
|
|
79
|
+
ANCHOR_VALIDATE_UNESCAPED_RESERVED_CHAR: "ANCHOR_VALIDATE_UNESCAPED_RESERVED_CHAR",
|
|
80
|
+
ANCHOR_VALIDATE_TOO_MANY_ARGS: "ANCHOR_VALIDATE_TOO_MANY_ARGS",
|
|
81
|
+
ANCHOR_RESOLVE_PAGE_NOT_FOUND: "ANCHOR_RESOLVE_PAGE_NOT_FOUND",
|
|
82
|
+
ANCHOR_RESOLVE_SECTION_NOT_FOUND: "ANCHOR_RESOLVE_SECTION_NOT_FOUND",
|
|
83
|
+
ANCHOR_RESOLVE_KEY_NOT_FOUND: "ANCHOR_RESOLVE_KEY_NOT_FOUND",
|
|
84
|
+
ANCHOR_RESOLVE_BINDING_MISSING_ARG: "ANCHOR_RESOLVE_BINDING_MISSING_ARG",
|
|
85
|
+
ANCHOR_RESOLVE_INVALID_NAMED_ARG: "ANCHOR_RESOLVE_INVALID_NAMED_ARG"
|
|
86
|
+
};
|
|
87
|
+
var AnchorRouteError = class extends Error {
|
|
88
|
+
code;
|
|
89
|
+
raw;
|
|
90
|
+
mode;
|
|
91
|
+
constructor(code, message, raw, mode) {
|
|
92
|
+
super(message);
|
|
93
|
+
this.name = "AnchorRouteError";
|
|
94
|
+
this.code = code;
|
|
95
|
+
this.raw = raw;
|
|
96
|
+
this.mode = mode;
|
|
97
|
+
}
|
|
98
|
+
toDiagnostic() {
|
|
99
|
+
return {
|
|
100
|
+
code: this.code,
|
|
101
|
+
message: this.message,
|
|
102
|
+
raw: this.raw,
|
|
103
|
+
mode: this.mode
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
function createDiagnostic(code, message, raw, mode) {
|
|
108
|
+
return { code, message, raw, mode };
|
|
109
|
+
}
|
|
110
|
+
function isAnchorRouteError(error) {
|
|
111
|
+
return error instanceof AnchorRouteError;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/escape.ts
|
|
115
|
+
var encoder = new TextEncoder();
|
|
116
|
+
function encodeCharacter(character) {
|
|
117
|
+
return Array.from(encoder.encode(character)).map((byte) => `%${byte.toString(16).toUpperCase().padStart(2, "0")}`).join("");
|
|
118
|
+
}
|
|
119
|
+
function escapeAnchorValue(input) {
|
|
120
|
+
const value = String(input ?? "");
|
|
121
|
+
let output = "";
|
|
122
|
+
for (const character of value) {
|
|
123
|
+
output += SAFE_LITERAL_CHARACTER.test(character) ? character : encodeCharacter(character);
|
|
124
|
+
}
|
|
125
|
+
return output;
|
|
126
|
+
}
|
|
127
|
+
function validateEncodedToken(rawToken, rawInput, mode) {
|
|
128
|
+
for (let index = 0; index < rawToken.length; index += 1) {
|
|
129
|
+
const character = rawToken[index];
|
|
130
|
+
if (character === "%") {
|
|
131
|
+
const high = rawToken[index + 1];
|
|
132
|
+
const low = rawToken[index + 2];
|
|
133
|
+
if (!high || !low || !isHexDigit(high) || !isHexDigit(low)) {
|
|
134
|
+
throw new AnchorRouteError(
|
|
135
|
+
ANCHOR_ERROR_CODES.ANCHOR_ESCAPE_INVALID_SEQUENCE,
|
|
136
|
+
`Invalid percent-escape sequence in "${rawToken}".`,
|
|
137
|
+
rawInput,
|
|
138
|
+
mode
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
index += 2;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (isReservedAnchorCharacter(character)) {
|
|
145
|
+
throw new AnchorRouteError(
|
|
146
|
+
ANCHOR_ERROR_CODES.ANCHOR_VALIDATE_UNESCAPED_RESERVED_CHAR,
|
|
147
|
+
`Reserved character "${character}" must be percent-encoded inside anchor values.`,
|
|
148
|
+
rawInput,
|
|
149
|
+
mode
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function decodeAnchorToken(rawToken, rawInput, mode) {
|
|
155
|
+
validateEncodedToken(rawToken, rawInput, mode);
|
|
156
|
+
try {
|
|
157
|
+
return decodeURIComponent(rawToken);
|
|
158
|
+
} catch {
|
|
159
|
+
throw new AnchorRouteError(
|
|
160
|
+
ANCHOR_ERROR_CODES.ANCHOR_ESCAPE_INVALID_SEQUENCE,
|
|
161
|
+
`Invalid percent-escape sequence in "${rawToken}".`,
|
|
162
|
+
rawInput,
|
|
163
|
+
mode
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function decodeAnchorKey(rawKey, rawInput, mode) {
|
|
168
|
+
if (!rawKey) {
|
|
169
|
+
throw new AnchorRouteError(
|
|
170
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_KEY,
|
|
171
|
+
"Anchor route key cannot be empty.",
|
|
172
|
+
rawInput,
|
|
173
|
+
mode
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const rawSegments = rawKey.split(DOT_SEPARATOR);
|
|
177
|
+
if (rawSegments.some((segment) => segment.length === 0)) {
|
|
178
|
+
throw new AnchorRouteError(
|
|
179
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_DOT_KEY,
|
|
180
|
+
`Dot key "${rawKey}" contains an empty path token.`,
|
|
181
|
+
rawInput,
|
|
182
|
+
mode
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const pathTokens = rawSegments.map((segment) => {
|
|
186
|
+
const decoded = decodeAnchorToken(segment, rawInput, mode);
|
|
187
|
+
if (decoded.includes(DOT_SEPARATOR)) {
|
|
188
|
+
throw new AnchorRouteError(
|
|
189
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_DOT_KEY,
|
|
190
|
+
`Dot key segment "${decoded}" must not decode to another dot separator.`,
|
|
191
|
+
rawInput,
|
|
192
|
+
mode
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
return decoded;
|
|
196
|
+
});
|
|
197
|
+
return {
|
|
198
|
+
key: pathTokens.join(DOT_SEPARATOR),
|
|
199
|
+
pathTokens
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function escapeAnchorKey(input) {
|
|
203
|
+
const rawKey = String(input ?? "").trim();
|
|
204
|
+
if (!rawKey) {
|
|
205
|
+
throw new AnchorRouteError(
|
|
206
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_KEY,
|
|
207
|
+
"Anchor route key cannot be empty.",
|
|
208
|
+
rawKey
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const segments = rawKey.split(DOT_SEPARATOR).map((segment) => segment.trim());
|
|
212
|
+
if (segments.some((segment) => !segment)) {
|
|
213
|
+
throw new AnchorRouteError(
|
|
214
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_DOT_KEY,
|
|
215
|
+
`Dot key "${rawKey}" contains an empty path token.`,
|
|
216
|
+
rawKey
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return segments.map((segment) => escapeAnchorValue(segment)).join(DOT_SEPARATOR);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/builders.ts
|
|
223
|
+
function requireValue(value, code, message) {
|
|
224
|
+
const trimmed = String(value ?? "").trim();
|
|
225
|
+
if (!trimmed) {
|
|
226
|
+
throw new AnchorRouteError(code, message, String(value ?? ""));
|
|
227
|
+
}
|
|
228
|
+
return trimmed;
|
|
229
|
+
}
|
|
230
|
+
function buildPageAnchor(page, section) {
|
|
231
|
+
const safePage = escapeAnchorValue(requireValue(page, ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_PAGE, "Page value cannot be empty."));
|
|
232
|
+
const safeSection = escapeAnchorValue(
|
|
233
|
+
requireValue(section, ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_SECTION, "Section value cannot be empty.")
|
|
234
|
+
);
|
|
235
|
+
return `/${safePage}${ANCHOR_SEPARATOR}${safeSection}`;
|
|
236
|
+
}
|
|
237
|
+
function buildParamAnchor(key, args = []) {
|
|
238
|
+
const safeKey = escapeAnchorKey(key);
|
|
239
|
+
const encodedArgs = args.map((argument) => escapeAnchorValue(String(argument ?? "")));
|
|
240
|
+
return `${ANCHOR_PATH_PREFIX}${safeKey}${encodedArgs.map((argument) => `${ARG_SEPARATOR}${argument}`).join("")}`;
|
|
241
|
+
}
|
|
242
|
+
function buildDotAnchor(tokens) {
|
|
243
|
+
if (!Array.isArray(tokens) || tokens.length === 0) {
|
|
244
|
+
throw new AnchorRouteError(
|
|
245
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_DOT_KEY,
|
|
246
|
+
"Dot case requires at least one token.",
|
|
247
|
+
String(tokens ?? "")
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
const key = tokens.map((token) => requireValue(token, ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_DOT_KEY, "Dot token cannot be empty.")).join(DOT_SEPARATOR);
|
|
251
|
+
return `${ANCHOR_PATH_PREFIX}${escapeAnchorKey(key)}`;
|
|
252
|
+
}
|
|
253
|
+
function buildIntentAnchor(key, namedArgs) {
|
|
254
|
+
const safeKey = escapeAnchorKey(key);
|
|
255
|
+
const pairs = Object.entries(namedArgs ?? {});
|
|
256
|
+
if (pairs.length === 0) {
|
|
257
|
+
return `${ANCHOR_PATH_PREFIX}${safeKey}`;
|
|
258
|
+
}
|
|
259
|
+
const encodedPairs = pairs.map(([name, value]) => {
|
|
260
|
+
const safeName = escapeAnchorValue(
|
|
261
|
+
requireValue(name, ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_INTENT_PAIR, "Intent argument name cannot be empty.")
|
|
262
|
+
);
|
|
263
|
+
const safeValue = escapeAnchorValue(
|
|
264
|
+
requireValue(value, ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_INTENT_PAIR, `Intent argument "${name}" cannot be empty.`)
|
|
265
|
+
);
|
|
266
|
+
return `${ARG_SEPARATOR}${safeName}${DOT_SEPARATOR}${safeValue}`;
|
|
267
|
+
});
|
|
268
|
+
return `${ANCHOR_PATH_PREFIX}${safeKey}${encodedPairs.join("")}`;
|
|
269
|
+
}
|
|
270
|
+
function serializeAnchor(instruction) {
|
|
271
|
+
switch (instruction.mode) {
|
|
272
|
+
case "page":
|
|
273
|
+
return buildPageAnchor(instruction.page ?? "", instruction.section ?? "");
|
|
274
|
+
case "param":
|
|
275
|
+
return buildParamAnchor(instruction.key ?? "", instruction.args ?? []);
|
|
276
|
+
case "dot":
|
|
277
|
+
return buildDotAnchor(instruction.pathTokens ?? (instruction.key ? instruction.key.split(DOT_SEPARATOR) : []));
|
|
278
|
+
case "intent":
|
|
279
|
+
return buildIntentAnchor(instruction.key ?? "", instruction.namedArgs ?? {});
|
|
280
|
+
default:
|
|
281
|
+
throw new AnchorRouteError(
|
|
282
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_FORMAT,
|
|
283
|
+
`Unsupported anchor route mode "${String(instruction.mode)}".`,
|
|
284
|
+
instruction.raw
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/normalize.ts
|
|
290
|
+
function normalizeAnchor(input) {
|
|
291
|
+
const raw = String(input ?? "").trim();
|
|
292
|
+
if (!raw) {
|
|
293
|
+
return "";
|
|
294
|
+
}
|
|
295
|
+
let normalized = raw;
|
|
296
|
+
if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(normalized)) {
|
|
297
|
+
try {
|
|
298
|
+
const url = new URL(normalized);
|
|
299
|
+
normalized = url.pathname || "/";
|
|
300
|
+
} catch {
|
|
301
|
+
normalized = raw;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const suffixIndex = normalized.search(/[?#]/);
|
|
305
|
+
if (suffixIndex >= 0) {
|
|
306
|
+
normalized = normalized.slice(0, suffixIndex);
|
|
307
|
+
}
|
|
308
|
+
if (!normalized.startsWith("/")) {
|
|
309
|
+
normalized = `/${normalized}`;
|
|
310
|
+
}
|
|
311
|
+
normalized = normalized.replace(/\/{2,}/g, "/");
|
|
312
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
313
|
+
normalized = normalized.slice(0, -1);
|
|
314
|
+
}
|
|
315
|
+
return normalized;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/parser.ts
|
|
319
|
+
function parsePageRoute(normalized) {
|
|
320
|
+
const body = normalized.slice(1);
|
|
321
|
+
if (body.includes("/")) {
|
|
322
|
+
throw new AnchorRouteError(
|
|
323
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_FORMAT,
|
|
324
|
+
"Page Case must use a single path segment in the form /page~section.",
|
|
325
|
+
normalized,
|
|
326
|
+
"page"
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const parts = body.split(ANCHOR_SEPARATOR);
|
|
330
|
+
if (parts.length !== 2) {
|
|
331
|
+
throw new AnchorRouteError(
|
|
332
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_FORMAT,
|
|
333
|
+
"Page Case must contain exactly one ~ separator.",
|
|
334
|
+
normalized,
|
|
335
|
+
"page"
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
const [rawPage, rawSection] = parts;
|
|
339
|
+
if (!rawPage) {
|
|
340
|
+
throw new AnchorRouteError(
|
|
341
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_PAGE,
|
|
342
|
+
"Page Case requires a non-empty page value.",
|
|
343
|
+
normalized,
|
|
344
|
+
"page"
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (!rawSection) {
|
|
348
|
+
throw new AnchorRouteError(
|
|
349
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_SECTION,
|
|
350
|
+
"Page Case requires a non-empty section value.",
|
|
351
|
+
normalized,
|
|
352
|
+
"page"
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
mode: "page",
|
|
357
|
+
raw: normalized,
|
|
358
|
+
page: decodeAnchorToken(rawPage, normalized, "page"),
|
|
359
|
+
section: decodeAnchorToken(rawSection, normalized, "page")
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function parseDotRoute(normalized, rawKey) {
|
|
363
|
+
const key = decodeAnchorKey(rawKey, normalized, "dot");
|
|
364
|
+
return {
|
|
365
|
+
mode: "dot",
|
|
366
|
+
raw: normalized,
|
|
367
|
+
key: key.key,
|
|
368
|
+
pathTokens: key.pathTokens
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function parseParamRoute(normalized, rawKey, payload) {
|
|
372
|
+
const key = decodeAnchorKey(rawKey, normalized, "param");
|
|
373
|
+
const args = payload.map((rawArgument) => {
|
|
374
|
+
if (!rawArgument) {
|
|
375
|
+
throw new AnchorRouteError(
|
|
376
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_FORMAT,
|
|
377
|
+
"Param Case does not allow empty positional arguments.",
|
|
378
|
+
normalized,
|
|
379
|
+
"param"
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
return decodeAnchorToken(rawArgument, normalized, "param");
|
|
383
|
+
});
|
|
384
|
+
return {
|
|
385
|
+
mode: "param",
|
|
386
|
+
raw: normalized,
|
|
387
|
+
key: key.key,
|
|
388
|
+
pathTokens: key.pathTokens,
|
|
389
|
+
args
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function parseIntentRoute(normalized, rawKey, payload) {
|
|
393
|
+
const key = decodeAnchorKey(rawKey, normalized, "intent");
|
|
394
|
+
const namedArgs = {};
|
|
395
|
+
for (const rawPair of payload) {
|
|
396
|
+
const separatorIndex = rawPair.indexOf(DOT_SEPARATOR);
|
|
397
|
+
if (separatorIndex <= 0 || separatorIndex === rawPair.length - 1) {
|
|
398
|
+
throw new AnchorRouteError(
|
|
399
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_INTENT_PAIR,
|
|
400
|
+
`Intent Case pair "${rawPair}" must use the form name.value.`,
|
|
401
|
+
normalized,
|
|
402
|
+
"intent"
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
const rawName = rawPair.slice(0, separatorIndex);
|
|
406
|
+
const rawValue = rawPair.slice(separatorIndex + 1);
|
|
407
|
+
const name = decodeAnchorToken(rawName, normalized, "intent");
|
|
408
|
+
const value = decodeAnchorToken(rawValue, normalized, "intent");
|
|
409
|
+
if (Object.prototype.hasOwnProperty.call(namedArgs, name)) {
|
|
410
|
+
throw new AnchorRouteError(
|
|
411
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_DUPLICATE_NAMED_ARG,
|
|
412
|
+
`Intent Case contains duplicate named argument "${name}".`,
|
|
413
|
+
normalized,
|
|
414
|
+
"intent"
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
namedArgs[name] = value;
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
mode: "intent",
|
|
421
|
+
raw: normalized,
|
|
422
|
+
key: key.key,
|
|
423
|
+
pathTokens: key.pathTokens,
|
|
424
|
+
namedArgs
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function parseAnchor(input) {
|
|
428
|
+
const normalized = normalizeAnchor(input);
|
|
429
|
+
if (!normalized) {
|
|
430
|
+
throw new AnchorRouteError(
|
|
431
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_INPUT,
|
|
432
|
+
"Anchor route input cannot be empty.",
|
|
433
|
+
String(input ?? "")
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (!normalized.startsWith(ANCHOR_PATH_PREFIX) && normalized.includes(ANCHOR_SEPARATOR)) {
|
|
437
|
+
return parsePageRoute(normalized);
|
|
438
|
+
}
|
|
439
|
+
if (!normalized.startsWith(ANCHOR_PATH_PREFIX)) {
|
|
440
|
+
throw new AnchorRouteError(
|
|
441
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_FORMAT,
|
|
442
|
+
"Input is not an Anchor Route.",
|
|
443
|
+
normalized
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
const capsuleBody = normalized.slice(ANCHOR_PATH_PREFIX.length);
|
|
447
|
+
if (!capsuleBody || capsuleBody.includes("/")) {
|
|
448
|
+
throw new AnchorRouteError(
|
|
449
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_FORMAT,
|
|
450
|
+
"Capsule cases must use the form /~key or /~key;payload.",
|
|
451
|
+
normalized
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const [rawKey, ...payload] = capsuleBody.split(ARG_SEPARATOR);
|
|
455
|
+
if (!rawKey) {
|
|
456
|
+
throw new AnchorRouteError(
|
|
457
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_KEY,
|
|
458
|
+
"Capsule cases require a non-empty route key.",
|
|
459
|
+
normalized
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (payload.length === 0) {
|
|
463
|
+
if (rawKey.includes(DOT_SEPARATOR)) {
|
|
464
|
+
return parseDotRoute(normalized, rawKey);
|
|
465
|
+
}
|
|
466
|
+
return parseParamRoute(normalized, rawKey, []);
|
|
467
|
+
}
|
|
468
|
+
const payloadHasDot = payload.some((segment) => segment.includes(DOT_SEPARATOR));
|
|
469
|
+
const payloadAllNamedPairs = payload.every((segment) => {
|
|
470
|
+
const dotIndex = segment.indexOf(DOT_SEPARATOR);
|
|
471
|
+
return dotIndex > 0 && dotIndex < segment.length - 1 && segment.indexOf(DOT_SEPARATOR, dotIndex + 1) === -1;
|
|
472
|
+
});
|
|
473
|
+
if (payloadHasDot) {
|
|
474
|
+
if (!payloadAllNamedPairs) {
|
|
475
|
+
throw new AnchorRouteError(
|
|
476
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_INTENT_PAIR,
|
|
477
|
+
"Intent Case payload must use only name.value pairs.",
|
|
478
|
+
normalized,
|
|
479
|
+
"intent"
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
return parseIntentRoute(normalized, rawKey, payload);
|
|
483
|
+
}
|
|
484
|
+
return parseParamRoute(normalized, rawKey, payload);
|
|
485
|
+
}
|
|
486
|
+
function isAnchorRoute(input) {
|
|
487
|
+
try {
|
|
488
|
+
parseAnchor(input);
|
|
489
|
+
return true;
|
|
490
|
+
} catch {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/validate.ts
|
|
496
|
+
function highestPositionalIndex(instruction, registry) {
|
|
497
|
+
if (instruction.mode !== "param" || !instruction.key) {
|
|
498
|
+
return -1;
|
|
499
|
+
}
|
|
500
|
+
const definition = registry?.global?.[instruction.key];
|
|
501
|
+
if (!definition?.positionalBind) {
|
|
502
|
+
return -1;
|
|
503
|
+
}
|
|
504
|
+
return Math.max(...Object.values(definition.positionalBind), -1);
|
|
505
|
+
}
|
|
506
|
+
function validateAnchor(input, registry, options = {}) {
|
|
507
|
+
let instruction;
|
|
508
|
+
try {
|
|
509
|
+
instruction = typeof input === "string" ? parseAnchor(input) : input;
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (isAnchorRouteError(error)) {
|
|
512
|
+
return { valid: false, errors: [error.toDiagnostic()] };
|
|
513
|
+
}
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
const errors = [];
|
|
517
|
+
switch (instruction.mode) {
|
|
518
|
+
case "page": {
|
|
519
|
+
if (!instruction.page) {
|
|
520
|
+
errors.push(createDiagnostic(ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_PAGE, "Page Case requires a page value.", instruction.raw, "page"));
|
|
521
|
+
}
|
|
522
|
+
if (!instruction.section) {
|
|
523
|
+
errors.push(createDiagnostic(ANCHOR_ERROR_CODES.ANCHOR_PARSE_EMPTY_SECTION, "Page Case requires a section value.", instruction.raw, "page"));
|
|
524
|
+
}
|
|
525
|
+
if (instruction.page && registry?.pages && !registry.pages[instruction.page]) {
|
|
526
|
+
errors.push(
|
|
527
|
+
createDiagnostic(
|
|
528
|
+
ANCHOR_ERROR_CODES.ANCHOR_RESOLVE_PAGE_NOT_FOUND,
|
|
529
|
+
`No page registry entry exists for "${instruction.page}".`,
|
|
530
|
+
instruction.raw,
|
|
531
|
+
"page"
|
|
532
|
+
)
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
if (instruction.page && instruction.section && registry?.pages?.[instruction.page] && !registry.pages[instruction.page][instruction.section]) {
|
|
536
|
+
errors.push(
|
|
537
|
+
createDiagnostic(
|
|
538
|
+
ANCHOR_ERROR_CODES.ANCHOR_RESOLVE_SECTION_NOT_FOUND,
|
|
539
|
+
`No page section registry entry exists for "${instruction.page}~${instruction.section}".`,
|
|
540
|
+
instruction.raw,
|
|
541
|
+
"page"
|
|
542
|
+
)
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
case "param": {
|
|
548
|
+
const definition = instruction.key ? registry?.global?.[instruction.key] : void 0;
|
|
549
|
+
if (instruction.key && registry?.global && !definition) {
|
|
550
|
+
errors.push(
|
|
551
|
+
createDiagnostic(
|
|
552
|
+
ANCHOR_ERROR_CODES.ANCHOR_RESOLVE_KEY_NOT_FOUND,
|
|
553
|
+
`No global registry entry exists for key "${instruction.key}".`,
|
|
554
|
+
instruction.raw,
|
|
555
|
+
"param"
|
|
556
|
+
)
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
const requiredCount = definition?.requiredPositionalCount ?? highestPositionalIndex(instruction, registry) + 1;
|
|
560
|
+
if ((instruction.args?.length ?? 0) < requiredCount) {
|
|
561
|
+
errors.push(
|
|
562
|
+
createDiagnostic(
|
|
563
|
+
ANCHOR_ERROR_CODES.ANCHOR_RESOLVE_BINDING_MISSING_ARG,
|
|
564
|
+
`Param Case key "${instruction.key}" expects at least ${requiredCount} positional arguments.`,
|
|
565
|
+
instruction.raw,
|
|
566
|
+
"param"
|
|
567
|
+
)
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
if (options.maxArgs != null && (instruction.args?.length ?? 0) > options.maxArgs) {
|
|
571
|
+
errors.push(
|
|
572
|
+
createDiagnostic(
|
|
573
|
+
ANCHOR_ERROR_CODES.ANCHOR_VALIDATE_TOO_MANY_ARGS,
|
|
574
|
+
`Param Case exceeds the maximum allowed positional argument count of ${options.maxArgs}.`,
|
|
575
|
+
instruction.raw,
|
|
576
|
+
"param"
|
|
577
|
+
)
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
case "dot": {
|
|
583
|
+
if (instruction.key && registry?.global && !registry.global[instruction.key]) {
|
|
584
|
+
errors.push(
|
|
585
|
+
createDiagnostic(
|
|
586
|
+
ANCHOR_ERROR_CODES.ANCHOR_RESOLVE_KEY_NOT_FOUND,
|
|
587
|
+
`No global registry entry exists for key "${instruction.key}".`,
|
|
588
|
+
instruction.raw,
|
|
589
|
+
"dot"
|
|
590
|
+
)
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
case "intent": {
|
|
596
|
+
const definition = instruction.key ? registry?.global?.[instruction.key] : void 0;
|
|
597
|
+
if (instruction.key && registry?.global && !definition) {
|
|
598
|
+
errors.push(
|
|
599
|
+
createDiagnostic(
|
|
600
|
+
ANCHOR_ERROR_CODES.ANCHOR_RESOLVE_KEY_NOT_FOUND,
|
|
601
|
+
`No global registry entry exists for key "${instruction.key}".`,
|
|
602
|
+
instruction.raw,
|
|
603
|
+
"intent"
|
|
604
|
+
)
|
|
605
|
+
);
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
const requiredNames = definition?.requiredNamedArgs ?? Object.values(definition?.namedBind ?? {});
|
|
609
|
+
for (const requiredName of requiredNames) {
|
|
610
|
+
if (!instruction.namedArgs || !Object.prototype.hasOwnProperty.call(instruction.namedArgs, requiredName)) {
|
|
611
|
+
errors.push(
|
|
612
|
+
createDiagnostic(
|
|
613
|
+
ANCHOR_ERROR_CODES.ANCHOR_RESOLVE_BINDING_MISSING_ARG,
|
|
614
|
+
`Intent Case key "${instruction.key}" requires named argument "${requiredName}".`,
|
|
615
|
+
instruction.raw,
|
|
616
|
+
"intent"
|
|
617
|
+
)
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (definition?.namedBind && instruction.namedArgs) {
|
|
622
|
+
const allowedNames = new Set(Object.values(definition.namedBind));
|
|
623
|
+
for (const name of Object.keys(instruction.namedArgs)) {
|
|
624
|
+
if (!allowedNames.has(name) && (options.unknownNamedArgs ?? "reject") === "reject") {
|
|
625
|
+
errors.push(
|
|
626
|
+
createDiagnostic(
|
|
627
|
+
ANCHOR_ERROR_CODES.ANCHOR_RESOLVE_INVALID_NAMED_ARG,
|
|
628
|
+
`Intent Case key "${instruction.key}" does not allow named argument "${name}".`,
|
|
629
|
+
instruction.raw,
|
|
630
|
+
"intent"
|
|
631
|
+
)
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
default:
|
|
639
|
+
throw new AnchorRouteError(
|
|
640
|
+
ANCHOR_ERROR_CODES.ANCHOR_PARSE_INVALID_FORMAT,
|
|
641
|
+
`Unsupported route mode "${String(instruction.mode)}".`,
|
|
642
|
+
instruction.raw
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
valid: errors.length === 0,
|
|
647
|
+
instruction,
|
|
648
|
+
errors
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/resolve.ts
|
|
653
|
+
function pickBindingTarget(definition, field) {
|
|
654
|
+
return definition.bindingTargets?.[field] ?? "params";
|
|
655
|
+
}
|
|
656
|
+
function assignBoundValue(field, value, definition, params, query) {
|
|
657
|
+
if (pickBindingTarget(definition, field) === "query") {
|
|
658
|
+
query[field] = value;
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
params[field] = value;
|
|
662
|
+
}
|
|
663
|
+
function buildResolvedRoute(instruction, definition, extraMeta = {}) {
|
|
664
|
+
const params = { ...definition.defaultParams ?? {} };
|
|
665
|
+
const query = { ...definition.defaultQuery ?? {} };
|
|
666
|
+
if (instruction.mode === "param" && definition.positionalBind) {
|
|
667
|
+
for (const [field, index] of Object.entries(definition.positionalBind)) {
|
|
668
|
+
const value = instruction.args?.[index];
|
|
669
|
+
if (value != null) {
|
|
670
|
+
assignBoundValue(field, value, definition, params, query);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (instruction.mode === "intent" && definition.namedBind) {
|
|
675
|
+
for (const [field, sourceName] of Object.entries(definition.namedBind)) {
|
|
676
|
+
const value = instruction.namedArgs?.[sourceName];
|
|
677
|
+
if (value != null) {
|
|
678
|
+
assignBoundValue(field, value, definition, params, query);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
success: true,
|
|
684
|
+
mode: instruction.mode,
|
|
685
|
+
raw: instruction.raw,
|
|
686
|
+
name: definition.name,
|
|
687
|
+
path: definition.path,
|
|
688
|
+
params: Object.keys(params).length > 0 ? params : void 0,
|
|
689
|
+
query: Object.keys(query).length > 0 ? query : void 0,
|
|
690
|
+
hash: definition.hash,
|
|
691
|
+
title: definition.title,
|
|
692
|
+
permissions: definition.permissions,
|
|
693
|
+
meta: {
|
|
694
|
+
...definition.meta,
|
|
695
|
+
...extraMeta,
|
|
696
|
+
instruction
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function errorResult(raw, mode, code, message, diagnostics = code ? [createDiagnostic(code, message, raw, mode)] : void 0) {
|
|
701
|
+
return {
|
|
702
|
+
success: false,
|
|
703
|
+
mode,
|
|
704
|
+
raw,
|
|
705
|
+
errorCode: code,
|
|
706
|
+
errorMessage: message,
|
|
707
|
+
diagnostics
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function resolveAnchor(input, registry = {}, options = {}) {
|
|
711
|
+
let instruction;
|
|
712
|
+
try {
|
|
713
|
+
instruction = typeof input === "string" ? parseAnchor(input) : input;
|
|
714
|
+
} catch (error) {
|
|
715
|
+
if (isAnchorRouteError(error)) {
|
|
716
|
+
return {
|
|
717
|
+
success: false,
|
|
718
|
+
mode: "unknown",
|
|
719
|
+
raw: error.raw,
|
|
720
|
+
errorCode: error.code,
|
|
721
|
+
errorMessage: error.message,
|
|
722
|
+
diagnostics: [error.toDiagnostic()]
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
throw error;
|
|
726
|
+
}
|
|
727
|
+
const validation = validateAnchor(instruction, registry, options);
|
|
728
|
+
if (!validation.valid) {
|
|
729
|
+
const [firstDiagnostic] = validation.errors;
|
|
730
|
+
return {
|
|
731
|
+
success: false,
|
|
732
|
+
mode: instruction.mode,
|
|
733
|
+
raw: instruction.raw,
|
|
734
|
+
errorCode: firstDiagnostic?.code,
|
|
735
|
+
errorMessage: firstDiagnostic?.message,
|
|
736
|
+
diagnostics: validation.errors
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
switch (instruction.mode) {
|
|
740
|
+
case "page": {
|
|
741
|
+
const definition = registry.pages?.[instruction.page ?? ""]?.[instruction.section ?? ""];
|
|
742
|
+
if (!definition) {
|
|
743
|
+
return errorResult(
|
|
744
|
+
instruction.raw,
|
|
745
|
+
"page",
|
|
746
|
+
validation.errors[0]?.code ?? "ANCHOR_RESOLVE_PAGE_NOT_FOUND",
|
|
747
|
+
validation.errors[0]?.message ?? `Page Case could not be resolved for "${instruction.page}~${instruction.section}".`,
|
|
748
|
+
validation.errors.length > 0 ? validation.errors : void 0
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
return buildResolvedRoute(instruction, definition, {
|
|
752
|
+
page: instruction.page,
|
|
753
|
+
section: instruction.section
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
case "param": {
|
|
757
|
+
const definition = registry.global?.[instruction.key ?? ""];
|
|
758
|
+
if (!definition) {
|
|
759
|
+
return errorResult(instruction.raw, "param", validation.errors[0]?.code, validation.errors[0]?.message ?? "Param Case could not be resolved.", validation.errors);
|
|
760
|
+
}
|
|
761
|
+
return buildResolvedRoute(instruction, definition, {
|
|
762
|
+
key: instruction.key,
|
|
763
|
+
args: instruction.args ?? []
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
case "dot": {
|
|
767
|
+
const definition = registry.global?.[instruction.key ?? ""];
|
|
768
|
+
if (!definition) {
|
|
769
|
+
return errorResult(instruction.raw, "dot", validation.errors[0]?.code, validation.errors[0]?.message ?? "Dot Case could not be resolved.", validation.errors);
|
|
770
|
+
}
|
|
771
|
+
return buildResolvedRoute(instruction, definition, {
|
|
772
|
+
key: instruction.key,
|
|
773
|
+
pathTokens: instruction.pathTokens ?? []
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
case "intent": {
|
|
777
|
+
const definition = registry.global?.[instruction.key ?? ""];
|
|
778
|
+
if (!definition) {
|
|
779
|
+
return errorResult(instruction.raw, "intent", validation.errors[0]?.code, validation.errors[0]?.message ?? "Intent Case could not be resolved.", validation.errors);
|
|
780
|
+
}
|
|
781
|
+
const allowedNames = new Set(Object.values(definition.namedBind ?? {}));
|
|
782
|
+
const unknownNamedArgs = Object.entries(instruction.namedArgs ?? {}).filter(([name]) => !allowedNames.has(name));
|
|
783
|
+
const meta = {
|
|
784
|
+
key: instruction.key,
|
|
785
|
+
namedArgs: instruction.namedArgs ?? {}
|
|
786
|
+
};
|
|
787
|
+
if ((options.unknownNamedArgs ?? "reject") === "keep" && unknownNamedArgs.length > 0) {
|
|
788
|
+
meta.unknownNamedArgs = Object.fromEntries(unknownNamedArgs);
|
|
789
|
+
}
|
|
790
|
+
return buildResolvedRoute(instruction, definition, meta);
|
|
791
|
+
}
|
|
792
|
+
default:
|
|
793
|
+
return errorResult(instruction.raw, "unknown", void 0, `Unsupported route mode "${String(instruction.mode)}".`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
797
|
+
0 && (module.exports = {
|
|
798
|
+
ANCHOR_ERROR_CODES,
|
|
799
|
+
ANCHOR_PATH_PREFIX,
|
|
800
|
+
ANCHOR_SEPARATOR,
|
|
801
|
+
ARG_SEPARATOR,
|
|
802
|
+
AnchorRouteError,
|
|
803
|
+
DOT_SEPARATOR,
|
|
804
|
+
RESERVED_ANCHOR_CHARACTERS,
|
|
805
|
+
SAFE_LITERAL_CHARACTER,
|
|
806
|
+
buildDotAnchor,
|
|
807
|
+
buildIntentAnchor,
|
|
808
|
+
buildPageAnchor,
|
|
809
|
+
buildParamAnchor,
|
|
810
|
+
createDiagnostic,
|
|
811
|
+
decodeAnchorKey,
|
|
812
|
+
decodeAnchorToken,
|
|
813
|
+
escapeAnchorKey,
|
|
814
|
+
escapeAnchorValue,
|
|
815
|
+
isAnchorRoute,
|
|
816
|
+
isAnchorRouteError,
|
|
817
|
+
isHexDigit,
|
|
818
|
+
isReservedAnchorCharacter,
|
|
819
|
+
normalizeAnchor,
|
|
820
|
+
parseAnchor,
|
|
821
|
+
resolveAnchor,
|
|
822
|
+
serializeAnchor,
|
|
823
|
+
validateAnchor,
|
|
824
|
+
validateEncodedToken
|
|
825
|
+
});
|
|
826
|
+
//# sourceMappingURL=index.js.map
|