@datasworn-community/build-tools 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/cli/datasworn-build.js +12 -2
  2. package/dist/cli/datasworn-build.js.map +1 -1
  3. package/dist/config.d.ts +3 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/content-package-builder.d.ts +54 -0
  7. package/dist/content-package-builder.d.ts.map +1 -0
  8. package/dist/content-package-builder.js +159 -0
  9. package/dist/content-package-builder.js.map +1 -0
  10. package/dist/id-references.d.ts +20 -0
  11. package/dist/id-references.d.ts.map +1 -0
  12. package/dist/id-references.js +96 -0
  13. package/dist/id-references.js.map +1 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/rules-package-builder.d.ts +14 -0
  19. package/dist/rules-package-builder.d.ts.map +1 -1
  20. package/dist/rules-package-builder.js +14 -1
  21. package/dist/rules-package-builder.js.map +1 -1
  22. package/package.json +6 -3
  23. package/schema-source/builders/IdBuilder.ts +849 -0
  24. package/schema-source/schema/common/Id.ts +3 -1
  25. package/schema-source/schema/delve/common.ts +3 -1
  26. package/schema-source/schema/generic/CollectableNode.ts +3 -1
  27. package/schema-source/schema/generic/CollectionNode.ts +3 -1
  28. package/schema-source/schema/generic/EmbedOnlyNode.ts +3 -1
  29. package/schema-source/schema/generic/NonCollectableNode.ts +3 -1
  30. package/schema-source/schema/generic/PrimaryTypeNode.ts +3 -1
  31. package/schema-source/schema/rules/TagRule.ts +3 -1
  32. package/schema-source/schema/utils/EmbeddedNode.ts +3 -1
  33. package/schema-source/schema/utils/string.ts +2 -2
  34. package/schema-source/scripts/augmentations.ts +70 -0
  35. package/schema-source/scripts/codegen/schemaToTsDeclaration.ts +290 -0
  36. package/schema-source/scripts/codegen/tsDocTags.ts +75 -0
  37. package/schema-source/scripts/codegen/writeTypescriptTypes.ts +35 -0
  38. package/schema-source/scripts/const.ts +40 -2
  39. package/schema-source/scripts/datasworn/sort.ts +80 -0
  40. package/schema-source/scripts/json-typedef/typedef.ts +1138 -0
  41. package/schema-source/scripts/json-typedef/utils.ts +349 -0
  42. package/schema-source/scripts/schema/buildSchema.ts +109 -0
  43. package/schema-source/scripts/utils/Log.ts +15 -0
  44. package/schema-source/scripts/utils/readWrite.ts +180 -0
  45. package/schema-source/scripts/validation/ajv.ts +29 -0
  46. package/schema-source/scripts/validation/formats.ts +6 -0
  47. package/schema-source/scripts/validation/keywords.ts +36 -0
