velocious 1.0.552 → 1.0.555

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 (117) hide show
  1. package/README.md +2 -0
  2. package/build/background-jobs/status-reporter.js +15 -2
  3. package/build/background-jobs/worker.js +5 -1
  4. package/build/frontend-models/base.js +105 -47
  5. package/build/frontend-models/transport-deadline.js +66 -0
  6. package/build/src/background-jobs/status-reporter.d.ts +3 -1
  7. package/build/src/background-jobs/status-reporter.d.ts.map +1 -1
  8. package/build/src/background-jobs/status-reporter.js +16 -3
  9. package/build/src/background-jobs/worker.d.ts.map +1 -1
  10. package/build/src/background-jobs/worker.js +6 -2
  11. package/build/src/frontend-models/base.d.ts +10 -1
  12. package/build/src/frontend-models/base.d.ts.map +1 -1
  13. package/build/src/frontend-models/base.js +71 -26
  14. package/build/src/frontend-models/transport-deadline.d.ts +26 -0
  15. package/build/src/frontend-models/transport-deadline.d.ts.map +1 -0
  16. package/build/src/frontend-models/transport-deadline.js +61 -0
  17. package/build/src/testing/factory/association-declaration.d.ts +37 -0
  18. package/build/src/testing/factory/association-declaration.d.ts.map +1 -0
  19. package/build/src/testing/factory/association-declaration.js +41 -0
  20. package/build/src/testing/factory/declarations.d.ts +190 -0
  21. package/build/src/testing/factory/declarations.d.ts.map +1 -0
  22. package/build/src/testing/factory/declarations.js +98 -0
  23. package/build/src/testing/factory/definition-builder.d.ts +132 -0
  24. package/build/src/testing/factory/definition-builder.d.ts.map +1 -0
  25. package/build/src/testing/factory/definition-builder.js +417 -0
  26. package/build/src/testing/factory/errors.d.ts +62 -0
  27. package/build/src/testing/factory/errors.d.ts.map +1 -0
  28. package/build/src/testing/factory/errors.js +67 -0
  29. package/build/src/testing/factory/evaluation-context.d.ts +95 -0
  30. package/build/src/testing/factory/evaluation-context.d.ts.map +1 -0
  31. package/build/src/testing/factory/evaluation-context.js +175 -0
  32. package/build/src/testing/factory/events.d.ts +21 -0
  33. package/build/src/testing/factory/events.d.ts.map +1 -0
  34. package/build/src/testing/factory/events.js +26 -0
  35. package/build/src/testing/factory/factory-definition.d.ts +39 -0
  36. package/build/src/testing/factory/factory-definition.d.ts.map +1 -0
  37. package/build/src/testing/factory/factory-definition.js +35 -0
  38. package/build/src/testing/factory/factory-registry.d.ts +263 -0
  39. package/build/src/testing/factory/factory-registry.d.ts.map +1 -0
  40. package/build/src/testing/factory/factory-registry.js +438 -0
  41. package/build/src/testing/factory/factory-runner.d.ts +179 -0
  42. package/build/src/testing/factory/factory-runner.d.ts.map +1 -0
  43. package/build/src/testing/factory/factory-runner.js +266 -0
  44. package/build/src/testing/factory/index.d.ts +18 -0
  45. package/build/src/testing/factory/index.d.ts.map +1 -0
  46. package/build/src/testing/factory/index.js +21 -0
  47. package/build/src/testing/factory/linter.d.ts +55 -0
  48. package/build/src/testing/factory/linter.d.ts.map +1 -0
  49. package/build/src/testing/factory/linter.js +106 -0
  50. package/build/src/testing/factory/model-contract.d.ts +11 -0
  51. package/build/src/testing/factory/model-contract.d.ts.map +1 -0
  52. package/build/src/testing/factory/model-contract.js +26 -0
  53. package/build/src/testing/factory/node/load-definitions.d.ts +24 -0
  54. package/build/src/testing/factory/node/load-definitions.d.ts.map +1 -0
  55. package/build/src/testing/factory/node/load-definitions.js +87 -0
  56. package/build/src/testing/factory/sequence.d.ts +73 -0
  57. package/build/src/testing/factory/sequence.d.ts.map +1 -0
  58. package/build/src/testing/factory/sequence.js +78 -0
  59. package/build/src/testing/factory/strategies/attributes-for.d.ts +21 -0
  60. package/build/src/testing/factory/strategies/attributes-for.d.ts.map +1 -0
  61. package/build/src/testing/factory/strategies/attributes-for.js +22 -0
  62. package/build/src/testing/factory/strategies/base.d.ts +101 -0
  63. package/build/src/testing/factory/strategies/base.d.ts.map +1 -0
  64. package/build/src/testing/factory/strategies/base.js +191 -0
  65. package/build/src/testing/factory/strategies/build.d.ts +20 -0
  66. package/build/src/testing/factory/strategies/build.d.ts.map +1 -0
  67. package/build/src/testing/factory/strategies/build.js +34 -0
  68. package/build/src/testing/factory/strategies/create.d.ts +32 -0
  69. package/build/src/testing/factory/strategies/create.d.ts.map +1 -0
  70. package/build/src/testing/factory/strategies/create.js +57 -0
  71. package/build/src/testing/factory/trait-definition.d.ts +22 -0
  72. package/build/src/testing/factory/trait-definition.d.ts.map +1 -0
  73. package/build/src/testing/factory/trait-definition.js +22 -0
  74. package/build/testing/factory/association-declaration.js +50 -0
  75. package/build/testing/factory/declarations.js +110 -0
  76. package/build/testing/factory/definition-builder.js +476 -0
  77. package/build/testing/factory/errors.js +79 -0
  78. package/build/testing/factory/evaluation-context.js +199 -0
  79. package/build/testing/factory/events.js +30 -0
  80. package/build/testing/factory/factory-definition.js +41 -0
  81. package/build/testing/factory/factory-registry.js +501 -0
  82. package/build/testing/factory/factory-runner.js +296 -0
  83. package/build/testing/factory/index.js +24 -0
  84. package/build/testing/factory/linter.js +119 -0
  85. package/build/testing/factory/model-contract.js +31 -0
  86. package/build/testing/factory/node/load-definitions.js +103 -0
  87. package/build/testing/factory/sequence.js +91 -0
  88. package/build/testing/factory/strategies/attributes-for.js +24 -0
  89. package/build/testing/factory/strategies/base.js +209 -0
  90. package/build/testing/factory/strategies/build.js +42 -0
  91. package/build/testing/factory/strategies/create.js +68 -0
  92. package/build/testing/factory/trait-definition.js +24 -0
  93. package/build/tsconfig.tsbuildinfo +1 -1
  94. package/package.json +1 -1
  95. package/src/background-jobs/status-reporter.js +15 -2
  96. package/src/background-jobs/worker.js +5 -1
  97. package/src/frontend-models/base.js +105 -47
  98. package/src/frontend-models/transport-deadline.js +66 -0
  99. package/src/testing/factory/association-declaration.js +50 -0
  100. package/src/testing/factory/declarations.js +110 -0
  101. package/src/testing/factory/definition-builder.js +476 -0
  102. package/src/testing/factory/errors.js +79 -0
  103. package/src/testing/factory/evaluation-context.js +199 -0
  104. package/src/testing/factory/events.js +30 -0
  105. package/src/testing/factory/factory-definition.js +41 -0
  106. package/src/testing/factory/factory-registry.js +501 -0
  107. package/src/testing/factory/factory-runner.js +296 -0
  108. package/src/testing/factory/index.js +24 -0
  109. package/src/testing/factory/linter.js +119 -0
  110. package/src/testing/factory/model-contract.js +31 -0
  111. package/src/testing/factory/node/load-definitions.js +103 -0
  112. package/src/testing/factory/sequence.js +91 -0
  113. package/src/testing/factory/strategies/attributes-for.js +24 -0
  114. package/src/testing/factory/strategies/base.js +209 -0
  115. package/src/testing/factory/strategies/build.js +42 -0
  116. package/src/testing/factory/strategies/create.js +68 -0
  117. package/src/testing/factory/trait-definition.js +24 -0
