@datasworn-community/core 0.2.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 +11 -0
- package/dist/cjs/Datasworn.d.ts +5012 -0
- package/dist/cjs/Datasworn.d.ts.map +1 -0
- package/dist/cjs/Datasworn.js +3 -0
- package/dist/cjs/Datasworn.js.map +1 -0
- package/dist/cjs/DataswornSource.d.ts +5025 -0
- package/dist/cjs/DataswornSource.d.ts.map +1 -0
- package/dist/cjs/DataswornSource.js +3 -0
- package/dist/cjs/DataswornSource.js.map +1 -0
- package/dist/cjs/DataswornTree.d.ts +9 -0
- package/dist/cjs/DataswornTree.d.ts.map +1 -0
- package/dist/cjs/DataswornTree.js +25 -0
- package/dist/cjs/DataswornTree.js.map +1 -0
- package/dist/cjs/DataswornVersion.d.ts +8 -0
- package/dist/cjs/DataswornVersion.d.ts.map +1 -0
- package/dist/cjs/DataswornVersion.js +10 -0
- package/dist/cjs/DataswornVersion.js.map +1 -0
- package/dist/cjs/Errors.d.ts +5 -0
- package/dist/cjs/Errors.d.ts.map +1 -0
- package/dist/cjs/Errors.js +11 -0
- package/dist/cjs/Errors.js.map +1 -0
- package/dist/cjs/IdElements/CONST.d.ts +37 -0
- package/dist/cjs/IdElements/CONST.d.ts.map +1 -0
- package/dist/cjs/IdElements/CONST.js +26 -0
- package/dist/cjs/IdElements/CONST.js.map +1 -0
- package/dist/cjs/IdElements/PathKeys.d.ts +13 -0
- package/dist/cjs/IdElements/PathKeys.d.ts.map +1 -0
- package/dist/cjs/IdElements/PathKeys.js +3 -0
- package/dist/cjs/IdElements/PathKeys.js.map +1 -0
- package/dist/cjs/IdElements/Pattern.d.ts +13 -0
- package/dist/cjs/IdElements/Pattern.d.ts.map +1 -0
- package/dist/cjs/IdElements/Pattern.js +20 -0
- package/dist/cjs/IdElements/Pattern.js.map +1 -0
- package/dist/cjs/IdElements/TypeGuard.d.ts +25 -0
- package/dist/cjs/IdElements/TypeGuard.d.ts.map +1 -0
- package/dist/cjs/IdElements/TypeGuard.js +72 -0
- package/dist/cjs/IdElements/TypeGuard.js.map +1 -0
- package/dist/cjs/IdElements/TypeId.d.ts +114 -0
- package/dist/cjs/IdElements/TypeId.d.ts.map +1 -0
- package/dist/cjs/IdElements/TypeId.js +259 -0
- package/dist/cjs/IdElements/TypeId.js.map +1 -0
- package/dist/cjs/IdElements/index.d.ts +6 -0
- package/dist/cjs/IdElements/index.d.ts.map +1 -0
- package/dist/cjs/IdElements/index.js +48 -0
- package/dist/cjs/IdElements/index.js.map +1 -0
- package/dist/cjs/IdParser.d.ts +380 -0
- package/dist/cjs/IdParser.d.ts.map +1 -0
- package/dist/cjs/IdParser.js +1012 -0
- package/dist/cjs/IdParser.js.map +1 -0
- package/dist/cjs/Migrations/index.d.ts +2 -0
- package/dist/cjs/Migrations/index.d.ts.map +1 -0
- package/dist/cjs/Migrations/index.js +18 -0
- package/dist/cjs/Migrations/index.js.map +1 -0
- package/dist/cjs/Migrations/updateIds.d.ts +34 -0
- package/dist/cjs/Migrations/updateIds.d.ts.map +1 -0
- package/dist/cjs/Migrations/updateIds.js +141 -0
- package/dist/cjs/Migrations/updateIds.js.map +1 -0
- package/dist/cjs/StringId.d.ts +47 -0
- package/dist/cjs/StringId.d.ts.map +1 -0
- package/dist/cjs/StringId.js +3 -0
- package/dist/cjs/StringId.js.map +1 -0
- package/dist/cjs/Tree.d.ts +5 -0
- package/dist/cjs/Tree.d.ts.map +1 -0
- package/dist/cjs/Tree.js +3 -0
- package/dist/cjs/Tree.js.map +1 -0
- package/dist/cjs/TypeNode.d.ts +93 -0
- package/dist/cjs/TypeNode.d.ts.map +1 -0
- package/dist/cjs/TypeNode.js +3 -0
- package/dist/cjs/TypeNode.js.map +1 -0
- package/dist/cjs/Utils/Array.d.ts +16 -0
- package/dist/cjs/Utils/Array.d.ts.map +1 -0
- package/dist/cjs/Utils/Array.js +14 -0
- package/dist/cjs/Utils/Array.js.map +1 -0
- package/dist/cjs/Utils/Id.d.ts +75 -0
- package/dist/cjs/Utils/Id.d.ts.map +1 -0
- package/dist/cjs/Utils/Id.js +3 -0
- package/dist/cjs/Utils/Id.js.map +1 -0
- package/dist/cjs/Utils/Number.d.ts +5 -0
- package/dist/cjs/Utils/Number.d.ts.map +1 -0
- package/dist/cjs/Utils/Number.js +4 -0
- package/dist/cjs/Utils/Number.js.map +1 -0
- package/dist/cjs/Utils/Sort.d.ts +17 -0
- package/dist/cjs/Utils/Sort.d.ts.map +1 -0
- package/dist/cjs/Utils/Sort.js +277 -0
- package/dist/cjs/Utils/Sort.js.map +1 -0
- package/dist/cjs/Utils/String.d.ts +4 -0
- package/dist/cjs/Utils/String.d.ts.map +1 -0
- package/dist/cjs/Utils/String.js +3 -0
- package/dist/cjs/Utils/String.js.map +1 -0
- package/dist/cjs/index.d.ts +10 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/mergeExpansion.d.ts +3 -0
- package/dist/cjs/mergeExpansion.d.ts.map +1 -0
- package/dist/cjs/mergeExpansion.js +113 -0
- package/dist/cjs/mergeExpansion.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -0
- package/dist/esm/Datasworn.d.ts +5012 -0
- package/dist/esm/Datasworn.d.ts.map +1 -0
- package/dist/esm/Datasworn.js +2 -0
- package/dist/esm/Datasworn.js.map +1 -0
- package/dist/esm/DataswornSource.d.ts +5025 -0
- package/dist/esm/DataswornSource.d.ts.map +1 -0
- package/dist/esm/DataswornSource.js +2 -0
- package/dist/esm/DataswornSource.js.map +1 -0
- package/dist/esm/DataswornTree.d.ts +9 -0
- package/dist/esm/DataswornTree.d.ts.map +1 -0
- package/dist/esm/DataswornTree.js +21 -0
- package/dist/esm/DataswornTree.js.map +1 -0
- package/dist/esm/DataswornVersion.d.ts +8 -0
- package/dist/esm/DataswornVersion.d.ts.map +1 -0
- package/dist/esm/DataswornVersion.js +7 -0
- package/dist/esm/DataswornVersion.js.map +1 -0
- package/dist/esm/Errors.d.ts +5 -0
- package/dist/esm/Errors.d.ts.map +1 -0
- package/dist/esm/Errors.js +7 -0
- package/dist/esm/Errors.js.map +1 -0
- package/dist/esm/IdElements/CONST.d.ts +37 -0
- package/dist/esm/IdElements/CONST.d.ts.map +1 -0
- package/dist/esm/IdElements/CONST.js +23 -0
- package/dist/esm/IdElements/CONST.js.map +1 -0
- package/dist/esm/IdElements/PathKeys.d.ts +13 -0
- package/dist/esm/IdElements/PathKeys.d.ts.map +1 -0
- package/dist/esm/IdElements/PathKeys.js +2 -0
- package/dist/esm/IdElements/PathKeys.js.map +1 -0
- package/dist/esm/IdElements/Pattern.d.ts +13 -0
- package/dist/esm/IdElements/Pattern.d.ts.map +1 -0
- package/dist/esm/IdElements/Pattern.js +18 -0
- package/dist/esm/IdElements/Pattern.js.map +1 -0
- package/dist/esm/IdElements/TypeGuard.d.ts +25 -0
- package/dist/esm/IdElements/TypeGuard.d.ts.map +1 -0
- package/dist/esm/IdElements/TypeGuard.js +67 -0
- package/dist/esm/IdElements/TypeGuard.js.map +1 -0
- package/dist/esm/IdElements/TypeId.d.ts +114 -0
- package/dist/esm/IdElements/TypeId.d.ts.map +1 -0
- package/dist/esm/IdElements/TypeId.js +257 -0
- package/dist/esm/IdElements/TypeId.js.map +1 -0
- package/dist/esm/IdElements/index.d.ts +6 -0
- package/dist/esm/IdElements/index.d.ts.map +1 -0
- package/dist/esm/IdElements/index.js +6 -0
- package/dist/esm/IdElements/index.js.map +1 -0
- package/dist/esm/IdParser.d.ts +380 -0
- package/dist/esm/IdParser.d.ts.map +1 -0
- package/dist/esm/IdParser.js +1004 -0
- package/dist/esm/IdParser.js.map +1 -0
- package/dist/esm/Migrations/index.d.ts +2 -0
- package/dist/esm/Migrations/index.d.ts.map +1 -0
- package/dist/esm/Migrations/index.js +2 -0
- package/dist/esm/Migrations/index.js.map +1 -0
- package/dist/esm/Migrations/updateIds.d.ts +34 -0
- package/dist/esm/Migrations/updateIds.d.ts.map +1 -0
- package/dist/esm/Migrations/updateIds.js +135 -0
- package/dist/esm/Migrations/updateIds.js.map +1 -0
- package/dist/esm/StringId.d.ts +47 -0
- package/dist/esm/StringId.d.ts.map +1 -0
- package/dist/esm/StringId.js +2 -0
- package/dist/esm/StringId.js.map +1 -0
- package/dist/esm/Tree.d.ts +5 -0
- package/dist/esm/Tree.d.ts.map +1 -0
- package/dist/esm/Tree.js +2 -0
- package/dist/esm/Tree.js.map +1 -0
- package/dist/esm/TypeNode.d.ts +93 -0
- package/dist/esm/TypeNode.d.ts.map +1 -0
- package/dist/esm/TypeNode.js +2 -0
- package/dist/esm/TypeNode.js.map +1 -0
- package/dist/esm/Utils/Array.d.ts +16 -0
- package/dist/esm/Utils/Array.d.ts.map +1 -0
- package/dist/esm/Utils/Array.js +11 -0
- package/dist/esm/Utils/Array.js.map +1 -0
- package/dist/esm/Utils/Id.d.ts +75 -0
- package/dist/esm/Utils/Id.d.ts.map +1 -0
- package/dist/esm/Utils/Id.js +2 -0
- package/dist/esm/Utils/Id.js.map +1 -0
- package/dist/esm/Utils/Number.d.ts +5 -0
- package/dist/esm/Utils/Number.d.ts.map +1 -0
- package/dist/esm/Utils/Number.js +3 -0
- package/dist/esm/Utils/Number.js.map +1 -0
- package/dist/esm/Utils/Sort.d.ts +17 -0
- package/dist/esm/Utils/Sort.d.ts.map +1 -0
- package/dist/esm/Utils/Sort.js +267 -0
- package/dist/esm/Utils/Sort.js.map +1 -0
- package/dist/esm/Utils/String.d.ts +4 -0
- package/dist/esm/Utils/String.d.ts.map +1 -0
- package/dist/esm/Utils/String.js +2 -0
- package/dist/esm/Utils/String.js.map +1 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mergeExpansion.d.ts +3 -0
- package/dist/esm/mergeExpansion.d.ts.map +1 -0
- package/dist/esm/mergeExpansion.js +107 -0
- package/dist/esm/mergeExpansion.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -0
- package/json/datasworn-source.schema.json +11420 -0
- package/json/datasworn.schema.json +11354 -0
- package/migration/0.1.0/id_regex_map.json +21 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
import { ParseError } from './Errors.js';
|
|
2
|
+
import { COLLECTION_DEPTH_MAX, COLLECTION_DEPTH_MIN, CollectionsKey, ContentsKey, GlobstarString, IdKey, PathKeySep, PrefixSep, TypeSep, WildcardString } from './IdElements/CONST.js';
|
|
3
|
+
import { Pattern } from './IdElements/index.js';
|
|
4
|
+
import TypeGuard from './IdElements/TypeGuard.js';
|
|
5
|
+
import TypeId from './IdElements/TypeId.js';
|
|
6
|
+
/**
|
|
7
|
+
* Resolves a potentially dot-separated property path to retrieve a nested value.
|
|
8
|
+
* For example, 'trigger.conditions' on { trigger: { conditions: [...] } } returns the conditions array.
|
|
9
|
+
*/
|
|
10
|
+
function getNestedProperty(obj, propertyPath) {
|
|
11
|
+
if (typeof obj !== 'object' || obj === null)
|
|
12
|
+
return undefined;
|
|
13
|
+
const parts = propertyPath.split('.');
|
|
14
|
+
let current = obj;
|
|
15
|
+
for (const part of parts) {
|
|
16
|
+
if (typeof current !== 'object' || current === null)
|
|
17
|
+
return undefined;
|
|
18
|
+
current = current[part];
|
|
19
|
+
}
|
|
20
|
+
return current;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a potentially dot-separated property path exists on an object.
|
|
24
|
+
*/
|
|
25
|
+
function hasNestedProperty(obj, propertyPath) {
|
|
26
|
+
return getNestedProperty(obj, propertyPath) !== undefined;
|
|
27
|
+
}
|
|
28
|
+
/** Performs parsing, validation, and construction of Datasworn IDs, and traverses the Datasworn hierarchy to retrieve matching node(s). */
|
|
29
|
+
class IdParser {
|
|
30
|
+
static #tree = null;
|
|
31
|
+
/** */
|
|
32
|
+
static get tree() {
|
|
33
|
+
return IdParser.#tree;
|
|
34
|
+
}
|
|
35
|
+
static set tree(value) {
|
|
36
|
+
IdParser.#tree = value;
|
|
37
|
+
}
|
|
38
|
+
#pathSegments;
|
|
39
|
+
#typeIds;
|
|
40
|
+
/**
|
|
41
|
+
* The object used for log messages.
|
|
42
|
+
* @default console
|
|
43
|
+
*/
|
|
44
|
+
static logger = console;
|
|
45
|
+
get typeIds() {
|
|
46
|
+
return this.#typeIds;
|
|
47
|
+
}
|
|
48
|
+
set typeIds(value) {
|
|
49
|
+
IdParser.#validateTypeIds(value);
|
|
50
|
+
this.#typeIds = value;
|
|
51
|
+
}
|
|
52
|
+
get pathSegments() {
|
|
53
|
+
return this.#pathSegments;
|
|
54
|
+
}
|
|
55
|
+
set pathSegments(value) {
|
|
56
|
+
IdParser._validatePathSegments(this.typeIds, value);
|
|
57
|
+
this.#pathSegments = value;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* This node's ancestor key on {@link Datasworn.RulesPackage}.
|
|
61
|
+
*/
|
|
62
|
+
get typeBranchKey() {
|
|
63
|
+
return TypeId.getBranchKey(this.primaryTypeId);
|
|
64
|
+
}
|
|
65
|
+
constructor(options) {
|
|
66
|
+
// prepare the ID so we can throw errors with it if necessary
|
|
67
|
+
const id = IdParser.#toString(options);
|
|
68
|
+
const { typeIds, pathSegments } = options;
|
|
69
|
+
const errors = [];
|
|
70
|
+
try {
|
|
71
|
+
this.typeIds = typeIds;
|
|
72
|
+
this.pathSegments = pathSegments;
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
errors.push(e);
|
|
76
|
+
}
|
|
77
|
+
if (errors.length > 0)
|
|
78
|
+
throw new ParseError(id, errors.join('\n'));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Returns a string representation of the ID.
|
|
82
|
+
*/
|
|
83
|
+
toString() {
|
|
84
|
+
return IdParser.#toString(this);
|
|
85
|
+
}
|
|
86
|
+
toJSON() {
|
|
87
|
+
return this.toString();
|
|
88
|
+
}
|
|
89
|
+
// ID parts
|
|
90
|
+
/** The type ID of the target node. For primary IDs, this is the same as {@link IdParser.typeId}. */
|
|
91
|
+
get typeId() {
|
|
92
|
+
return this.typeIds.at(-1);
|
|
93
|
+
}
|
|
94
|
+
/** The type ID of the most recent primary node. For primary IDs, this is the same as {@link IdParser.typeId} */
|
|
95
|
+
get primaryTypeId() {
|
|
96
|
+
return this.typeIds[0];
|
|
97
|
+
}
|
|
98
|
+
get primaryPath() {
|
|
99
|
+
return this.pathSegments[0];
|
|
100
|
+
}
|
|
101
|
+
get primaryPathKeys() {
|
|
102
|
+
return this.primaryPath.split(PathKeySep);
|
|
103
|
+
}
|
|
104
|
+
/** The ID of the {@link RulesPackage} that contains this ID, or a wildcard to represent any RulesPackage. */
|
|
105
|
+
get rulesPackageId() {
|
|
106
|
+
return this.primaryPathKeys[0];
|
|
107
|
+
}
|
|
108
|
+
/** The dot-separated, fully-qualified type ID. For primary types, this is the same as {@link IdParser.typeId} */
|
|
109
|
+
get compositeTypeId() {
|
|
110
|
+
return this.typeIds.join(TypeSep);
|
|
111
|
+
}
|
|
112
|
+
/** The dot-separated, fully-qualified path. For primary type IDs, this is the same as {@link IdParser.primaryPath} */
|
|
113
|
+
get compositePath() {
|
|
114
|
+
return this.pathSegments.join(TypeSep);
|
|
115
|
+
}
|
|
116
|
+
/** Does this ID contain any wildcard ("*") or globstar ("**") elements? */
|
|
117
|
+
get isWildcard() {
|
|
118
|
+
return this.toString().includes(WildcardString);
|
|
119
|
+
}
|
|
120
|
+
/** Get an array of the types that are embeddable by this type. */
|
|
121
|
+
getEmbeddableTypes() {
|
|
122
|
+
return TypeId.getEmbeddableTypes(this.typeId, this instanceof EmbeddedId);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get a Datasworn node by its ID.
|
|
126
|
+
* @throws If the ID is a wildcard ID; if no Datasworn tree is provided (either in {@link IdParser.tree} or as an argument).
|
|
127
|
+
* @returns The identified node, or `undefined` if the node doesn't exist.
|
|
128
|
+
*/
|
|
129
|
+
get(tree = IdParser.tree) {
|
|
130
|
+
tree ||= IdParser.tree;
|
|
131
|
+
if (tree == null)
|
|
132
|
+
throw new Error(`No Datasworn tree found -- set the static property ${IdParser.constructor.name}#tree, or provide one as a parameter.`);
|
|
133
|
+
if (this.isWildcard)
|
|
134
|
+
throw new Error(`${this.constructor.name}#${this.get.name} expected a non-wildcard ID, but got <${this.toString()}>. If you want to get wildcard matches, use ${this.constructor.name}#${this.getMatches.name} instead.`);
|
|
135
|
+
try {
|
|
136
|
+
return this._getUnsafe(tree);
|
|
137
|
+
}
|
|
138
|
+
catch (_e) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get all Datasworn nodes that match this wildcard ID.
|
|
144
|
+
* @remarks Non-wildcard IDs work here too; technically they're valid as wildcard IDs.
|
|
145
|
+
* @param forEach Optional function to apply to each match. If it returns `true`, the matcher will exit early and return only the matches it has made so far.
|
|
146
|
+
* @returns A {@link Map}
|
|
147
|
+
*/
|
|
148
|
+
getMatches(tree = IdParser.tree, forEach) {
|
|
149
|
+
tree ||= IdParser.tree;
|
|
150
|
+
if (tree == null)
|
|
151
|
+
throw new Error(`No Datasworn tree found -- set the static property ${IdParser.constructor.name}#tree, or provide one as a parameter.`);
|
|
152
|
+
// skip the matching process if it's not actually a wildcard ID
|
|
153
|
+
if (!this.isWildcard) {
|
|
154
|
+
const match = this.get(tree);
|
|
155
|
+
const matches = new Map();
|
|
156
|
+
if (match != null) {
|
|
157
|
+
matches.set(this.toString(), match);
|
|
158
|
+
if (typeof forEach === 'function')
|
|
159
|
+
forEach(this.toString(), match);
|
|
160
|
+
}
|
|
161
|
+
return matches;
|
|
162
|
+
}
|
|
163
|
+
return this._getMatchesUnsafe(tree, forEach);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Can the target ID be matched with this ID?
|
|
167
|
+
* @throws If `target` is a wildcard.
|
|
168
|
+
*/
|
|
169
|
+
isMatch(target) {
|
|
170
|
+
const targetIsWildcard = target.toString().includes(WildcardString);
|
|
171
|
+
if (targetIsWildcard)
|
|
172
|
+
throw new Error(`Expected a non-wildcard ID, got "${target.toString()}"`);
|
|
173
|
+
// lazy evaluation for exact matches
|
|
174
|
+
if (this.toString() === target.toString())
|
|
175
|
+
return true;
|
|
176
|
+
const that = typeof target === 'string' ? IdParser.parse(target) : target;
|
|
177
|
+
// if it's not an exact match, and this isn't a wildcard, then it logically can't be a match
|
|
178
|
+
if (!this.isWildcard)
|
|
179
|
+
return false;
|
|
180
|
+
// can never match a different type composite
|
|
181
|
+
if (this.compositeTypeId !== that.compositeTypeId)
|
|
182
|
+
return false;
|
|
183
|
+
// no more trivial matches, fall back regular expression (more computationally expensive)
|
|
184
|
+
return this.pathRegExp.test(that.compositePath);
|
|
185
|
+
}
|
|
186
|
+
/** Is this non-wildcard ID matched by at least one of the provided wildcard IDs?
|
|
187
|
+
* @throws If this instance is a wildcard ID; if it attempts a comparison with an invalid ID string
|
|
188
|
+
*/
|
|
189
|
+
isMatchedBy(...wildcardIds) {
|
|
190
|
+
if (this.isWildcard)
|
|
191
|
+
throw new Error(`Matching a wildcard ID as a subset of another wildcard ID is not implemented.`);
|
|
192
|
+
for (const wildcardId of wildcardIds) {
|
|
193
|
+
// short circuit parsing if it's a trivial match
|
|
194
|
+
if (this.toString() === wildcardId.toString())
|
|
195
|
+
return true;
|
|
196
|
+
const parsed = typeof wildcardId === 'string' ? IdParser.parse(wildcardId) : wildcardId;
|
|
197
|
+
if (parsed.isMatch(this))
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
/** @internal */
|
|
203
|
+
_getPrefixRegExpSource() {
|
|
204
|
+
const src = this.typeIds.join(`\\${TypeSep}`);
|
|
205
|
+
return src;
|
|
206
|
+
}
|
|
207
|
+
#pathRegExp = null;
|
|
208
|
+
get pathRegExp() {
|
|
209
|
+
if (this.#pathRegExp == null)
|
|
210
|
+
this.#pathRegExp = RegExp(`^${this._getPathRegExpSource()}$`);
|
|
211
|
+
return this.#pathRegExp;
|
|
212
|
+
}
|
|
213
|
+
#regExp = null;
|
|
214
|
+
get regExp() {
|
|
215
|
+
if (this.#regExp == null)
|
|
216
|
+
this.#regExp = RegExp('^' +
|
|
217
|
+
this._getPrefixRegExpSource() +
|
|
218
|
+
PrefixSep +
|
|
219
|
+
this._getPathRegExpSource() +
|
|
220
|
+
'$');
|
|
221
|
+
return this.#regExp;
|
|
222
|
+
}
|
|
223
|
+
/** @internal */
|
|
224
|
+
_flush() {
|
|
225
|
+
this.#pathRegExp = null;
|
|
226
|
+
this.#regExp = null;
|
|
227
|
+
}
|
|
228
|
+
/** @internal */
|
|
229
|
+
_getPathRegExpSource() {
|
|
230
|
+
let pathPattern = '';
|
|
231
|
+
const keySep = `\\${PathKeySep}`;
|
|
232
|
+
const { min, max } = IdParser._getPathKeyCount(this.primaryTypeId);
|
|
233
|
+
/** The minimum number of path keys a single globstar ("**") may stand in for */
|
|
234
|
+
const expansionMin = Math.max(0, min - (this.primaryPathKeys.length - 1));
|
|
235
|
+
/** The maximum number of keys that a single globstar ("**") may stand in for. */
|
|
236
|
+
const expansionMax = Math.max(0, max - (this.primaryPathKeys.length - 1));
|
|
237
|
+
for (let i = 0; i < this.primaryPathKeys.length; i++) {
|
|
238
|
+
const currentKey = this.primaryPathKeys[i];
|
|
239
|
+
switch (true) {
|
|
240
|
+
// first key is always the rules package, which allows more characters
|
|
241
|
+
case i === 0:
|
|
242
|
+
pathPattern += TypeGuard.Wildcard(currentKey)
|
|
243
|
+
? Pattern.RulesPackageElement.source
|
|
244
|
+
: currentKey;
|
|
245
|
+
break;
|
|
246
|
+
case TypeGuard.Wildcard(currentKey):
|
|
247
|
+
if (i > 0)
|
|
248
|
+
pathPattern += keySep;
|
|
249
|
+
pathPattern += Pattern.DictKeyElement.source;
|
|
250
|
+
break;
|
|
251
|
+
case TypeGuard.Globstar(currentKey):
|
|
252
|
+
// no key separator here since it's part of the non-capturing group
|
|
253
|
+
pathPattern += `(?:${keySep}${Pattern.DictKeyElement.source}){${expansionMin},${expansionMax}}`;
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
if (i > 0)
|
|
257
|
+
pathPattern += keySep;
|
|
258
|
+
pathPattern += currentKey;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return pathPattern;
|
|
263
|
+
}
|
|
264
|
+
/** Assign `_id` strings in a Datasworn node.
|
|
265
|
+
* @param node The Datasworn
|
|
266
|
+
* @param recursive Should IDs be assigned to descendant objects too? (default: true)
|
|
267
|
+
* @param index A Map used to index items by their assigned ID.
|
|
268
|
+
* @returns The mutated object.
|
|
269
|
+
*/
|
|
270
|
+
assignIdsIn(node, _recursive = true, index) {
|
|
271
|
+
if (typeof node !== 'object' || node === null)
|
|
272
|
+
throw new Error(`Expected a Datasworn node object, but got ${String(node)}`);
|
|
273
|
+
if (IdKey in node && typeof node._id === 'string')
|
|
274
|
+
IdParser.logger.warn(`Can't assign <${this.toString()}>, node already has <${node._id}>`);
|
|
275
|
+
else {
|
|
276
|
+
if (index instanceof Map && index.has(this.toString()))
|
|
277
|
+
throw new Error(`Generated ID <${this.toString()}>, but it already exists in the index`);
|
|
278
|
+
// @ts-expect-error
|
|
279
|
+
node._id = this.toString();
|
|
280
|
+
if (index instanceof Map)
|
|
281
|
+
index.set(this.toString(), node);
|
|
282
|
+
}
|
|
283
|
+
return node;
|
|
284
|
+
}
|
|
285
|
+
static get(id, tree = IdParser.datasworn) {
|
|
286
|
+
const parsed = id instanceof IdParser ? id : IdParser.parse(id);
|
|
287
|
+
return parsed.get(tree);
|
|
288
|
+
}
|
|
289
|
+
static getMatches(id, tree = IdParser.datasworn, forEach) {
|
|
290
|
+
const parsed = id instanceof IdParser ? id : IdParser.parse(id);
|
|
291
|
+
return parsed.getMatches(tree, forEach);
|
|
292
|
+
}
|
|
293
|
+
static parse(id) {
|
|
294
|
+
const { typeIds, pathSegments } = IdParser.#parseOptions(id);
|
|
295
|
+
const [primaryTypeId, ...embeddedTypeIds] = typeIds;
|
|
296
|
+
const [primaryPath, ...embeddedPaths] = pathSegments;
|
|
297
|
+
const [rulesPackage, ...pathKeys] = primaryPath.split(PathKeySep);
|
|
298
|
+
const Ctor = IdParser.#getClassForPrimaryTypeId(primaryTypeId);
|
|
299
|
+
// @ts-expect-error
|
|
300
|
+
const base = new Ctor(primaryTypeId, rulesPackage, ...pathKeys);
|
|
301
|
+
if (embeddedTypeIds.length === 0)
|
|
302
|
+
return base;
|
|
303
|
+
let lastParser = base;
|
|
304
|
+
for (let i = 0; i < embeddedTypeIds.length; i++) {
|
|
305
|
+
const typeId = embeddedTypeIds[i];
|
|
306
|
+
const pathKey = embeddedPaths[i];
|
|
307
|
+
lastParser = lastParser.createEmbeddedIdChild(typeId, pathKey);
|
|
308
|
+
}
|
|
309
|
+
return lastParser;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Recursively assigns IDs to all eligibile nodes within a given {@link DataswornSource.RulesPackage}.
|
|
313
|
+
* @param rulesPackage The rules package to assign IDs to. This function mutates the object.
|
|
314
|
+
* @param index If provided, nodes that receive IDs will be indexed in the map (with their ID as the key).
|
|
315
|
+
* @returns The mutated `rulesPackage`, which now satisfies the requirements for a complete {@link Datasworn.RulesPackage}
|
|
316
|
+
*/
|
|
317
|
+
static assignIdsInRulesPackage(rulesPackage, index) {
|
|
318
|
+
const errorMessages = [];
|
|
319
|
+
for (const typeId in TypeId.BranchKey) {
|
|
320
|
+
const typeBranchKey = TypeId.getBranchKey(typeId);
|
|
321
|
+
const typeBranch = rulesPackage[typeBranchKey];
|
|
322
|
+
if (typeBranch == null)
|
|
323
|
+
continue;
|
|
324
|
+
for (const dictKey in typeBranch) {
|
|
325
|
+
const node = typeBranch[dictKey];
|
|
326
|
+
if (node == null)
|
|
327
|
+
continue;
|
|
328
|
+
try {
|
|
329
|
+
switch (true) {
|
|
330
|
+
case TypeGuard.CollectionType(typeId):
|
|
331
|
+
new CollectionId(typeId, rulesPackage._id, dictKey).assignIdsIn(node, true, index);
|
|
332
|
+
break;
|
|
333
|
+
case TypeGuard.NonCollectableType(typeId):
|
|
334
|
+
new NonCollectableId(typeId, rulesPackage._id, dictKey).assignIdsIn(node, true, index);
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
const id = `${typeId}${PrefixSep}${rulesPackage._id}${PathKeySep}${dictKey}`;
|
|
340
|
+
errorMessages.push(`Failed to create ID within <${id}>. ${String(e)}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (errorMessages.length > 0)
|
|
345
|
+
throw new Error(errorMessages.join('\n'));
|
|
346
|
+
// @ts-expect-error
|
|
347
|
+
return rulesPackage;
|
|
348
|
+
}
|
|
349
|
+
// Static private methods
|
|
350
|
+
static #validateTypeIds(typeIds) {
|
|
351
|
+
if (!Array.isArray(typeIds) ||
|
|
352
|
+
!typeIds.every((str) => typeof str === 'string'))
|
|
353
|
+
throw new Error(`Expected an array of strings but got ${JSON.stringify(typeIds)}`);
|
|
354
|
+
const primaryTypeId = typeIds[0];
|
|
355
|
+
if (!TypeGuard.PrimaryType(primaryTypeId))
|
|
356
|
+
throw new Error(`Expected a primary type but got ${JSON.stringify(primaryTypeId)}`);
|
|
357
|
+
for (let i = 1; i < typeIds.length; i++) {
|
|
358
|
+
const embeddedTypeId = typeIds[i];
|
|
359
|
+
const parentTypeId = typeIds[i - 1];
|
|
360
|
+
const parentTypeIsEmbedded = i > 1;
|
|
361
|
+
const embeddableTypes = TypeId.getEmbeddableTypes(parentTypeId, parentTypeIsEmbedded);
|
|
362
|
+
if (!embeddableTypes.includes(embeddedTypeId)) {
|
|
363
|
+
const parentTypeIdComposite = typeIds.slice(0, i).join(TypeSep);
|
|
364
|
+
throw new Error(`Can't embed type "${embeddedTypeId}" in type "${parentTypeIdComposite}"`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
static #toString({ typeIds, pathSegments }) {
|
|
370
|
+
const leftSide = typeIds.join(TypeSep);
|
|
371
|
+
const rightSide = pathSegments.join(TypeSep);
|
|
372
|
+
return leftSide + PrefixSep + rightSide;
|
|
373
|
+
}
|
|
374
|
+
static _validatePathSegments(typeIds, pathSegments) {
|
|
375
|
+
if (!(Array.isArray(pathSegments) &&
|
|
376
|
+
pathSegments.every((str) => typeof str === 'string')))
|
|
377
|
+
throw new Error(`Expected an array of strings, but got ${JSON.stringify(pathSegments)}`);
|
|
378
|
+
if (typeIds.length !== pathSegments.length)
|
|
379
|
+
throw new Error(`The length of typeIds (${typeIds.length}) and pathSegments (${pathSegments.length}) does't match.`);
|
|
380
|
+
const [primaryTypeId, ..._embeddedTypeIds] = typeIds;
|
|
381
|
+
const errors = [];
|
|
382
|
+
const [primaryPath, ...embeddedPaths] = pathSegments;
|
|
383
|
+
try {
|
|
384
|
+
IdParser._validatePrimaryPath(primaryTypeId, primaryPath);
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
errors.push(e);
|
|
388
|
+
}
|
|
389
|
+
// if there's no embedded paths, we're done
|
|
390
|
+
if (pathSegments.length === 1)
|
|
391
|
+
return true;
|
|
392
|
+
// TODO: make it so that a single globstar as full path matches any of its type -- even for embedded ones
|
|
393
|
+
if (TypeId.canHaveEmbed(primaryTypeId, false))
|
|
394
|
+
for (let i = 0; i < embeddedPaths.length; i++) {
|
|
395
|
+
const currentTypeIds = typeIds.slice(0, i + 1);
|
|
396
|
+
const embeddedPath = embeddedPaths[i];
|
|
397
|
+
try {
|
|
398
|
+
EmbeddedId._validateEmbeddedPath(currentTypeIds, embeddedPath);
|
|
399
|
+
}
|
|
400
|
+
catch (e) {
|
|
401
|
+
errors.push(e);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
else if (embeddedPaths.length > 0) {
|
|
405
|
+
errors.push(`"${primaryTypeId}" isn't an embedding type, but received embedded paths: ${JSON.stringify(embeddedPaths)}`);
|
|
406
|
+
}
|
|
407
|
+
if (errors.length)
|
|
408
|
+
throw new Error(`Expected a valid ID path, but got ${pathSegments.join(TypeSep)}\n${errors.join('\n')}`);
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
static _validateEmbeddedPath(typeIds, path) {
|
|
412
|
+
const _embeddedTypeId = typeIds.at(-1);
|
|
413
|
+
const pathParts = path.split(PathKeySep);
|
|
414
|
+
for (const part of pathParts)
|
|
415
|
+
if (!(IdParser._validateDictKey(part) || IdParser._validateIndexKey(part)))
|
|
416
|
+
throw new Error(`Expected a DictKey, array index, or wildcard, but got ${JSON.stringify(part)}`);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
static _validateIndexKey(value) {
|
|
420
|
+
return TypeGuard.Wildcard(value) || TypeGuard.IndexKey(value);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Parses an ID string in to an IdParser options object without attempting to validate it.
|
|
424
|
+
*/
|
|
425
|
+
static #parseOptions(id) {
|
|
426
|
+
const [leftSide, rightSide] = id.split(PrefixSep);
|
|
427
|
+
const typeIds = leftSide.split(TypeSep);
|
|
428
|
+
const pathSegments = rightSide.split(TypeSep);
|
|
429
|
+
return {
|
|
430
|
+
typeIds,
|
|
431
|
+
pathSegments
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
static #getClassForPrimaryTypeId(typeId) {
|
|
435
|
+
switch (true) {
|
|
436
|
+
case TypeGuard.CollectionType(typeId):
|
|
437
|
+
// @ts-expect-error
|
|
438
|
+
return CollectionId;
|
|
439
|
+
case TypeGuard.CollectableType(typeId):
|
|
440
|
+
// @ts-expect-error
|
|
441
|
+
return CollectableId;
|
|
442
|
+
case TypeGuard.NonCollectableType(typeId):
|
|
443
|
+
// @ts-expect-error
|
|
444
|
+
return NonCollectableId;
|
|
445
|
+
default:
|
|
446
|
+
throw new Error(`Expected TypeId.AnyPrimary, but got ${JSON.stringify(typeId)}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/** @internal */
|
|
450
|
+
static _getPathKeyCount(typeId) {
|
|
451
|
+
let min, max;
|
|
452
|
+
switch (true) {
|
|
453
|
+
case TypeGuard.CollectionType(typeId):
|
|
454
|
+
// has a rulespackage
|
|
455
|
+
min = COLLECTION_DEPTH_MIN + 1;
|
|
456
|
+
max = COLLECTION_DEPTH_MAX + 1;
|
|
457
|
+
break;
|
|
458
|
+
case TypeGuard.CollectableType(typeId):
|
|
459
|
+
// has a rulespackage + its own key
|
|
460
|
+
min = COLLECTION_DEPTH_MIN + 2;
|
|
461
|
+
max = COLLECTION_DEPTH_MAX + 2;
|
|
462
|
+
break;
|
|
463
|
+
case TypeGuard.NonCollectableType(typeId):
|
|
464
|
+
// has a rulespackage
|
|
465
|
+
min = max = COLLECTION_DEPTH_MIN + 1;
|
|
466
|
+
break;
|
|
467
|
+
default:
|
|
468
|
+
throw new Error(`Expected primary TypeId but got ${String(typeId)}`);
|
|
469
|
+
}
|
|
470
|
+
return { min, max };
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* @throws If the typeId isn't a primary TypeId; if the path doesn't meet the minimum or maximum length for its type; or if any of the individual elements are invalid.
|
|
474
|
+
*/
|
|
475
|
+
static _validatePrimaryPath(typeId, path) {
|
|
476
|
+
if (!TypeGuard.PrimaryType(typeId))
|
|
477
|
+
throw new Error(`Expected a primary TypeId, but got ${JSON.stringify(typeId)}. Valid TypeIds are: ${JSON.stringify(TypeId.Primary)}`);
|
|
478
|
+
const { min, max } = IdParser._getPathKeyCount(typeId);
|
|
479
|
+
const [rulesPackageId, ...tail] = path.split(PathKeySep);
|
|
480
|
+
const errors = [];
|
|
481
|
+
let nonGlobstarCount = 0;
|
|
482
|
+
let globstarCount = 0;
|
|
483
|
+
switch (true) {
|
|
484
|
+
case TypeGuard.Globstar(rulesPackageId):
|
|
485
|
+
globstarCount++;
|
|
486
|
+
break;
|
|
487
|
+
case TypeGuard.Wildcard(rulesPackageId):
|
|
488
|
+
case TypeGuard.RulesPackageId(rulesPackageId):
|
|
489
|
+
nonGlobstarCount++;
|
|
490
|
+
break;
|
|
491
|
+
default:
|
|
492
|
+
errors.push(`Expected RulesPackageId but got ${String(rulesPackageId)}`);
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
for (const k of tail)
|
|
496
|
+
switch (true) {
|
|
497
|
+
case TypeGuard.Globstar(k):
|
|
498
|
+
globstarCount++;
|
|
499
|
+
break;
|
|
500
|
+
case TypeGuard.Wildcard(k):
|
|
501
|
+
case TypeGuard.DictKey(k):
|
|
502
|
+
nonGlobstarCount++;
|
|
503
|
+
break;
|
|
504
|
+
default:
|
|
505
|
+
errors.push(`Expected DictKey but got ${String(k)}`);
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
if (nonGlobstarCount > max)
|
|
509
|
+
// so this error appears first
|
|
510
|
+
errors.unshift(`Path expects a maximum length of ${max}, but got ${nonGlobstarCount}`);
|
|
511
|
+
if (nonGlobstarCount < min &&
|
|
512
|
+
// if there's at least one globstar, then it'll expand to meet the minimum
|
|
513
|
+
!(globstarCount > 0))
|
|
514
|
+
errors.unshift(`Path expects a minimum length of ${min}, but got ${nonGlobstarCount}`);
|
|
515
|
+
if (errors.length > 0)
|
|
516
|
+
throw new Error(`Expected valid Primary ID path but got "${path}".\n\t${errors.join('\n\t')}`);
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
/** @internal */
|
|
520
|
+
static _validateDictKey(key) {
|
|
521
|
+
return TypeGuard.AnyWildcard(key) || TypeGuard.DictKey(key);
|
|
522
|
+
}
|
|
523
|
+
static _getMatchesFrom(obj, matchKey = WildcardString) {
|
|
524
|
+
switch (true) {
|
|
525
|
+
case Array.isArray(obj):
|
|
526
|
+
return IdParser.#getMatchesFromArray(obj, matchKey);
|
|
527
|
+
case obj instanceof Map:
|
|
528
|
+
return IdParser.#getMatchesFromMap(obj, matchKey);
|
|
529
|
+
case typeof obj === 'object':
|
|
530
|
+
return IdParser.#getMatchesFromRecord(obj, matchKey.toString());
|
|
531
|
+
default:
|
|
532
|
+
throw new Error(`Expected an Array, Map, or Record, but got ${String(obj)}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
static #getMatchesFromArray(array, matchKey = WildcardString) {
|
|
536
|
+
if (!Array.isArray(array))
|
|
537
|
+
throw new Error(`Expected an Array, but got ${String(array)}`);
|
|
538
|
+
// special case: a wildcard matches everything, so return it as a new Map
|
|
539
|
+
if (TypeGuard.AnyWildcard(matchKey))
|
|
540
|
+
return new Map(array.entries());
|
|
541
|
+
const index = typeof matchKey === 'string' ? Number.parseInt(matchKey, 10) : matchKey;
|
|
542
|
+
if (!Number.isInteger(index))
|
|
543
|
+
throw Error(`Unable to parse an index (integer) from ${String(matchKey)}`);
|
|
544
|
+
const results = new Map();
|
|
545
|
+
const match = array[index];
|
|
546
|
+
if (match != null)
|
|
547
|
+
results.set(index, match);
|
|
548
|
+
return results;
|
|
549
|
+
}
|
|
550
|
+
static #getMatchesFromMap(map, matchKey = WildcardString) {
|
|
551
|
+
if (!(map instanceof Map))
|
|
552
|
+
throw new Error(`Expected a Map, but got ${String(map)}`);
|
|
553
|
+
// special case: a wildcard matches everything, so return it as a new Map
|
|
554
|
+
if (TypeGuard.AnyWildcard(matchKey))
|
|
555
|
+
return new Map(map);
|
|
556
|
+
const results = new Map();
|
|
557
|
+
if (map.has(matchKey))
|
|
558
|
+
results.set(matchKey, map.get(matchKey));
|
|
559
|
+
return results;
|
|
560
|
+
}
|
|
561
|
+
static #getMatchesFromRecord(record, matchKey) {
|
|
562
|
+
if (record == null || typeof record !== 'object' || Array.isArray(record))
|
|
563
|
+
throw new Error(`Expected a Record object, but got ${String(record)}`);
|
|
564
|
+
const results = new Map();
|
|
565
|
+
switch (true) {
|
|
566
|
+
case TypeGuard.AnyWildcard(matchKey):
|
|
567
|
+
for (const k in record)
|
|
568
|
+
if (Object.hasOwn(record, k))
|
|
569
|
+
results.set(k, record[k]);
|
|
570
|
+
break;
|
|
571
|
+
case Object.hasOwn(record, matchKey):
|
|
572
|
+
results.set(matchKey, record[matchKey]);
|
|
573
|
+
break;
|
|
574
|
+
default:
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
return results;
|
|
578
|
+
}
|
|
579
|
+
// Static properties
|
|
580
|
+
/** An optional reference to the Datasworn tree object, shared by all subclasses. Used as the default value for several traversal methods. */
|
|
581
|
+
static datasworn = null;
|
|
582
|
+
static RulesPackagePattern = Pattern.RulesPackageElement;
|
|
583
|
+
static DictKeyPattern = Pattern.DictKeyElement;
|
|
584
|
+
static RecursiveDictKeyPattern = Pattern.RecursiveDictKeysElement;
|
|
585
|
+
/** @internal */
|
|
586
|
+
_getRulesPackage(tree) {
|
|
587
|
+
if (tree == null)
|
|
588
|
+
throw new Error(`No Datasworn tree found -- set the static property ${IdParser.constructor.name}#tree, or provide one as a parameter.`);
|
|
589
|
+
if (this.isWildcard)
|
|
590
|
+
throw new Error(`${this.constructor.name}#${this.get.name} expected a non-wildcard ID, but got <${this.toString()}>. If you want to get wildcard matches, use ${this.constructor.name}#${this.getMatches.name} instead.`);
|
|
591
|
+
if (tree instanceof Map)
|
|
592
|
+
return tree.get(this.rulesPackageId) ?? undefined;
|
|
593
|
+
return tree[this.rulesPackageId] ?? undefined;
|
|
594
|
+
}
|
|
595
|
+
/** @internal */
|
|
596
|
+
_getTypeBranch(tree) {
|
|
597
|
+
const pkg = this._getRulesPackage(tree);
|
|
598
|
+
if (pkg == null)
|
|
599
|
+
throw new Error(`Couldn't find a RulesPackage with the id "${this.rulesPackageId}"`);
|
|
600
|
+
return pkg[this.typeBranchKey];
|
|
601
|
+
}
|
|
602
|
+
/** @internal */
|
|
603
|
+
_getUnsafe(tree) {
|
|
604
|
+
const typeBranch = this._getTypeBranch(tree);
|
|
605
|
+
if (typeBranch == null)
|
|
606
|
+
throw new Error(`RulesPackage <${this.rulesPackageId}> doesn't have a "${this.typeBranchKey}" type branch.`);
|
|
607
|
+
const [_rulesPackageId, key, ..._tailKeys] = this.primaryPathKeys;
|
|
608
|
+
let result;
|
|
609
|
+
if (typeBranch instanceof Map)
|
|
610
|
+
result = typeBranch.get(key);
|
|
611
|
+
else
|
|
612
|
+
result = typeBranch[key];
|
|
613
|
+
if (result == null)
|
|
614
|
+
throw new Error(`Couldn't find key <${key}> in <${this.rulesPackageId}.${this.typeBranchKey}>`);
|
|
615
|
+
return result;
|
|
616
|
+
}
|
|
617
|
+
/** @internal */
|
|
618
|
+
_matchRulesPackages(tree = IdParser.tree) {
|
|
619
|
+
if (tree == null)
|
|
620
|
+
throw new Error(`No Datasworn tree found -- set the static property ${IdParser.constructor.name}#tree, or provide one as a parameter.`);
|
|
621
|
+
return IdParser._getMatchesFrom(tree, this.rulesPackageId);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* @param forEach Optional function to apply to each match. If it returns `true`, the matcher will exit early and return whatever results it currently has.
|
|
625
|
+
* @internal
|
|
626
|
+
*/
|
|
627
|
+
_getMatchesUnsafe(tree, forEach) {
|
|
628
|
+
const pkgs = this._matchRulesPackages(tree);
|
|
629
|
+
const results = new Map();
|
|
630
|
+
const [_rulesPackageId, nextKey] = this.primaryPathKeys;
|
|
631
|
+
const joiner = this instanceof EmbeddedId ? TypeSep : PathKeySep;
|
|
632
|
+
for (const [pkgId, pkg] of pkgs) {
|
|
633
|
+
// @ts-expect-error
|
|
634
|
+
const typeBranch = pkg[this.typeBranchKey];
|
|
635
|
+
if (typeBranch == null)
|
|
636
|
+
continue;
|
|
637
|
+
const matches = IdParser._getMatchesFrom(typeBranch, nextKey);
|
|
638
|
+
for (const [key, match] of matches) {
|
|
639
|
+
const path = [pkgId, key].join(joiner);
|
|
640
|
+
// computing the ID for this *position*, not the value of the _id property;
|
|
641
|
+
// this is done so that a node that overrides this position's default node retains its own ID, but still matches as intended.
|
|
642
|
+
const positionId = [this.compositeTypeId, path].join(PrefixSep);
|
|
643
|
+
results.set(positionId, match);
|
|
644
|
+
if (typeof forEach === 'function' && forEach(positionId, match))
|
|
645
|
+
return results;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return results;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
class EmbeddingId extends IdParser {
|
|
652
|
+
/**
|
|
653
|
+
* Create a child EmbeddedId with a given type and key.
|
|
654
|
+
*/
|
|
655
|
+
createEmbeddedIdChild(embeddedTypeId, key) {
|
|
656
|
+
return new EmbeddedId(this, embeddedTypeId, key);
|
|
657
|
+
}
|
|
658
|
+
get embeddableTypes() {
|
|
659
|
+
return TypeId.getEmbeddableTypes(this.typeId, this instanceof EmbeddedId);
|
|
660
|
+
}
|
|
661
|
+
assignIdsIn(node, recursive, index) {
|
|
662
|
+
const result = super.assignIdsIn(node, recursive, index);
|
|
663
|
+
if (recursive)
|
|
664
|
+
for (const embedTypeId of this.getEmbeddableTypes()) {
|
|
665
|
+
const propertyPath = TypeId.getEmbeddedPropertyKey(embedTypeId);
|
|
666
|
+
if (typeof node !== 'object')
|
|
667
|
+
continue;
|
|
668
|
+
// Support nested property paths like 'trigger.conditions'
|
|
669
|
+
if (!hasNestedProperty(node, propertyPath))
|
|
670
|
+
continue;
|
|
671
|
+
const childNodes = getNestedProperty(node, propertyPath);
|
|
672
|
+
if (childNodes == null)
|
|
673
|
+
continue;
|
|
674
|
+
if (Array.isArray(childNodes)) {
|
|
675
|
+
this.#assignEmbeddedIdsInArray(childNodes, embedTypeId, recursive, index);
|
|
676
|
+
}
|
|
677
|
+
else if (childNodes instanceof Map) {
|
|
678
|
+
this.#assignEmbeddedIdsInMap(childNodes, embedTypeId, recursive, index);
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
this.#assignEmbeddedIdsInRecord(childNodes, embedTypeId, recursive, index);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return result;
|
|
685
|
+
}
|
|
686
|
+
#assignEmbeddedIdsInMap(children, nextTypeId, recursive, index) {
|
|
687
|
+
children.forEach((childNode, k) => this.createEmbeddedIdChild(nextTypeId, k.toString()).assignIdsIn(childNode, recursive, index));
|
|
688
|
+
}
|
|
689
|
+
#assignEmbeddedIdsInRecord(children, nextTypeId, recursive, index) {
|
|
690
|
+
for (const k in children) {
|
|
691
|
+
if (!Object.hasOwn(children, k))
|
|
692
|
+
continue;
|
|
693
|
+
const childNode = children[k];
|
|
694
|
+
if (childNode == null)
|
|
695
|
+
continue;
|
|
696
|
+
const childParser = this.createEmbeddedIdChild(nextTypeId, k);
|
|
697
|
+
childParser.assignIdsIn(childNode, recursive, index);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
#assignEmbeddedIdsInArray(children, nextTypeId, recursive, index) {
|
|
701
|
+
children.forEach((childNode, i) => this.createEmbeddedIdChild(nextTypeId, i.toString()).assignIdsIn(childNode, recursive, index));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
class NonCollectableId extends EmbeddingId {
|
|
705
|
+
get isRecursive() {
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
constructor(typeId, rulesPackage, key) {
|
|
709
|
+
const pathSegment = [rulesPackage, key].join(PathKeySep);
|
|
710
|
+
super({
|
|
711
|
+
typeIds: [typeId],
|
|
712
|
+
pathSegments: [pathSegment]
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
class CollectableId extends EmbeddingId {
|
|
717
|
+
get isRecursive() {
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
get embeddableTypes() {
|
|
721
|
+
return TypeId.getEmbeddableTypes(this.primaryTypeId, false);
|
|
722
|
+
}
|
|
723
|
+
constructor(typeId, rulesPackage, ...pathKeys) {
|
|
724
|
+
const pathSegment = [rulesPackage, ...pathKeys].join(PathKeySep);
|
|
725
|
+
super({
|
|
726
|
+
typeIds: [typeId],
|
|
727
|
+
pathSegments: [pathSegment]
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
get collectionAncestorKeys() {
|
|
731
|
+
// 1st element is the RulesPackageId, which isn't counted.
|
|
732
|
+
// last element is the key of this collectable, which isn't counted.
|
|
733
|
+
return this.primaryPathKeys.slice(1, -1);
|
|
734
|
+
}
|
|
735
|
+
get recursionDepth() {
|
|
736
|
+
return this.collectionAncestorKeys.length;
|
|
737
|
+
}
|
|
738
|
+
get parentTypeId() {
|
|
739
|
+
return TypeId.getCollectionOf(this.typeId);
|
|
740
|
+
}
|
|
741
|
+
getCollectionIdParent() {
|
|
742
|
+
const ancestorKeys = this.primaryPathKeys.slice(0, -1);
|
|
743
|
+
return new CollectionId(this.parentTypeId, ...ancestorKeys);
|
|
744
|
+
}
|
|
745
|
+
/** @internal */
|
|
746
|
+
_getUnsafe(tree) {
|
|
747
|
+
const parent = this.getCollectionIdParent();
|
|
748
|
+
const parentNode = parent._getUnsafe(tree);
|
|
749
|
+
// console.log(`<${this}> got parent`, parentNode)
|
|
750
|
+
const thisKey = this.primaryPathKeys.at(-1);
|
|
751
|
+
const { contents } = parentNode;
|
|
752
|
+
let result;
|
|
753
|
+
if (contents instanceof Map)
|
|
754
|
+
result = contents.get(thisKey);
|
|
755
|
+
else if (Object.hasOwn(contents, thisKey))
|
|
756
|
+
result = contents[thisKey];
|
|
757
|
+
if (result == null)
|
|
758
|
+
throw new Error(`No result for <${this.toString()}>`);
|
|
759
|
+
return result;
|
|
760
|
+
}
|
|
761
|
+
/** @internal */
|
|
762
|
+
_getMatchesUnsafe(tree, forEach) {
|
|
763
|
+
const parentId = this.getCollectionIdParent();
|
|
764
|
+
let matches;
|
|
765
|
+
const thisKey = this.primaryPathKeys.at(-1);
|
|
766
|
+
// these aren't the targets, so they don't get forEach passed to them
|
|
767
|
+
const parentMatches = parentId._getMatchesUnsafe(tree);
|
|
768
|
+
for (const [parentIdStr, parentMatch] of parentMatches) {
|
|
769
|
+
const contents = parentMatch[ContentsKey];
|
|
770
|
+
if (contents == null)
|
|
771
|
+
continue;
|
|
772
|
+
const collectables = IdParser._getMatchesFrom(contents, thisKey);
|
|
773
|
+
for (const [currentKey, match] of collectables) {
|
|
774
|
+
const [_parentTypeId, parentPath] = parentIdStr.split(PrefixSep);
|
|
775
|
+
const currentPath = parentPath + PathKeySep + currentKey;
|
|
776
|
+
const currentId = this.compositeTypeId + PrefixSep + currentPath;
|
|
777
|
+
matches ||= new Map();
|
|
778
|
+
matches.set(currentId, match);
|
|
779
|
+
if (typeof forEach === 'function' && forEach(currentId, match))
|
|
780
|
+
return matches;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return matches ?? new Map();
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
class CollectionId extends IdParser {
|
|
787
|
+
constructor(typeId, rulesPackage, ...pathKeys) {
|
|
788
|
+
const pathSegment = [rulesPackage, ...pathKeys].join(PathKeySep);
|
|
789
|
+
super({
|
|
790
|
+
typeIds: [typeId],
|
|
791
|
+
pathSegments: [pathSegment]
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
get isRecursive() {
|
|
795
|
+
return true;
|
|
796
|
+
}
|
|
797
|
+
get recursionDepth() {
|
|
798
|
+
// first element is the RulesPackageId, which isn't counted.
|
|
799
|
+
return this.collectionAncestorKeys.length;
|
|
800
|
+
}
|
|
801
|
+
get collectionAncestorKeys() {
|
|
802
|
+
// 1st key is the RulesPackageId, so it's omitted
|
|
803
|
+
// last key is this Collection node -- it's omitted too
|
|
804
|
+
// everything but the last key, which belongs to the node itself
|
|
805
|
+
return this.primaryPathKeys.slice(1, -1);
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Create an ID representing a {@link CollectableId} child of this CollectionId, using the provided key.
|
|
809
|
+
* @example
|
|
810
|
+
* ```typescript
|
|
811
|
+
* const collection = IdParser.parse('oracle_collection:starforged/core')
|
|
812
|
+
* const collectable = collection.createCollectableIdChild('action')
|
|
813
|
+
* console.log(collectable.toString()) // 'oracle_rollable:starforged/core/action'
|
|
814
|
+
* ```
|
|
815
|
+
*/
|
|
816
|
+
createCollectableIdChild(key) {
|
|
817
|
+
return new CollectableId(TypeId.getCollectableOf(this.primaryTypeId), ...this.primaryPathKeys, key);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Create an ID representing a Collection child of this CollectionId, using the provided key.
|
|
821
|
+
* @throws If a child collection ID can't be created because the maximum recursion depth has been reached.
|
|
822
|
+
* @see {@link COLLECTION_DEPTH_MAX}
|
|
823
|
+
* @example
|
|
824
|
+
* ```typescript
|
|
825
|
+
* const collection = IdParser.parse('oracle_collection:starforged/planet')
|
|
826
|
+
* const childCollection = collection.createCollectionIdChild('furnace')
|
|
827
|
+
* console.log(childCollection.toString()) // 'oracle_collection:starforged/planet/furnace'
|
|
828
|
+
* ```
|
|
829
|
+
*/
|
|
830
|
+
createCollectionIdChild(key) {
|
|
831
|
+
if (this.recursionDepth >= COLLECTION_DEPTH_MAX)
|
|
832
|
+
throw new ParseError(this.toString(), `Cant't generate a child collection ID because this ID has reached the maximum recursion depth (${COLLECTION_DEPTH_MAX})`);
|
|
833
|
+
return new CollectionId(this.primaryTypeId, ...this.primaryPathKeys, key);
|
|
834
|
+
}
|
|
835
|
+
assignIdsIn(node, recursive = true, index) {
|
|
836
|
+
// run this up front so the log ordering is more intuitive
|
|
837
|
+
const base = super.assignIdsIn(node, recursive, index);
|
|
838
|
+
if (recursive) {
|
|
839
|
+
if (ContentsKey in node && node[ContentsKey] != null)
|
|
840
|
+
for (const childKey in node[ContentsKey]) {
|
|
841
|
+
const childNode = node[ContentsKey][childKey];
|
|
842
|
+
if (childNode == null)
|
|
843
|
+
continue;
|
|
844
|
+
const childParser = this.createCollectableIdChild(childKey);
|
|
845
|
+
childParser.assignIdsIn(childNode, recursive, index);
|
|
846
|
+
}
|
|
847
|
+
if (CollectionsKey in node && node[CollectionsKey] != null)
|
|
848
|
+
for (const childKey in node[CollectionsKey]) {
|
|
849
|
+
const childCollection = node[CollectionsKey][childKey];
|
|
850
|
+
if (childCollection == null)
|
|
851
|
+
continue;
|
|
852
|
+
const childParser = this.createCollectionIdChild(childKey);
|
|
853
|
+
childParser.assignIdsIn(childCollection, recursive, index);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return base;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Returns a CollectionId representing the parent collection of this ID.
|
|
860
|
+
* @throws If a parent collection ID isn't possible (because this ID doesn't have a parent collection.)
|
|
861
|
+
* @example
|
|
862
|
+
* ```typescript
|
|
863
|
+
* const collection = IdParser.parse('oracle_collection:starforged/planet/jungle/settlements')
|
|
864
|
+
* const parentCollection = collection.getCollectionIdParent()
|
|
865
|
+
* console.log(parentCollection.toString()) // 'oracle_collection:starforged/planet/jungle'
|
|
866
|
+
* ```
|
|
867
|
+
*/
|
|
868
|
+
getCollectionIdParent() {
|
|
869
|
+
if (this.collectionAncestorKeys.length === 0)
|
|
870
|
+
throw new ParseError(this.toString(), `Can't generate a parent ID because this ID has no ancestors.`);
|
|
871
|
+
return new CollectionId(this.primaryTypeId, this.rulesPackageId, ...this.collectionAncestorKeys);
|
|
872
|
+
}
|
|
873
|
+
/** @internal */
|
|
874
|
+
_getUnsafe(tree) {
|
|
875
|
+
const ancestorNode = super._getUnsafe(tree);
|
|
876
|
+
const [_rulesPackage, _ancestorKey, ...keys] = this.primaryPathKeys;
|
|
877
|
+
const ancestorNodes = [ancestorNode];
|
|
878
|
+
for (const key of keys) {
|
|
879
|
+
const currentNode = ancestorNodes.at(-1);
|
|
880
|
+
if (!(CollectionsKey in currentNode))
|
|
881
|
+
throw new Error(`Couldn't find collection <${key}> in <${currentNode._id}>`);
|
|
882
|
+
const nextNode = currentNode[CollectionsKey][key];
|
|
883
|
+
if (nextNode == null)
|
|
884
|
+
throw new Error(`Expected collection <${key}> in <${currentNode._id}>, but got ${String(nextNode)}`);
|
|
885
|
+
ancestorNodes.push(nextNode);
|
|
886
|
+
}
|
|
887
|
+
return ancestorNodes.at(-1);
|
|
888
|
+
}
|
|
889
|
+
static #getPositionId(path, node) {
|
|
890
|
+
const pathStr = path.join(PathKeySep);
|
|
891
|
+
return [node.type, pathStr].join(PrefixSep);
|
|
892
|
+
}
|
|
893
|
+
/** @internal */
|
|
894
|
+
static _recurseMatches(from, currentPath, nextPath, matches = new Map(), forEach, depth = 0) {
|
|
895
|
+
// no further traversal needed
|
|
896
|
+
if (nextPath.length === 0) {
|
|
897
|
+
// from.type is fine here since collections can't be embedded.
|
|
898
|
+
const positionId = CollectionId.#getPositionId(currentPath, from);
|
|
899
|
+
if (typeof forEach === 'function')
|
|
900
|
+
forEach(positionId, from);
|
|
901
|
+
return matches.set(positionId, from);
|
|
902
|
+
}
|
|
903
|
+
if (depth > COLLECTION_DEPTH_MAX) {
|
|
904
|
+
IdParser.logger.warn(`Exceeded max collection depth (${COLLECTION_DEPTH_MAX}) @ <${CollectionId.#getPositionId(currentPath, from)}>`);
|
|
905
|
+
return matches;
|
|
906
|
+
}
|
|
907
|
+
const [keyToMatch, ...tailKeys] = nextPath;
|
|
908
|
+
const childCollections = from[CollectionsKey];
|
|
909
|
+
if (childCollections == null)
|
|
910
|
+
return matches;
|
|
911
|
+
const childMatches = IdParser._getMatchesFrom(childCollections, keyToMatch);
|
|
912
|
+
for (const [childKey, child] of childMatches) {
|
|
913
|
+
const currentChildPath = [...currentPath, childKey];
|
|
914
|
+
if (TypeGuard.Globstar(keyToMatch))
|
|
915
|
+
// recurse through children without consuming the globstar
|
|
916
|
+
for (const [matchId, match] of CollectionId._recurseMatches(child, currentChildPath, nextPath, matches, forEach, depth + 1))
|
|
917
|
+
matches.set(matchId, match);
|
|
918
|
+
// regular key + * matches
|
|
919
|
+
for (const [matchId, match] of CollectionId._recurseMatches(child, currentChildPath, tailKeys, matches, forEach, depth + 1))
|
|
920
|
+
matches.set(matchId, match);
|
|
921
|
+
}
|
|
922
|
+
return matches;
|
|
923
|
+
}
|
|
924
|
+
/** @internal */
|
|
925
|
+
_getMatchesUnsafe(tree, forEach) {
|
|
926
|
+
const pkgs = this._matchRulesPackages(tree);
|
|
927
|
+
// defer creating this until we need it
|
|
928
|
+
let matches = new Map();
|
|
929
|
+
const [_pkgMatchKey, matchKey, ...tailKeys] = this.primaryPathKeys;
|
|
930
|
+
for (const [rulesPackageId, pkg] of pkgs) {
|
|
931
|
+
const typeBranch = pkg[this.typeBranchKey];
|
|
932
|
+
if (typeBranch == null)
|
|
933
|
+
continue;
|
|
934
|
+
const collectionMatches = IdParser._getMatchesFrom(typeBranch, matchKey);
|
|
935
|
+
for (const [collectionKey, collection] of collectionMatches) {
|
|
936
|
+
const currentCollectionPath = [rulesPackageId, collectionKey];
|
|
937
|
+
if (TypeGuard.Globstar(rulesPackageId)) {
|
|
938
|
+
// carry forward the rules package globstar if it's present
|
|
939
|
+
matches = new Map([
|
|
940
|
+
...CollectionId._recurseMatches(collection, currentCollectionPath, [GlobstarString, matchKey, ...tailKeys], matches, forEach),
|
|
941
|
+
...CollectionId._recurseMatches(collection, currentCollectionPath, [matchKey, ...tailKeys], matches, forEach)
|
|
942
|
+
]);
|
|
943
|
+
}
|
|
944
|
+
else if (TypeGuard.Globstar(matchKey)) {
|
|
945
|
+
// carry forward current key if it's a globstar
|
|
946
|
+
matches = new Map([
|
|
947
|
+
...CollectionId._recurseMatches(collection, currentCollectionPath, [matchKey, ...tailKeys], matches, forEach),
|
|
948
|
+
...CollectionId._recurseMatches(collection, currentCollectionPath, tailKeys, matches, forEach)
|
|
949
|
+
]);
|
|
950
|
+
}
|
|
951
|
+
else
|
|
952
|
+
matches = CollectionId._recurseMatches(collection, currentCollectionPath, tailKeys, matches, forEach);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return matches ?? new Map();
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
// @ts-expect-error Complex generic type constraints
|
|
959
|
+
class EmbeddedId extends EmbeddingId {
|
|
960
|
+
#parent;
|
|
961
|
+
/**
|
|
962
|
+
* Returns the embedding ID parent of this ID.
|
|
963
|
+
*/
|
|
964
|
+
getEmbeddingIdParent() {
|
|
965
|
+
return this.#parent;
|
|
966
|
+
}
|
|
967
|
+
_getUnsafe(tree) {
|
|
968
|
+
const parentNode = this.#parent.get(tree);
|
|
969
|
+
const propertyPath = TypeId.getEmbeddedPropertyKey(this.typeId);
|
|
970
|
+
// Support nested property paths like 'trigger.conditions'
|
|
971
|
+
const obj = getNestedProperty(parentNode, propertyPath);
|
|
972
|
+
return obj?.[this.pathSegments.at(-1)];
|
|
973
|
+
}
|
|
974
|
+
_getPathRegExpSource() {
|
|
975
|
+
let basePath = this.#parent._getPathRegExpSource();
|
|
976
|
+
const [_primaryPathSegment, ...secondaryPathSegments] = this.pathSegments;
|
|
977
|
+
for (const segment of secondaryPathSegments) {
|
|
978
|
+
basePath += `\\${TypeSep}`;
|
|
979
|
+
const keys = segment.split(PathKeySep);
|
|
980
|
+
for (const key of keys) {
|
|
981
|
+
switch (true) {
|
|
982
|
+
// FIXME: this doesn't account for globstars because these are always single-element path segments right now
|
|
983
|
+
case TypeGuard.AnyWildcard(key):
|
|
984
|
+
basePath += Pattern.DictKeyElement.source;
|
|
985
|
+
break;
|
|
986
|
+
default:
|
|
987
|
+
basePath += key;
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return basePath;
|
|
993
|
+
}
|
|
994
|
+
constructor(parent, typeId, key) {
|
|
995
|
+
const options = {
|
|
996
|
+
typeIds: [...parent.typeIds, typeId],
|
|
997
|
+
pathSegments: [...parent.pathSegments, key.toString()]
|
|
998
|
+
};
|
|
999
|
+
super(options);
|
|
1000
|
+
this.#parent = parent;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
export { CollectionId, EmbeddedId, IdParser };
|
|
1004
|
+
//# sourceMappingURL=IdParser.js.map
|