@@ -0,0 +1,849 @@
1
+ import {
2
+ type StringOptions,
3
+ type TRef,
4
+ type TString,
5
+ type TUnion,
6
+ Type
7
+ } from '@sinclair/typebox'
8
+ import { escapeRegExp, snakeCase } from 'lodash-es'
9
+ import type { PascalCase } from 'type-fest'
10
+ import { type Datasworn, IdElements, type TypeNode } from '@datasworn-community/core'
11
+ import { JsonTypeDef } from '../schema/Symbols.js'
12
+ import { pascalCase } from '../schema/utils/string.js'
13
+ import {
14
+ COLLECTION_DEPTH_MAX,
15
+ COLLECTION_DEPTH_MIN,
16
+ CollectionsKey,
17
+ ContentsKey,
18
+ PathKeySep,
19
+ PrefixSep,
20
+ TypeSep,
21
+ WildcardString
22
+ } from '../scripts/const.js'
23
+ import JtdType from '../scripts/json-typedef/typedef.js'
24
+
25
+ import TypeGuard = IdElements.TypeGuard
26
+ import Pattern = IdElements.Pattern
27
+ import TypeId = IdElements.TypeId
28
+
29
+ type RegexGroupType =
30
+ | 'none'
31
+ | 'capture_group'
32
+ | 'non_capturing_group'
33
+ | 'named_capture_group'
34
+
35
+ // inexact but enough to catch some errors
36
+ type ValidDictKey = Exclude<string, `_${string}` | `${number}`>
37
+
38
+ type Dict<T = unknown> = Record<ValidDictKey, T>
39
+
40
+ type PropKeysOfType<O, T> = {
41
+ [P in string & keyof O]-?: O[P] extends T ? P : never
42
+ }[keyof O & string]
43
+
44
+ // type DictPropKeyIn<O> = PropKeysOfType<O, Dict>
45
+ type DictPropKeyIn<O> = keyof O
46
+ // type ArrayPropKeyIn<O> = PropKeysOfType<O, Array<any>>
47
+ type ArrayPropKeyIn<O> = keyof O
48
+
49
+ type EntryInProp<
50
+ O,
51
+ P extends DictPropKeyIn<O> | ArrayPropKeyIn<O>
52
+ > = O[P] extends Array<any> ? O[P][number] : O[P][keyof O[P]]
53
+
54
+ /**
55
+ * Represents a single, slash-separated path element of a Datasworn ID.
56
+ * @template Origin The object type of the starting point of this path element.
57
+ */
58
+ abstract class PathSymbol<Origin, Key extends keyof Origin = keyof Origin> {
59
+ readonly inProperty: Key
60
+ abstract get pattern(): RegExp
61
+ abstract get wildcardPattern(): RegExp
62
+
63
+ abstract clone(...args: any[]): this
64
+
65
+ constructor(inProperty: Key) {
66
+ this.inProperty = inProperty
67
+ }
68
+ }
69
+
70
+ namespace PathSymbol {
71
+ export class RulesPackage extends PathSymbol<null> {
72
+ static readonly WILDCARD = new RegExp(
73
+ // TODO: proper handling for standalone RulesPackage globstars
74
+ // `(?:${Pattern.RulesPackageElement.source}|\\${WildcardString}|\\${WildcardString}\\${WildcardString})`
75
+ `(?:${Pattern.RulesPackageElement.source}|${escapeRegExp(WildcardString)})`
76
+ )
77
+
78
+ get pattern() {
79
+ return Pattern.RulesPackageElement
80
+ }
81
+
82
+ get wildcardPattern() {
83
+ return RulesPackage.WILDCARD
84
+ }
85
+
86
+ constructor() {
87
+ // @ts-expect-error
88
+ super(null)
89
+ }
90
+
91
+ clone() {
92
+ return new PathSymbol.RulesPackage() as this
93
+ }
94
+ }
95
+
96
+ export class DictKey<
97
+ Origin,
98
+ Prop extends DictPropKeyIn<Origin>
99
+ > extends PathSymbol<Origin, Prop> {
100
+ static readonly PATTERN = Pattern.DictKeyElement
101
+ static readonly WILDCARD = new RegExp(
102
+ `${DictKey.PATTERN.source}|\\${PathKeySep}\\${WildcardString}|\\${PathKeySep}\\${WildcardString}\\${WildcardString}`
103
+ )
104
+
105
+ public get minReps(): number {
106
+ return COLLECTION_DEPTH_MIN
107
+ }
108
+
109
+ public get maxReps(): number {
110
+ return COLLECTION_DEPTH_MIN
111
+ }
112
+
113
+ get pattern() {
114
+ return DictKey.PATTERN
115
+ }
116
+
117
+ get wildcardPattern() {
118
+ return DictKey.WILDCARD
119
+ }
120
+
121
+ clone() {
122
+ return new DictKey<Origin, Prop>(this.inProperty) as this
123
+ }
124
+
125
+ static get stringTemplateLiteral() {
126
+ // eslint-disable-next-line no-template-curly-in-string
127
+ return '${string}'
128
+ }
129
+
130
+ static renderDictKeys(min: number, max: number, wildcard = false): RegExp {
131
+ // exit early if there's nothing to do
132
+ if (min === 1 && max === 1)
133
+ return wildcard
134
+ ? PathSymbol.DictKey.WILDCARD
135
+ : PathSymbol.DictKey.PATTERN
136
+
137
+ const base = wildcard
138
+ ? `${IdPattern.PathSep}(?:${PathSymbol.DictKey.PATTERN.source}|${IdPattern.WILDCARD}|${IdPattern.GLOBSTAR})`
139
+ : IdPattern.PathSep + PathSymbol.DictKey.PATTERN.source
140
+
141
+ const minMax = min === max ? min.toString() : `${min},${max}`
142
+
143
+ const wrappedKey = `(?:${base}){${minMax}}`
144
+
145
+ if (!wildcard) return RegExp(wrappedKey)
146
+
147
+ return new RegExp(wrappedKey)
148
+ }
149
+
150
+ static repsStringTemplateLiteral(min: number, max: number) {
151
+ let variantsToGenerate = max - min + 1
152
+
153
+ const variants: string[] = []
154
+
155
+ while (variantsToGenerate > 0) {
156
+ const variantParts: string[] = []
157
+
158
+ for (let i = 0; i < variantsToGenerate; i++)
159
+ variantParts.push(DictKey.stringTemplateLiteral)
160
+
161
+ variants.push(variantParts.join(PathKeySep))
162
+
163
+ variantsToGenerate--
164
+ }
165
+ return variants
166
+ }
167
+
168
+ static reduceReps(...dictKeys: DictKey<any, any>[]) {
169
+ const min = dictKeys.reduce((a, b) => a + b.minReps, 0)
170
+ const max = dictKeys.reduce((a, b) => a + b.maxReps, 0)
171
+ return { min, max }
172
+ }
173
+ }
174
+
175
+ export class RecursiveDictKeys<
176
+ Origin,
177
+ Prop extends DictPropKeyIn<Origin>,
178
+ RecursiveProp extends DictPropKeyIn<EntryInProp<Origin, Prop>>
179
+ > extends DictKey<Origin, Prop> {
180
+ readonly recursiveProperty: RecursiveProp
181
+
182
+ override get minReps() {
183
+ return COLLECTION_DEPTH_MIN
184
+ }
185
+
186
+ override get maxReps() {
187
+ return COLLECTION_DEPTH_MAX
188
+ }
189
+
190
+ constructor(inProperty: Prop, recursiveProperty: RecursiveProp) {
191
+ super(inProperty)
192
+ this.recursiveProperty = recursiveProperty
193
+ }
194
+
195
+ clone<
196
+ CloneProp extends DictPropKeyIn<Origin> = Prop,
197
+ CloneRecursiveProp extends DictPropKeyIn<
198
+ EntryInProp<Origin, Prop>
199
+ > = RecursiveProp
200
+ >() {
201
+ // @ts-expect-error
202
+ return new RecursiveDictKeys<Origin, CloneProp, CloneRecursiveProp>(
203
+ this.inProperty,
204
+ this.recursiveProperty
205
+ ) as this
206
+ }
207
+ }
208
+
209
+ export class Index<
210
+ Origin,
211
+ Key extends ArrayPropKeyIn<Origin>
212
+ > extends PathSymbol<Origin, Key> {
213
+ clone() {
214
+ return new Index<Origin, Key>(this.inProperty) as this
215
+ }
216
+
217
+ get wildcardPattern() {
218
+ return new RegExp(`${Pattern.IndexElement.source}|\\${WildcardString}`)
219
+ }
220
+
221
+ override get pattern() {
222
+ return Pattern.IndexElement
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Represents a complete Datasworn ID, consisting of one or more type prefixes
229
+ * @template CurrentNode The type of the current (last) node in an IdPattern. Used to provide typechecks for keys when chaining `add` methods.
230
+ */
231
+ class IdPattern<
232
+ CurrentNode = Datasworn.RulesPackage
233
+ > extends Array<PathFormat> {
234
+ static readonly TypeSep = `\\${TypeSep}`
235
+
236
+ static readonly PrefixSep = PrefixSep
237
+
238
+ static readonly PathSep = `\\${PathKeySep}`
239
+
240
+ static readonly WILDCARD = `\\${WildcardString}`
241
+ static readonly GLOBSTAR = IdPattern.WILDCARD + IdPattern.WILDCARD
242
+
243
+ /**
244
+ * Get the left side of the ID pattern: the type prefix.
245
+ * @example `oracle_rollable`
246
+ * @example `move_category`
247
+ * @example `asset.ability.move`
248
+ */
249
+ getLeftSide(): RegExp {
250
+ return new RegExp(this.map(({ typeId }) => typeId).join(IdPattern.TypeSep))
251
+ }
252
+
253
+ get fullTypeId() {
254
+ return this.map(({ typeId }) => typeId).join(TypeSep)
255
+ }
256
+
257
+ /**
258
+ * Get the right hand side of the ID pattern: the path.
259
+ * @example `starforged/core/action`
260
+ * @example `classic/suffer`
261
+ * @example `classic/ritual/commune.0.commune`
262
+ */
263
+ getRightSide(
264
+ groups: RegexGroupType = 'named_capture_group',
265
+ wildcard = false
266
+ ): RegExp {
267
+ const result = this.map((part) =>
268
+ part.toRegexSource(groups, wildcard)
269
+ ).join(IdPattern.TypeSep)
270
+
271
+ return new RegExp(result)
272
+ }
273
+
274
+ /**
275
+ * Return a new copy of this IdPattern.
276
+ * @param primaryTypeId An optional override for the typeId of the *last* PathFormat.
277
+ */
278
+ clone(primaryTypeId?: string) {
279
+ return new IdPattern(
280
+ ...this.map((el, i, arr) =>
281
+ typeof primaryTypeId === 'string' && i === arr.length - 1
282
+ ? el.clone(primaryTypeId)
283
+ : el.clone()
284
+ )
285
+ ) as this
286
+ }
287
+
288
+ /** The type ID of the most recent primary node ancestor. */
289
+ get primaryTypeId() {
290
+ return this.at(0)?.typeId ?? null
291
+ }
292
+
293
+ /** The type of the node itself. */
294
+ get nodeTypeId() {
295
+ return this.at(-1)?.typeId ?? null
296
+ }
297
+
298
+ get current() {
299
+ return (this.at(-1) as PathFormat<CurrentNode>) ?? null
300
+ }
301
+
302
+ get isEmbedded() {
303
+ // if there's more than one path format, it's a multi-part path, and therefore an embedded ID
304
+ return this.length > 1
305
+ }
306
+
307
+ createEmbedded<T extends TypeId.Embeddable>(
308
+ typeId: T
309
+ ): IdPattern<Extract<TypeNode.Embedded, { type: typeof typeId }>> {
310
+ const withGroup = this.clone().addNewTypeGroup(typeId)
311
+
312
+ switch (TypeId.getEmbeddedPropertyType(typeId)) {
313
+ case 'array':
314
+ return withGroup.addIndex(TypeId.getEmbeddedPropertyKey(typeId) as any)
315
+
316
+ case 'dictionary':
317
+ return withGroup.addDictKey(
318
+ TypeId.getEmbeddedPropertyKey(typeId) as any
319
+ )
320
+
321
+ default:
322
+ throw new Error(
323
+ `Expected an embeddable TypeId, but got ${String(typeId)}`
324
+ )
325
+ }
326
+ }
327
+
328
+ addNewTypeGroup(typeId: string) {
329
+ this.push(new PathFormat(typeId))
330
+ return this
331
+ }
332
+
333
+ addDictKey<Key extends DictPropKeyIn<CurrentNode>>(inProperty: Key) {
334
+ this.current.addDictKey(inProperty)
335
+ return this as IdPattern<CurrentNode[Key][keyof CurrentNode[Key]]>
336
+ }
337
+
338
+ addRecursiveDictKeys<
339
+ Prop extends DictPropKeyIn<CurrentNode>,
340
+ RecursiveProp extends DictPropKeyIn<
341
+ CurrentNode[Prop][keyof CurrentNode[Prop]]
342
+ >
343
+ >(property: Prop, recursiveProperty: RecursiveProp) {
344
+ this.current.addRecursiveDictKeys(property, recursiveProperty)
345
+ return this as IdPattern<EntryInProp<CurrentNode, Prop>>
346
+ }
347
+
348
+ addIndex<Prop extends ArrayPropKeyIn<CurrentNode>>(inProperty: Prop) {
349
+ this.current.addIndex<Prop>(inProperty)
350
+
351
+ return this as IdPattern<EntryInProp<CurrentNode, Prop>>
352
+ }
353
+
354
+ toRegex(
355
+ lineDelimiters = false,
356
+ groups: RegexGroupType = 'named_capture_group',
357
+ wildcard = false
358
+ ) {
359
+ const base =
360
+ this.getLeftSide().source +
361
+ IdPattern.PrefixSep +
362
+ this.getRightSide(groups, wildcard).source
363
+ if (lineDelimiters) return new RegExp(`^${base}$`)
364
+ return new RegExp(base)
365
+ }
366
+
367
+ toWildcardSchema(options: StringOptions = {}) {
368
+ const typeName = this.fullTypeId.split(TypeSep).map(pascalCase).join('')
369
+ const $id = `${typeName}IdWildcard`
370
+ const description = `A wildcarded ${typeName}Id that can be used to match multiple ${typeName} objects.`
371
+ // named capture groups aren't universally available, so JSON schema docs recommend against using them
372
+ const pattern = this.toRegex(true, 'capture_group', true).source
373
+
374
+ return Type.String({
375
+ $id,
376
+ description,
377
+ pattern,
378
+ ...options
379
+ })
380
+ }
381
+
382
+ toSchema(options: StringOptions = {}) {
383
+ const typeName = this.fullTypeId.split(TypeSep).map(pascalCase).join('')
384
+ const indefiniteArticle = typeName.match(/^[aeiou]/i) ? 'an' : 'a'
385
+ const $id = `${typeName}Id`
386
+ const description = `A unique ID representing ${indefiniteArticle} ${typeName} object.`
387
+ // named capture groups aren't universally available, so JSON schema docs recommend against using them
388
+ const pattern = this.toRegex(true, 'capture_group').source
389
+
390
+ return Type.String({
391
+ $id,
392
+ description,
393
+ pattern,
394
+ ...options
395
+ })
396
+ }
397
+
398
+ static fromRoot(typeId: string) {
399
+ const f = new PathFormat(typeId, true)
400
+ return new IdPattern(f)
401
+ }
402
+
403
+ constructor(...formats: PathFormat[]) {
404
+ super()
405
+ this.push(...formats)
406
+ }
407
+
408
+ static createCollection<T extends TypeId.Collection>(
409
+ typeId: T,
410
+ typeRoot: TypeId.BranchKey<T>
411
+ ) {
412
+ return (
413
+ IdPattern.fromRoot(typeId)
414
+ // @ts-expect-error this happens because not all union members of OracleCollection have the 'collections' property
415
+ .addRecursiveDictKeys(typeRoot, CollectionsKey) as IdPattern<
416
+ TypeNode.Collection<T>
417
+ >
418
+ )
419
+ }
420
+
421
+ static createCollectable<T extends TypeId.Collectable>(
422
+ typeId: T,
423
+ typeRoot: TypeId.BranchKey<T>
424
+ ) {
425
+ return (
426
+ IdPattern.fromRoot(typeId)
427
+ // @ts-expect-error
428
+ .addRecursiveDictKeys(typeRoot, CollectionsKey)
429
+ // @ts-expect-error
430
+ .addDictKey(ContentsKey) as IdPattern<TypeNode.Collectable<T>>
431
+ )
432
+ }
433
+
434
+ static createNonCollectable<T extends TypeId.NonCollectable>(
435
+ typeId: T,
436
+ typeRoot: TypeId.BranchKey<T>
437
+ ) {
438
+ return IdPattern.fromRoot(typeId).addDictKey(typeRoot) as IdPattern<
439
+ TypeNode.NonCollectable<T>
440
+ >
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Represents a slash-separated path in a Datasworn ID.
446
+ * @template Origin The object type of the origin for this path.
447
+ */
448
+ class PathFormat<Origin = Datasworn.RulesPackage> extends Array<
449
+ PathSymbol<any>
450
+ > {
451
+ typeId: string
452
+
453
+ toRegexSource(group?: RegexGroupType, wildcard = false) {
454
+ let base = ''
455
+ let dictKeys: PathSymbol.DictKey<any, any>[] = []
456
+ const isOnlyPart = this.length === 1
457
+
458
+ // TODO: apply globstar *after*?, rather then in the static prop WILDCARD?
459
+
460
+ for (let i = 0; i < this.length; i++) {
461
+ const part = this[i]
462
+ const nextPart = this[i + 1]
463
+ const isFirstPart = i === 0
464
+
465
+ switch (true) {
466
+ case isOnlyPart &&
467
+ part instanceof PathSymbol.DictKey &&
468
+ !(part instanceof PathSymbol.RecursiveDictKeys):
469
+ base += `${part.pattern.source}|${IdPattern.WILDCARD}` // single-symbol paths can't be globstarred
470
+ break
471
+ case part instanceof PathSymbol.DictKey &&
472
+ nextPart instanceof PathSymbol.DictKey:
473
+ // DictKey chain not done; grab the current one and continue
474
+ dictKeys.push(part)
475
+ break
476
+ case part instanceof PathSymbol.DictKey &&
477
+ !(nextPart instanceof PathSymbol.DictKey): {
478
+ // end of this DictKey chain; assemble complete DictKey string, and reset array
479
+ dictKeys.push(part)
480
+ const { min, max } = PathSymbol.DictKey.reduceReps(...dictKeys)
481
+ const toAppend = PathSymbol.DictKey.renderDictKeys(
482
+ min,
483
+ max,
484
+ wildcard
485
+ ).source
486
+ if (
487
+ !toAppend.startsWith(`(?:${IdPattern.PathSep}`) &&
488
+ toAppend.length > 0
489
+ )
490
+ base += IdPattern.PathSep
491
+ // if (min > 0 && max > 2) {
492
+ // toAppend = toAppend.replace(
493
+ // `|${IdPattern.GLOBSTAR})`,
494
+ // `|(?:${IdPattern.GLOBSTAR}){${min === max ? min - 1 : min - 1 + ',' + (max - 1)}})`
495
+ // )
496
+ // }
497
+ base += toAppend
498
+ dictKeys = []
499
+ break
500
+ }
501
+ default:
502
+ // standard handling
503
+ if (!isFirstPart) base += IdPattern.PathSep
504
+ base += wildcard ? part.wildcardPattern.source : part.pattern.source
505
+ break
506
+ }
507
+ }
508
+
509
+ // if (canBeGlobstar) {
510
+ // const maxWildcardsAfterInitialGlobstar = this.length - 1
511
+ // const extraWildcards =
512
+ // maxWildcardsAfterInitialGlobstar === 1
513
+ // ? `(?:${IdPattern.PathSep}${IdPattern.WILDCARD})?`
514
+ // : maxWildcardsAfterInitialGlobstar > 1
515
+ // ? `(?:${IdPattern.PathSep}${IdPattern.WILDCARD}){0,${maxWildcardsAfterInitialGlobstar}}`
516
+ // : ''
517
+ // // valid to represent the whole fragment
518
+ // base = `${base}|${IdPattern.GLOBSTAR}${extraWildcards}`
519
+ // }
520
+
521
+ switch (group) {
522
+ case 'capture_group':
523
+ base = `(${base})`
524
+ break
525
+ case 'non_capturing_group':
526
+ base = `(?:${base})`
527
+ break
528
+ case 'named_capture_group':
529
+ base = `(?<${this.typeId}>${base})`
530
+ break
531
+ default:
532
+ // if (canBeGlobstar) base = `(?:${base})`
533
+ break
534
+ }
535
+ // Lone globstar is valid for any ID type (to match all IDs of the same type)
536
+
537
+ return base
538
+ }
539
+
540
+ readonly relative: boolean
541
+
542
+ constructor(typeId: string, relative = false) {
543
+ super()
544
+ if (relative) this.push(new PathSymbol.RulesPackage())
545
+ this.relative = relative
546
+ this.typeId = typeId
547
+ }
548
+
549
+ clone(newTypeId?: string) {
550
+ const clone = new PathFormat(newTypeId ?? this.typeId, this.relative)
551
+ for (const part of this) {
552
+ if (part instanceof PathSymbol.RulesPackage) continue
553
+ clone.push(part.clone())
554
+ }
555
+ return clone as this
556
+ }
557
+
558
+ createRelativeAs(newTypeId: string) {
559
+ return new PathFormat(newTypeId, true) as typeof this
560
+ }
561
+
562
+ addDictKey<Prop extends DictPropKeyIn<Origin>>(inProperty: Prop) {
563
+ this.push(new PathSymbol.DictKey<Origin, Prop>(inProperty))
564
+ return this as PathFormat<Origin[Prop][keyof Origin[Prop]]>
565
+ }
566
+
567
+ addRecursiveDictKeys<
568
+ Prop extends DictPropKeyIn<Origin>,
569
+ RecursiveProp extends DictPropKeyIn<EntryInProp<Origin, Prop>>
570
+ >(property: Prop, recursiveProperty: RecursiveProp) {
571
+ this.push(
572
+ new PathSymbol.RecursiveDictKeys<Origin, Prop, RecursiveProp>(
573
+ property,
574
+ recursiveProperty
575
+ )
576
+ )
577
+ return this as PathFormat<EntryInProp<Origin, Prop>>
578
+ }
579
+
580
+ addIndex<Prop extends ArrayPropKeyIn<Origin>>(inProperty: Prop) {
581
+ this.push(new PathSymbol.Index<Origin, Prop>(inProperty))
582
+ return this as PathFormat<EntryInProp<Origin, Prop>>
583
+ }
584
+ }
585
+
586
+ const patternIndex = {} as Record<TypeId.Any, IdPattern[]>
587
+
588
+ for (const typeId of TypeId.Primary) {
589
+ let pattern: IdPattern
590
+
591
+ switch (true) {
592
+ case TypeGuard.CollectionType(typeId):
593
+ pattern = IdPattern.createCollection(typeId, TypeId.getBranchKey(typeId))
594
+ break
595
+
596
+ case TypeGuard.CollectableType(typeId):
597
+ pattern = IdPattern.createCollectable(typeId, TypeId.getBranchKey(typeId))
598
+ break
599
+
600
+ case TypeGuard.NonCollectableType(typeId):
601
+ pattern = IdPattern.createNonCollectable(
602
+ typeId,
603
+ TypeId.getBranchKey(typeId)
604
+ )
605
+ break
606
+ default:
607
+ throw new Error(`Expected a primary type, got ${JSON.stringify(typeId)}`)
608
+ }
609
+
610
+ patternIndex[typeId] ||= []
611
+ patternIndex[typeId].push(pattern)
612
+
613
+ if (TypeId.canHaveEmbed(typeId)) computeEmbeddedTypes(pattern)
614
+ }
615
+
616
+ function computeEmbeddedTypes(pattern: IdPattern) {
617
+ const nodeTypeId = pattern.nodeTypeId as TypeId.Any
618
+
619
+ if (!TypeId.canHaveEmbed(nodeTypeId, pattern.isEmbedded)) return
620
+
621
+ const embedTypes = TypeId.getEmbeddableTypes(nodeTypeId, pattern.isEmbedded)
622
+
623
+ for (const embedType of embedTypes) {
624
+ const embedPattern = pattern.createEmbedded(embedType)
625
+
626
+ patternIndex[embedType] ||= []
627
+ patternIndex[embedType].push(embedPattern)
628
+
629
+ computeEmbeddedTypes(embedPattern)
630
+ }
631
+ }
632
+
633
+ // patterns.sort((a, b) =>
634
+ // a.getLeftSide().source.localeCompare(b.getLeftSide().source, 'en-US'))
635
+
636
+ type PrimaryNodePrefix<T extends TypeId.Primary = TypeId.Primary> =
637
+ `${PascalCase<T>}`
638
+ type EmbeddedPrimaryNodePrefix<
639
+ T extends TypeId.EmbeddablePrimary = TypeId.EmbeddablePrimary
640
+ > = `Embedded${PascalCase<T>}`
641
+ type PrimaryNodeUnionPrefix<
642
+ T extends TypeId.EmbeddablePrimary = TypeId.EmbeddablePrimary
643
+ > = `Any${PascalCase<T>}`
644
+ type EmbedOnlyPrefix<
645
+ T extends TypeId.EmbedOnly = TypeId.EmbedOnly,
646
+ TParent extends TypeId.CanEmbedType<T> = TypeId.CanEmbedType<T>
647
+ > = `${PascalCase<TParent>}${PascalCase<T>}`
648
+
649
+ type IdSuffix = 'Id' | 'IdWildcard'
650
+
651
+ type IdSchemaName =
652
+ `${EmbeddedPrimaryNodePrefix | PrimaryNodePrefix | PrimaryNodeUnionPrefix | EmbedOnlyPrefix | 'Any'}${IdSuffix}`
653
+
654
+ const ids = {} as Record<IdSchemaName, TString | TUnion<TRef<string>[]>>
655
+
656
+ for (const typeId in patternIndex) {
657
+ const typePatterns = patternIndex[typeId as TypeId.Any]
658
+
659
+ for (const pattern of typePatterns)
660
+ for (const schema of [pattern.toSchema(), pattern.toWildcardSchema()]) {
661
+ ids[schema.$id as IdSchemaName] = schema
662
+ }
663
+ }
664
+
665
+ const permutations = {} as Record<string, Set<string>>
666
+
667
+ function permutate(typeId: TypeId.Any, parentTypeComposite: string) {
668
+ if (TypeId.isPrimary(typeId) && !TypeId.isEmbedOnly(parentTypeComposite)) {
669
+ const typeSchema = `${pascalCase(typeId)}Id`
670
+
671
+ const parentTypePrefix = parentTypeComposite
672
+ .split(TypeSep)
673
+ .map(pascalCase)
674
+ .join('')
675
+
676
+ const thisSchema = parentTypePrefix + typeSchema
677
+ const thisWildcardSchema = `${thisSchema}Wildcard`
678
+
679
+ const anySchema = `Any${typeSchema}`
680
+ const anyWildcardSchema = `${anySchema}Wildcard`
681
+ const embeddedSchema = `Embedded${typeSchema}`
682
+ const embeddedWildcardSchema = `${embeddedSchema}Wildcard`
683
+
684
+ permutations[anySchema] ||= new Set([typeSchema])
685
+ permutations[embeddedWildcardSchema] ||= new Set()
686
+ permutations[embeddedSchema] ||= new Set()
687
+ permutations[anyWildcardSchema] ||= new Set([`${typeSchema}Wildcard`])
688
+
689
+ permutations[anySchema].add(thisSchema)
690
+ permutations[anyWildcardSchema].add(thisWildcardSchema)
691
+
692
+ permutations[embeddedSchema].add(thisSchema)
693
+ permutations[embeddedWildcardSchema].add(thisWildcardSchema)
694
+ }
695
+
696
+ const embeddedTypes = TypeId.getEmbeddableTypes(typeId, true)
697
+
698
+ for (const embeddedTypeId of embeddedTypes)
699
+ permutate(embeddedTypeId, `${parentTypeComposite}${TypeSep}${typeId}`)
700
+ }
701
+
702
+ for (const primaryTypeId in TypeId.EmbedTypeMap) {
703
+ const embeddedTypeIds = TypeId.EmbedTypeMap[primaryTypeId as TypeId.Embedding]
704
+
705
+ for (const embeddedTypeId of embeddedTypeIds)
706
+ permutate(embeddedTypeId, primaryTypeId)
707
+ }
708
+
709
+ const anyIdSchemata: TRef<string>[] = []
710
+ const anyIdWildcardSchemata: TRef<string>[] = []
711
+
712
+ for (const $id in permutations) {
713
+ const schemaIds = Array.from(permutations[$id]).map((id) => Type.Ref(id))
714
+ ids[$id as IdSchemaName] = Type.Union(schemaIds, {
715
+ $id,
716
+ [JsonTypeDef]: { schema: JtdType.String() }
717
+ })
718
+ if ($id.endsWith('IdWildcard')) anyIdWildcardSchemata.push(...schemaIds)
719
+ else anyIdSchemata.push(...schemaIds)
720
+ }
721
+
722
+ // @ts-expect-error
723
+ ids.AnyOracleRollableRowId = Type.Union(
724
+ Object.values(ids)
725
+ .filter((schema) => schema.$id?.endsWith('OracleRollableRowId'))
726
+ .map((schema) => Type.Ref(schema.$id as string)),
727
+ {
728
+ $id: 'AnyOracleRollableRowId',
729
+ [JsonTypeDef]: { schema: JtdType.String() }
730
+ }
731
+ )
732
+ // @ts-expect-error
733
+
734
+ ids.AnyOracleRollableRowIdWildcard = Type.Union(
735
+ Object.values(ids)
736
+ .flat()
737
+ .filter((schema) => schema.$id?.endsWith('OracleRollableRowIdWildcard'))
738
+ .map((schema) => Type.Ref(schema.$id as string)),
739
+ {
740
+ $id: 'AnyOracleRollableRowIdWildcard',
741
+ [JsonTypeDef]: { schema: JtdType.String() }
742
+ }
743
+ )
744
+ // @ts-expect-error
745
+
746
+ ids.AnyMoveConditionId = Type.Union(
747
+ Object.values(ids)
748
+ .filter(
749
+ (schema) =>
750
+ schema.$id?.endsWith('ConditionId') &&
751
+ !schema.$id?.endsWith('IdWildcard')
752
+ )
753
+ .map((schema) => Type.Ref(schema.$id as string)),
754
+ {
755
+ $id: 'AnyMoveConditionId',
756
+ [JsonTypeDef]: { schema: JtdType.String() }
757
+ }
758
+ )
759
+ // @ts-expect-error
760
+
761
+ ids.AnyMoveConditionIdWildcard = Type.Union(
762
+ Object.values(ids)
763
+ .flat()
764
+ .filter((schema) => schema.$id?.endsWith('ConditionIdWildcard'))
765
+ .map((schema) => Type.Ref(schema.$id as string)),
766
+ {
767
+ $id: 'AnyMoveConditionIdWildcard',
768
+ [JsonTypeDef]: { schema: JtdType.String() }
769
+ }
770
+ )
771
+ // @ts-expect-error
772
+
773
+ ids.AnyMoveOutcomeId = Type.Union(
774
+ Object.values(ids)
775
+ .filter(
776
+ (schema) =>
777
+ schema.$id?.endsWith('OutcomeId') && !schema.$id?.endsWith('IdWildcard')
778
+ )
779
+ .map((schema) => Type.Ref(schema.$id as string)),
780
+ {
781
+ $id: 'AnyMoveOutcomeId',
782
+ [JsonTypeDef]: { schema: JtdType.String() }
783
+ }
784
+ )
785
+ // @ts-expect-error
786
+
787
+ ids.AnyMoveOutcomeIdWildcard = Type.Union(
788
+ Object.values(ids)
789
+ .flat()
790
+ .filter((schema) => schema.$id?.endsWith('OutcomeIdWildcard'))
791
+ .map((schema) => Type.Ref(schema.$id as string)),
792
+ {
793
+ $id: 'AnyMoveOutcomeIdWildcard',
794
+ [JsonTypeDef]: { schema: JtdType.String() }
795
+ }
796
+ )
797
+ // @ts-expect-error
798
+
799
+ ids.AnyId = Type.Union(
800
+ Object.values(ids)
801
+ .flat()
802
+ .filter(
803
+ (schema) =>
804
+ schema.type === 'string' && !schema.$id?.endsWith('IdWildcard')
805
+ )
806
+ .map((schema) => Type.Ref(schema)),
807
+ {
808
+ $id: 'AnyId',
809
+ description:
810
+ 'Represents any kind of non-wildcard ID, including IDs of embedded objects.',
811
+ [JsonTypeDef]: { schema: JtdType.String() }
812
+ }
813
+ )
814
+ // @ts-expect-error
815
+
816
+ ids.AnyIdWildcard = Type.Union(
817
+ Object.values(ids)
818
+ .flat()
819
+ .filter(
820
+ (schema) => schema.type === 'string' && schema.$id?.endsWith('IdWildcard')
821
+ )
822
+ .map((schema) => Type.Ref(schema)),
823
+ {
824
+ $id: 'AnyIdWildcard',
825
+ description:
826
+ 'Represents any kind of wildcard ID, including IDs of embedded objects.',
827
+ [JsonTypeDef]: { schema: JtdType.String() }
828
+ }
829
+ )
830
+
831
+ // generate union types from IDs that are marked Any*Id
832
+
833
+ const idUnionNamePattern = /^Any(?<typeName>[A-Z][A-z]+)Id$/
834
+
835
+ for (const idName in ids) {
836
+ const match = idName.match(idUnionNamePattern)
837
+ if (!match?.groups) continue
838
+ const { typeName } = match.groups
839
+ const typeId = snakeCase(typeName) as TypeId.Any
840
+ if (!TypeId.isPrimary(typeId)) continue
841
+
842
+ const $id = `Any${typeName}` as keyof typeof ids
843
+ ids[$id] = Type.Union([Type.Ref(typeName), Type.Ref(`Embedded${typeName}`)], {
844
+ $id,
845
+ [JsonTypeDef]: { skip: true }
846
+ })
847
+ }
848
+
849
+ export default ids