@@ -0,0 +1,476 @@
1
+ // @ts-check
2
+
3
+ import {isPlainObject} from "is-plain-object"
4
+ import DatabaseRecord from "../../database/record/index.js"
5
+ import {
6
+ attributeDeclaration,
7
+ callbackDeclaration,
8
+ initializeWithDeclaration,
9
+ skipCreateDeclaration,
10
+ toCreateDeclaration,
11
+ traitIncludeDeclaration
12
+ } from "./declarations.js"
13
+ import AssociationDeclaration from "./association-declaration.js"
14
+ import FactoryDefinition from "./factory-definition.js"
15
+ import Sequence from "./sequence.js"
16
+ import TraitDefinition from "./trait-definition.js"
17
+ import {InvalidDefinitionError} from "./errors.js"
18
+
19
+ /** Callback phases accepted by `before`/`after` mapped to their event suffix. */
20
+ const CALLBACK_PHASES = {all: "All", build: "Build", create: "Create"}
21
+
22
+ /**
23
+ * Resolves a `before`/`after` phase into the concrete event name.
24
+ * @param {"before" | "after"} prefix - Callback prefix.
25
+ * @param {string} phase - Declared phase (all/build/create).
26
+ * @returns {string} - Concrete event name (e.g. "afterCreate").
27
+ */
28
+ function eventNameFor(prefix, phase) {
29
+ const suffix = /** @type {Record<string, string>} */ (CALLBACK_PHASES)[phase]
30
+
31
+ if (!suffix) {
32
+ throw new InvalidDefinitionError(`Unknown callback phase "${String(phase)}". Use one of: ${Object.keys(CALLBACK_PHASES).join(", ")}`)
33
+ }
34
+
35
+ return `${prefix}${suffix}`
36
+ }
37
+
38
+ /**
39
+ * Validates a declared name is a non-empty string.
40
+ * @param {string} name - Name to validate.
41
+ * @param {string} what - What is being named (for the message).
42
+ * @returns {void}
43
+ */
44
+ function assertName(name, what) {
45
+ if (!name || typeof name !== "string") {
46
+ throw new InvalidDefinitionError(`${what} name must be a non-empty string, got: ${String(name)}`)
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Builds an association declaration from the loose `association(name, ...)` args:
52
+ * leading strings are traits and a trailing plain object supplies factory/strategy
53
+ * plus overrides.
54
+ * @param {string} name - Relationship name.
55
+ * @param {Array<?>} args - Remaining arguments.
56
+ * @returns {AssociationDeclaration} - The declaration.
57
+ */
58
+ function buildAssociationDeclaration(name, args) {
59
+ /** @type {string[]} */
60
+ const traits = []
61
+ /** @type {Record<string, ?>} */
62
+ let options = {}
63
+
64
+ for (const arg of args) {
65
+ if (typeof arg === "string") {
66
+ traits.push(arg)
67
+ } else if (isPlainObject(arg)) {
68
+ options = arg
69
+ } else {
70
+ throw new InvalidDefinitionError(`Invalid association argument for "${name}": ${String(arg)}`)
71
+ }
72
+ }
73
+
74
+ const {factory, strategy, ...overrides} = options
75
+
76
+ return new AssociationDeclaration({name, factory, strategy, traits, overrides})
77
+ }
78
+
79
+ /**
80
+ * Parses the polymorphic `sequence(name, ...)` argument forms into a Sequence.
81
+ * @param {string} name - Sequence name.
82
+ * @param {Array<?>} args - Remaining arguments (initial/options and/or formatter).
83
+ * @returns {Sequence} - The constructed sequence.
84
+ */
85
+ function buildSequence(name, args) {
86
+ assertName(name, "Sequence")
87
+
88
+ let initial = 1
89
+ /** @type {string[]} */
90
+ let aliases = []
91
+ /** @type {import("./sequence.js").SequenceFormatter | undefined} */
92
+ let formatter
93
+
94
+ for (const arg of args) {
95
+ if (typeof arg === "function") {
96
+ formatter = arg
97
+ } else if (typeof arg === "number") {
98
+ initial = arg
99
+ } else if (isPlainObject(arg)) {
100
+ if (typeof arg.initial === "number") initial = arg.initial
101
+ if (Array.isArray(arg.aliases)) aliases = arg.aliases
102
+ } else if (arg !== undefined) {
103
+ throw new InvalidDefinitionError(`Invalid sequence argument for "${name}": ${String(arg)}`)
104
+ }
105
+ }
106
+
107
+ return new Sequence({name, initial, formatter, aliases})
108
+ }
109
+
110
+ /**
111
+ * Collects declarations for one factory/trait block. Shared by factory blocks,
112
+ * trait blocks and the root registry-defaults block.
113
+ */
114
+ class DeclarationCollector {
115
+ /** Builds a collector. */
116
+ constructor() {
117
+ /** @type {import("./declarations.js").Declaration[]} - Ordered declarations. */
118
+ this.declarations = []
119
+ }
120
+
121
+ /**
122
+ * Records a literal/lazy attribute.
123
+ * @param {string} name - Attribute name.
124
+ * @param {?} value - Literal value or lazy function.
125
+ * @returns {void}
126
+ */
127
+ attribute(name, value) {
128
+ assertName(name, "Attribute")
129
+ this.declarations.push(attributeDeclaration(name, value, false))
130
+ }
131
+
132
+ /**
133
+ * Records a transient attribute.
134
+ * @param {string} name - Transient name.
135
+ * @param {?} value - Literal value or lazy function.
136
+ * @returns {void}
137
+ */
138
+ transient(name, value) {
139
+ assertName(name, "Transient")
140
+ this.declarations.push(attributeDeclaration(name, value, true))
141
+ }
142
+
143
+ /**
144
+ * Records an association.
145
+ * @param {string} name - Relationship name.
146
+ * @param {Array<?>} args - Traits and/or an options object.
147
+ * @returns {void}
148
+ */
149
+ association(name, ...args) {
150
+ assertName(name, "Association")
151
+ this.declarations.push(buildAssociationDeclaration(name, args))
152
+ }
153
+
154
+ /**
155
+ * Records a before-callback.
156
+ * @param {string} phase - Phase (all/build/create).
157
+ * @param {import("./declarations.js").CallbackDeclaration["fn"]} fn - Callback body.
158
+ * @returns {void}
159
+ */
160
+ before(phase, fn) {
161
+ this.declarations.push(callbackDeclaration(eventNameFor("before", phase), fn))
162
+ }
163
+
164
+ /**
165
+ * Records an after-callback.
166
+ * @param {string} phase - Phase (all/build/create).
167
+ * @param {import("./declarations.js").CallbackDeclaration["fn"]} fn - Callback body.
168
+ * @returns {void}
169
+ */
170
+ after(phase, fn) {
171
+ this.declarations.push(callbackDeclaration(eventNameFor("after", phase), fn))
172
+ }
173
+
174
+ /**
175
+ * Records a custom constructor.
176
+ * @param {import("./declarations.js").InitializeWithDeclaration["fn"]} fn - Constructor body.
177
+ * @returns {void}
178
+ */
179
+ initializeWith(fn) {
180
+ this.declarations.push(initializeWithDeclaration(fn))
181
+ }
182
+
183
+ /**
184
+ * Records a custom persistence hook.
185
+ * @param {import("./declarations.js").ToCreateDeclaration["fn"]} fn - Persistence body.
186
+ * @returns {void}
187
+ */
188
+ toCreate(fn) {
189
+ this.declarations.push(toCreateDeclaration(fn))
190
+ }
191
+
192
+ /**
193
+ * Records that persistence should be skipped for the create strategy.
194
+ * @returns {void}
195
+ */
196
+ skipCreate() {
197
+ this.declarations.push(skipCreateDeclaration())
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Runs a trait block against a fresh collector and compiles it into a definition.
203
+ * @param {string} name - Trait name.
204
+ * @param {(builder: object) => void} callback - Trait builder callback.
205
+ * @returns {TraitDefinition} - Compiled trait.
206
+ */
207
+ function compileTrait(name, callback) {
208
+ assertName(name, "Trait")
209
+
210
+ if (typeof callback !== "function") {
211
+ throw new InvalidDefinitionError(`Trait "${name}" requires a builder function`)
212
+ }
213
+
214
+ const collector = new DeclarationCollector()
215
+ const builder = {
216
+ attribute: (/** @type {string} */ attrName, /** @type {?} */ value) => collector.attribute(attrName, value),
217
+ transient: (/** @type {string} */ attrName, /** @type {?} */ value) => collector.transient(attrName, value),
218
+ association: (/** @type {string} */ assocName, /** @type {Array<?>} */ ...args) => collector.association(assocName, ...args),
219
+ before: (/** @type {string} */ phase, /** @type {?} */ fn) => collector.before(phase, fn),
220
+ after: (/** @type {string} */ phase, /** @type {?} */ fn) => collector.after(phase, fn),
221
+ initializeWith: (/** @type {?} */ fn) => collector.initializeWith(fn),
222
+ toCreate: (/** @type {?} */ fn) => collector.toCreate(fn),
223
+ skipCreate: () => collector.skipCreate(),
224
+ trait: (/** @type {string} */ includeName) => {
225
+ assertName(includeName, "Trait include")
226
+ collector.declarations.push(traitIncludeDeclaration(includeName))
227
+ }
228
+ }
229
+
230
+ callback(builder)
231
+
232
+ return new TraitDefinition({name, declarations: collector.declarations})
233
+ }
234
+
235
+ /**
236
+ * A single `define`/`modify` session. It walks the builder callbacks, compiles
237
+ * immutable definitions and registers them into the target registry, throwing on
238
+ * duplicate or structurally invalid declarations at definition time.
239
+ */
240
+ export default class DefinitionSession {
241
+ /**
242
+ * Builds a session.
243
+ * @param {import("./factory-registry.js").default} registry - Target registry.
244
+ */
245
+ constructor(registry) {
246
+ /** @type {import("./factory-registry.js").default} - Target registry. */
247
+ this.registry = registry
248
+ }
249
+
250
+ /**
251
+ * Runs a root `define` callback.
252
+ * @param {(builder: object) => void} callback - Root builder callback.
253
+ * @returns {void}
254
+ */
255
+ run(callback) {
256
+ if (typeof callback !== "function") {
257
+ throw new InvalidDefinitionError("define requires a builder callback")
258
+ }
259
+
260
+ callback(this._rootBuilder())
261
+ }
262
+
263
+ /**
264
+ * Runs a `modify` callback that reopens existing factories to append/override
265
+ * declarations, recompiling each into a fresh immutable definition rather than
266
+ * mutating the original.
267
+ * @param {(builder: object) => void} callback - Modify builder callback.
268
+ * @returns {void}
269
+ */
270
+ runModify(callback) {
271
+ if (typeof callback !== "function") {
272
+ throw new InvalidDefinitionError("modify requires a builder callback")
273
+ }
274
+
275
+ callback(this._modifyBuilder())
276
+ }
277
+
278
+ /**
279
+ * Builds the builder object exposed to `modify`.
280
+ * @returns {object} - Modify builder.
281
+ */
282
+ _modifyBuilder() {
283
+ return {
284
+ factory: (/** @type {string} */ name, /** @type {(builder: object) => void} */ cb) => this._modifyFactory(name, cb),
285
+ trait: (/** @type {string} */ name, /** @type {(builder: object) => void} */ cb) =>
286
+ this.registry._registerGlobalTrait(compileTrait(name, cb)),
287
+ sequence: (/** @type {string} */ name, /** @type {Array<?>} */ ...args) =>
288
+ this.registry._registerSequence(buildSequence(name, args), null),
289
+ before: (/** @type {string} */ phase, /** @type {?} */ fn) =>
290
+ this.registry._addGlobalDeclaration(callbackDeclaration(eventNameFor("before", phase), fn)),
291
+ after: (/** @type {string} */ phase, /** @type {?} */ fn) =>
292
+ this.registry._addGlobalDeclaration(callbackDeclaration(eventNameFor("after", phase), fn)),
293
+ initializeWith: (/** @type {?} */ fn) => this.registry._addGlobalDeclaration(initializeWithDeclaration(fn)),
294
+ toCreate: (/** @type {?} */ fn) => this.registry._addGlobalDeclaration(toCreateDeclaration(fn)),
295
+ skipCreate: () => this.registry._addGlobalDeclaration(skipCreateDeclaration())
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Recompiles an existing factory with appended declarations.
301
+ * @param {string} name - Existing factory name.
302
+ * @param {(builder: object) => void} cb - Factory builder callback.
303
+ * @returns {void}
304
+ */
305
+ _modifyFactory(name, cb) {
306
+ assertName(name, "Factory")
307
+
308
+ const existing = this.registry._factories.get(name)
309
+
310
+ if (!existing) {
311
+ throw new InvalidDefinitionError(`Cannot modify unknown factory "${name}"`)
312
+ }
313
+
314
+ const collector = new DeclarationCollector()
315
+ /** @type {Array<{name: string, modelOrOptions: ?, cb: ?}>} */
316
+ const nestedFactories = []
317
+ const localTraits = new Map(existing.localTraits)
318
+ /** @type {Sequence[]} */
319
+ const scopedSequences = []
320
+
321
+ if (typeof cb === "function") {
322
+ cb(this._factoryBuilder(collector, nestedFactories, localTraits, scopedSequences))
323
+ }
324
+
325
+ const merged = new FactoryDefinition({
326
+ name: existing.name,
327
+ modelClass: existing.modelClass,
328
+ parentName: existing.parentName,
329
+ aliases: [...existing.aliases],
330
+ declarations: [...existing.declarations, ...collector.declarations],
331
+ localTraits
332
+ })
333
+
334
+ this.registry._replaceFactoryDefinition(merged)
335
+
336
+ for (const sequence of scopedSequences) {
337
+ this.registry._registerSequence(sequence, name)
338
+ }
339
+
340
+ for (const nested of nestedFactories) {
341
+ this._defineFactory(nested.name, nested.modelOrOptions, nested.cb, name)
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Builds the root builder object exposed to `define`.
347
+ * @returns {object} - Root builder.
348
+ */
349
+ _rootBuilder() {
350
+ return {
351
+ factory: (/** @type {string} */ name, /** @type {?} */ modelOrOptions, /** @type {?} */ cb) =>
352
+ this._defineFactory(name, modelOrOptions, cb, null),
353
+ trait: (/** @type {string} */ name, /** @type {(builder: object) => void} */ cb) =>
354
+ this.registry._registerGlobalTrait(compileTrait(name, cb)),
355
+ sequence: (/** @type {string} */ name, /** @type {Array<?>} */ ...args) =>
356
+ this.registry._registerSequence(buildSequence(name, args), null),
357
+ before: (/** @type {string} */ phase, /** @type {?} */ fn) =>
358
+ this.registry._addGlobalDeclaration(callbackDeclaration(eventNameFor("before", phase), fn)),
359
+ after: (/** @type {string} */ phase, /** @type {?} */ fn) =>
360
+ this.registry._addGlobalDeclaration(callbackDeclaration(eventNameFor("after", phase), fn)),
361
+ initializeWith: (/** @type {?} */ fn) => this.registry._addGlobalDeclaration(initializeWithDeclaration(fn)),
362
+ toCreate: (/** @type {?} */ fn) => this.registry._addGlobalDeclaration(toCreateDeclaration(fn)),
363
+ skipCreate: () => this.registry._addGlobalDeclaration(skipCreateDeclaration())
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Compiles and registers a factory (and its nested children/local traits/scoped
369
+ * sequences).
370
+ * @param {string} name - Factory name.
371
+ * @param {?} modelOrOptions - Model class or options object.
372
+ * @param {?} cb - Factory builder callback.
373
+ * @param {string | null} inheritedParent - Parent name for nested factories.
374
+ * @returns {void}
375
+ */
376
+ _defineFactory(name, modelOrOptions, cb, inheritedParent) {
377
+ assertName(name, "Factory")
378
+
379
+ let modelClass = null
380
+ let builderCallback = cb
381
+ /** @type {Record<string, ?>} */
382
+ let options = {}
383
+
384
+ if (typeof cb === "function") {
385
+ // Three-argument form: the second argument is the model class or options.
386
+ if (typeof modelOrOptions === "function") {
387
+ modelClass = modelOrOptions
388
+ } else if (isPlainObject(modelOrOptions)) {
389
+ options = modelOrOptions
390
+ modelClass = options.model || options.class || null
391
+ } else if (modelOrOptions !== undefined) {
392
+ throw new InvalidDefinitionError(`Factory "${name}" model must be a class or an options object`)
393
+ }
394
+ } else if (typeof modelOrOptions === "function") {
395
+ // Two-argument form with a trailing function. A backend model class means
396
+ // "no builder"; any other function is the builder (child inherits its model).
397
+ if (modelOrOptions.prototype instanceof DatabaseRecord) {
398
+ modelClass = modelOrOptions
399
+ } else {
400
+ builderCallback = modelOrOptions
401
+ }
402
+ } else if (isPlainObject(modelOrOptions)) {
403
+ options = modelOrOptions
404
+ modelClass = options.model || options.class || null
405
+ } else if (modelOrOptions !== undefined) {
406
+ throw new InvalidDefinitionError(`Factory "${name}" model must be a class or an options object`)
407
+ }
408
+
409
+ const parentName = options.parent || inheritedParent || null
410
+ const aliases = Array.isArray(options.aliases) ? options.aliases : []
411
+ const baseTraits = Array.isArray(options.traits) ? options.traits : []
412
+
413
+ const collector = new DeclarationCollector()
414
+ /** @type {Array<{name: string, modelOrOptions: ?, cb: ?}>} */
415
+ const nestedFactories = []
416
+ /** @type {Map<string, TraitDefinition>} */
417
+ const localTraits = new Map()
418
+ /** @type {Sequence[]} */
419
+ const scopedSequences = []
420
+
421
+ if (typeof builderCallback === "function") {
422
+ builderCallback(this._factoryBuilder(collector, nestedFactories, localTraits, scopedSequences))
423
+ }
424
+
425
+ const declarations = [...baseTraits.map((traitName) => traitIncludeDeclaration(traitName)), ...collector.declarations]
426
+ const definition = new FactoryDefinition({name, modelClass, parentName, aliases, declarations, localTraits})
427
+
428
+ this.registry._registerFactoryDefinition(definition)
429
+
430
+ for (const sequence of scopedSequences) {
431
+ this.registry._registerSequence(sequence, name)
432
+ }
433
+
434
+ for (const nested of nestedFactories) {
435
+ this._defineFactory(nested.name, nested.modelOrOptions, nested.cb, name)
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Builds the factory builder object.
441
+ * @param {DeclarationCollector} collector - Declaration collector.
442
+ * @param {Array<{name: string, modelOrOptions: ?, cb: ?}>} nestedFactories - Nested factory sink.
443
+ * @param {Map<string, TraitDefinition>} localTraits - Local trait sink.
444
+ * @param {Sequence[]} scopedSequences - Scoped sequence sink.
445
+ * @returns {object} - Factory builder.
446
+ */
447
+ _factoryBuilder(collector, nestedFactories, localTraits, scopedSequences) {
448
+ return {
449
+ attribute: (/** @type {string} */ attrName, /** @type {?} */ value) => collector.attribute(attrName, value),
450
+ transient: (/** @type {string} */ attrName, /** @type {?} */ value) => collector.transient(attrName, value),
451
+ association: (/** @type {string} */ assocName, /** @type {Array<?>} */ ...args) => collector.association(assocName, ...args),
452
+ before: (/** @type {string} */ phase, /** @type {?} */ fn) => collector.before(phase, fn),
453
+ after: (/** @type {string} */ phase, /** @type {?} */ fn) => collector.after(phase, fn),
454
+ initializeWith: (/** @type {?} */ fn) => collector.initializeWith(fn),
455
+ toCreate: (/** @type {?} */ fn) => collector.toCreate(fn),
456
+ skipCreate: () => collector.skipCreate(),
457
+ sequence: (/** @type {string} */ seqName, /** @type {Array<?>} */ ...args) => scopedSequences.push(buildSequence(seqName, args)),
458
+ trait: (/** @type {string} */ traitName, /** @type {((builder: object) => void) | undefined} */ traitCb) => {
459
+ if (typeof traitCb === "function") {
460
+ const compiled = compileTrait(traitName, traitCb)
461
+
462
+ if (localTraits.has(traitName)) {
463
+ throw new InvalidDefinitionError(`Local trait "${traitName}" is already defined on this factory`)
464
+ }
465
+
466
+ localTraits.set(traitName, compiled)
467
+ } else {
468
+ assertName(traitName, "Trait include")
469
+ collector.declarations.push(traitIncludeDeclaration(traitName))
470
+ }
471
+ },
472
+ factory: (/** @type {string} */ childName, /** @type {?} */ childModelOrOptions, /** @type {?} */ childCb) =>
473
+ nestedFactories.push({name: childName, modelOrOptions: childModelOrOptions, cb: childCb})
474
+ }
475
+ }
476
+ }
@@ -0,0 +1,79 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Base error for every Factory-framework failure so callers can catch the whole
5
+ * family with a single `instanceof FactoryError` check.
6
+ */
7
+ class FactoryError extends Error {
8
+ /**
9
+ * Builds a factory error.
10
+ * @param {string} message - Human readable description of the failure.
11
+ */
12
+ constructor(message) {
13
+ super(message)
14
+ this.name = this.constructor.name
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Raised when a factory, trait or sequence is declared twice within the same
20
+ * registry, matching FactoryBot's duplicate-definition guard.
21
+ */
22
+ class DuplicateDefinitionError extends FactoryError {}
23
+
24
+ /**
25
+ * Raised when a strategy references a factory name that was never registered.
26
+ */
27
+ class UndefinedFactoryError extends FactoryError {}
28
+
29
+ /**
30
+ * Raised when a trait name cannot be resolved in the factory-local or global scope.
31
+ */
32
+ class UndefinedTraitError extends FactoryError {}
33
+
34
+ /**
35
+ * Raised when a sequence name cannot be resolved in the factory-local or global scope.
36
+ */
37
+ class UndefinedSequenceError extends FactoryError {}
38
+
39
+ /**
40
+ * Raised when a lazy attribute references another attribute that does not exist.
41
+ */
42
+ class UndefinedAttributeError extends FactoryError {}
43
+
44
+ /**
45
+ * Raised when attribute or factory/trait resolution forms a cycle. The message
46
+ * always contains the full offending path so the author can break it.
47
+ */
48
+ class FactoryCycleError extends FactoryError {}
49
+
50
+ /**
51
+ * Raised when a definition is structurally invalid at declaration time (bad
52
+ * names, option shapes, event names, or model references).
53
+ */
54
+ class InvalidDefinitionError extends FactoryError {}
55
+
56
+ /**
57
+ * Raised when a build/create strategy is asked to construct a class that is not a
58
+ * supported, initialized Velocious backend record.
59
+ */
60
+ class ModelContractError extends FactoryError {}
61
+
62
+ /**
63
+ * Raised when setup-time mutation (define/modify/reset/sequence set/rewind) is
64
+ * attempted while the registry has in-flight evaluations.
65
+ */
66
+ class RegistryBusyError extends FactoryError {}
67
+
68
+ export {
69
+ DuplicateDefinitionError,
70
+ FactoryCycleError,
71
+ FactoryError,
72
+ InvalidDefinitionError,
73
+ ModelContractError,
74
+ RegistryBusyError,
75
+ UndefinedAttributeError,
76
+ UndefinedFactoryError,
77
+ UndefinedSequenceError,
78
+ UndefinedTraitError
79
+ }