@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.
Files changed (201) hide show
  1. package/README.md +11 -0
  2. package/dist/cjs/Datasworn.d.ts +5012 -0
  3. package/dist/cjs/Datasworn.d.ts.map +1 -0
  4. package/dist/cjs/Datasworn.js +3 -0
  5. package/dist/cjs/Datasworn.js.map +1 -0
  6. package/dist/cjs/DataswornSource.d.ts +5025 -0
  7. package/dist/cjs/DataswornSource.d.ts.map +1 -0
  8. package/dist/cjs/DataswornSource.js +3 -0
  9. package/dist/cjs/DataswornSource.js.map +1 -0
  10. package/dist/cjs/DataswornTree.d.ts +9 -0
  11. package/dist/cjs/DataswornTree.d.ts.map +1 -0
  12. package/dist/cjs/DataswornTree.js +25 -0
  13. package/dist/cjs/DataswornTree.js.map +1 -0
  14. package/dist/cjs/DataswornVersion.d.ts +8 -0
  15. package/dist/cjs/DataswornVersion.d.ts.map +1 -0
  16. package/dist/cjs/DataswornVersion.js +10 -0
  17. package/dist/cjs/DataswornVersion.js.map +1 -0
  18. package/dist/cjs/Errors.d.ts +5 -0
  19. package/dist/cjs/Errors.d.ts.map +1 -0
  20. package/dist/cjs/Errors.js +11 -0
  21. package/dist/cjs/Errors.js.map +1 -0
  22. package/dist/cjs/IdElements/CONST.d.ts +37 -0
  23. package/dist/cjs/IdElements/CONST.d.ts.map +1 -0
  24. package/dist/cjs/IdElements/CONST.js +26 -0
  25. package/dist/cjs/IdElements/CONST.js.map +1 -0
  26. package/dist/cjs/IdElements/PathKeys.d.ts +13 -0
  27. package/dist/cjs/IdElements/PathKeys.d.ts.map +1 -0
  28. package/dist/cjs/IdElements/PathKeys.js +3 -0
  29. package/dist/cjs/IdElements/PathKeys.js.map +1 -0
  30. package/dist/cjs/IdElements/Pattern.d.ts +13 -0
  31. package/dist/cjs/IdElements/Pattern.d.ts.map +1 -0
  32. package/dist/cjs/IdElements/Pattern.js +20 -0
  33. package/dist/cjs/IdElements/Pattern.js.map +1 -0
  34. package/dist/cjs/IdElements/TypeGuard.d.ts +25 -0
  35. package/dist/cjs/IdElements/TypeGuard.d.ts.map +1 -0
  36. package/dist/cjs/IdElements/TypeGuard.js +72 -0
  37. package/dist/cjs/IdElements/TypeGuard.js.map +1 -0
  38. package/dist/cjs/IdElements/TypeId.d.ts +114 -0
  39. package/dist/cjs/IdElements/TypeId.d.ts.map +1 -0
  40. package/dist/cjs/IdElements/TypeId.js +259 -0
  41. package/dist/cjs/IdElements/TypeId.js.map +1 -0
  42. package/dist/cjs/IdElements/index.d.ts +6 -0
  43. package/dist/cjs/IdElements/index.d.ts.map +1 -0
  44. package/dist/cjs/IdElements/index.js +48 -0
  45. package/dist/cjs/IdElements/index.js.map +1 -0
  46. package/dist/cjs/IdParser.d.ts +380 -0
  47. package/dist/cjs/IdParser.d.ts.map +1 -0
  48. package/dist/cjs/IdParser.js +1012 -0
  49. package/dist/cjs/IdParser.js.map +1 -0
  50. package/dist/cjs/Migrations/index.d.ts +2 -0
  51. package/dist/cjs/Migrations/index.d.ts.map +1 -0
  52. package/dist/cjs/Migrations/index.js +18 -0
  53. package/dist/cjs/Migrations/index.js.map +1 -0
  54. package/dist/cjs/Migrations/updateIds.d.ts +34 -0
  55. package/dist/cjs/Migrations/updateIds.d.ts.map +1 -0
  56. package/dist/cjs/Migrations/updateIds.js +141 -0
  57. package/dist/cjs/Migrations/updateIds.js.map +1 -0
  58. package/dist/cjs/StringId.d.ts +47 -0
  59. package/dist/cjs/StringId.d.ts.map +1 -0
  60. package/dist/cjs/StringId.js +3 -0
  61. package/dist/cjs/StringId.js.map +1 -0
  62. package/dist/cjs/Tree.d.ts +5 -0
  63. package/dist/cjs/Tree.d.ts.map +1 -0
  64. package/dist/cjs/Tree.js +3 -0
  65. package/dist/cjs/Tree.js.map +1 -0
  66. package/dist/cjs/TypeNode.d.ts +93 -0
  67. package/dist/cjs/TypeNode.d.ts.map +1 -0
  68. package/dist/cjs/TypeNode.js +3 -0
  69. package/dist/cjs/TypeNode.js.map +1 -0
  70. package/dist/cjs/Utils/Array.d.ts +16 -0
  71. package/dist/cjs/Utils/Array.d.ts.map +1 -0
  72. package/dist/cjs/Utils/Array.js +14 -0
  73. package/dist/cjs/Utils/Array.js.map +1 -0
  74. package/dist/cjs/Utils/Id.d.ts +75 -0
  75. package/dist/cjs/Utils/Id.d.ts.map +1 -0
  76. package/dist/cjs/Utils/Id.js +3 -0
  77. package/dist/cjs/Utils/Id.js.map +1 -0
  78. package/dist/cjs/Utils/Number.d.ts +5 -0
  79. package/dist/cjs/Utils/Number.d.ts.map +1 -0
  80. package/dist/cjs/Utils/Number.js +4 -0
  81. package/dist/cjs/Utils/Number.js.map +1 -0
  82. package/dist/cjs/Utils/Sort.d.ts +17 -0
  83. package/dist/cjs/Utils/Sort.d.ts.map +1 -0
  84. package/dist/cjs/Utils/Sort.js +277 -0
  85. package/dist/cjs/Utils/Sort.js.map +1 -0
  86. package/dist/cjs/Utils/String.d.ts +4 -0
  87. package/dist/cjs/Utils/String.d.ts.map +1 -0
  88. package/dist/cjs/Utils/String.js +3 -0
  89. package/dist/cjs/Utils/String.js.map +1 -0
  90. package/dist/cjs/index.d.ts +10 -0
  91. package/dist/cjs/index.d.ts.map +1 -0
  92. package/dist/cjs/index.js +47 -0
  93. package/dist/cjs/index.js.map +1 -0
  94. package/dist/cjs/mergeExpansion.d.ts +3 -0
  95. package/dist/cjs/mergeExpansion.d.ts.map +1 -0
  96. package/dist/cjs/mergeExpansion.js +113 -0
  97. package/dist/cjs/mergeExpansion.js.map +1 -0
  98. package/dist/cjs/package.json +1 -0
  99. package/dist/cjs/tsconfig.tsbuildinfo +1 -0
  100. package/dist/esm/Datasworn.d.ts +5012 -0
  101. package/dist/esm/Datasworn.d.ts.map +1 -0
  102. package/dist/esm/Datasworn.js +2 -0
  103. package/dist/esm/Datasworn.js.map +1 -0
  104. package/dist/esm/DataswornSource.d.ts +5025 -0
  105. package/dist/esm/DataswornSource.d.ts.map +1 -0
  106. package/dist/esm/DataswornSource.js +2 -0
  107. package/dist/esm/DataswornSource.js.map +1 -0
  108. package/dist/esm/DataswornTree.d.ts +9 -0
  109. package/dist/esm/DataswornTree.d.ts.map +1 -0
  110. package/dist/esm/DataswornTree.js +21 -0
  111. package/dist/esm/DataswornTree.js.map +1 -0
  112. package/dist/esm/DataswornVersion.d.ts +8 -0
  113. package/dist/esm/DataswornVersion.d.ts.map +1 -0
  114. package/dist/esm/DataswornVersion.js +7 -0
  115. package/dist/esm/DataswornVersion.js.map +1 -0
  116. package/dist/esm/Errors.d.ts +5 -0
  117. package/dist/esm/Errors.d.ts.map +1 -0
  118. package/dist/esm/Errors.js +7 -0
  119. package/dist/esm/Errors.js.map +1 -0
  120. package/dist/esm/IdElements/CONST.d.ts +37 -0
  121. package/dist/esm/IdElements/CONST.d.ts.map +1 -0
  122. package/dist/esm/IdElements/CONST.js +23 -0
  123. package/dist/esm/IdElements/CONST.js.map +1 -0
  124. package/dist/esm/IdElements/PathKeys.d.ts +13 -0
  125. package/dist/esm/IdElements/PathKeys.d.ts.map +1 -0
  126. package/dist/esm/IdElements/PathKeys.js +2 -0
  127. package/dist/esm/IdElements/PathKeys.js.map +1 -0
  128. package/dist/esm/IdElements/Pattern.d.ts +13 -0
  129. package/dist/esm/IdElements/Pattern.d.ts.map +1 -0
  130. package/dist/esm/IdElements/Pattern.js +18 -0
  131. package/dist/esm/IdElements/Pattern.js.map +1 -0
  132. package/dist/esm/IdElements/TypeGuard.d.ts +25 -0
  133. package/dist/esm/IdElements/TypeGuard.d.ts.map +1 -0
  134. package/dist/esm/IdElements/TypeGuard.js +67 -0
  135. package/dist/esm/IdElements/TypeGuard.js.map +1 -0
  136. package/dist/esm/IdElements/TypeId.d.ts +114 -0
  137. package/dist/esm/IdElements/TypeId.d.ts.map +1 -0
  138. package/dist/esm/IdElements/TypeId.js +257 -0
  139. package/dist/esm/IdElements/TypeId.js.map +1 -0
  140. package/dist/esm/IdElements/index.d.ts +6 -0
  141. package/dist/esm/IdElements/index.d.ts.map +1 -0
  142. package/dist/esm/IdElements/index.js +6 -0
  143. package/dist/esm/IdElements/index.js.map +1 -0
  144. package/dist/esm/IdParser.d.ts +380 -0
  145. package/dist/esm/IdParser.d.ts.map +1 -0
  146. package/dist/esm/IdParser.js +1004 -0
  147. package/dist/esm/IdParser.js.map +1 -0
  148. package/dist/esm/Migrations/index.d.ts +2 -0
  149. package/dist/esm/Migrations/index.d.ts.map +1 -0
  150. package/dist/esm/Migrations/index.js +2 -0
  151. package/dist/esm/Migrations/index.js.map +1 -0
  152. package/dist/esm/Migrations/updateIds.d.ts +34 -0
  153. package/dist/esm/Migrations/updateIds.d.ts.map +1 -0
  154. package/dist/esm/Migrations/updateIds.js +135 -0
  155. package/dist/esm/Migrations/updateIds.js.map +1 -0
  156. package/dist/esm/StringId.d.ts +47 -0
  157. package/dist/esm/StringId.d.ts.map +1 -0
  158. package/dist/esm/StringId.js +2 -0
  159. package/dist/esm/StringId.js.map +1 -0
  160. package/dist/esm/Tree.d.ts +5 -0
  161. package/dist/esm/Tree.d.ts.map +1 -0
  162. package/dist/esm/Tree.js +2 -0
  163. package/dist/esm/Tree.js.map +1 -0
  164. package/dist/esm/TypeNode.d.ts +93 -0
  165. package/dist/esm/TypeNode.d.ts.map +1 -0
  166. package/dist/esm/TypeNode.js +2 -0
  167. package/dist/esm/TypeNode.js.map +1 -0
  168. package/dist/esm/Utils/Array.d.ts +16 -0
  169. package/dist/esm/Utils/Array.d.ts.map +1 -0
  170. package/dist/esm/Utils/Array.js +11 -0
  171. package/dist/esm/Utils/Array.js.map +1 -0
  172. package/dist/esm/Utils/Id.d.ts +75 -0
  173. package/dist/esm/Utils/Id.d.ts.map +1 -0
  174. package/dist/esm/Utils/Id.js +2 -0
  175. package/dist/esm/Utils/Id.js.map +1 -0
  176. package/dist/esm/Utils/Number.d.ts +5 -0
  177. package/dist/esm/Utils/Number.d.ts.map +1 -0
  178. package/dist/esm/Utils/Number.js +3 -0
  179. package/dist/esm/Utils/Number.js.map +1 -0
  180. package/dist/esm/Utils/Sort.d.ts +17 -0
  181. package/dist/esm/Utils/Sort.d.ts.map +1 -0
  182. package/dist/esm/Utils/Sort.js +267 -0
  183. package/dist/esm/Utils/Sort.js.map +1 -0
  184. package/dist/esm/Utils/String.d.ts +4 -0
  185. package/dist/esm/Utils/String.d.ts.map +1 -0
  186. package/dist/esm/Utils/String.js +2 -0
  187. package/dist/esm/Utils/String.js.map +1 -0
  188. package/dist/esm/index.d.ts +10 -0
  189. package/dist/esm/index.d.ts.map +1 -0
  190. package/dist/esm/index.js +7 -0
  191. package/dist/esm/index.js.map +1 -0
  192. package/dist/esm/mergeExpansion.d.ts +3 -0
  193. package/dist/esm/mergeExpansion.d.ts.map +1 -0
  194. package/dist/esm/mergeExpansion.js +107 -0
  195. package/dist/esm/mergeExpansion.js.map +1 -0
  196. package/dist/esm/package.json +1 -0
  197. package/dist/esm/tsconfig.tsbuildinfo +1 -0
  198. package/json/datasworn-source.schema.json +11420 -0
  199. package/json/datasworn.schema.json +11354 -0
  200. package/migration/0.1.0/id_regex_map.json +21 -0
  201. 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