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