@shirudo/ddd-kit 3.0.0-rc.12 → 3.0.0-rc.13

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.
@@ -0,0 +1,673 @@
1
+ import { E as KitWiringError, o as DomainError } from "./chunks/kit-errors.js";
2
+ import { _ as isIntrinsicConstructorPrototype, g as isBuiltInObject, m as findPropertyDescriptor, n as deepFreeze } from "./chunks/value-object.js";
3
+
4
+ //#region src/domain/state-machine/errors.ts
5
+ /** No transition is defined for the input in the current state. */
6
+ var InvalidDomainTransitionError = class extends DomainError {
7
+ state;
8
+ inputType;
9
+ constructor(state, inputType) {
10
+ super({
11
+ code: "INVALID_DOMAIN_TRANSITION",
12
+ message: `No domain transition from "${state}" on "${inputType}".`
13
+ });
14
+ this.state = state;
15
+ this.inputType = inputType;
16
+ }
17
+ };
18
+ /** A defined transition was rejected by its domain guard. */
19
+ var DomainTransitionGuardRejectedError = class extends DomainError {
20
+ state;
21
+ inputType;
22
+ constructor(state, inputType) {
23
+ super({
24
+ code: "DOMAIN_TRANSITION_GUARD_REJECTED",
25
+ message: `Domain transition guard rejected "${inputType}" from "${state}".`
26
+ });
27
+ this.state = state;
28
+ this.inputType = inputType;
29
+ }
30
+ };
31
+ /** The machine definition violates its runtime contract. */
32
+ var InvalidDomainMachineDefinitionError = class extends KitWiringError {
33
+ constructor(message, cause) {
34
+ super("INVALID_DOMAIN_MACHINE_DEFINITION", message, cause);
35
+ }
36
+ };
37
+ /** Context contains unsupported or unsafe runtime data. */
38
+ var InvalidDomainMachineContextError = class extends KitWiringError {
39
+ constructor(message, cause) {
40
+ super("INVALID_DOMAIN_MACHINE_CONTEXT", message, cause);
41
+ }
42
+ };
43
+ /** A supplied or produced snapshot is malformed or violates invariants. */
44
+ var InvalidDomainMachineSnapshotError = class extends KitWiringError {
45
+ constructor(message, cause) {
46
+ super("INVALID_DOMAIN_MACHINE_SNAPSHOT", message, cause);
47
+ }
48
+ };
49
+ /** An input is malformed or contains unsupported runtime data. */
50
+ var InvalidDomainMachineInputError = class extends KitWiringError {
51
+ constructor(message, cause) {
52
+ super("INVALID_DOMAIN_MACHINE_INPUT", message, cause);
53
+ }
54
+ };
55
+ /** A guard returned a value other than `boolean` or `DomainError`. */
56
+ var InvalidDomainTransitionGuardResultError = class extends KitWiringError {
57
+ constructor(message, cause) {
58
+ super("INVALID_DOMAIN_TRANSITION_GUARD_RESULT", message, cause);
59
+ }
60
+ };
61
+ /** A reducer returned a malformed result or unsupported output data. */
62
+ var InvalidDomainTransitionResultError = class extends KitWiringError {
63
+ constructor(message, cause) {
64
+ super("INVALID_DOMAIN_TRANSITION_RESULT", message, cause);
65
+ }
66
+ };
67
+ /** A callback attempted to evaluate the same stateful machine recursively. */
68
+ var ReentrantDomainStateMachineEvaluationError = class extends KitWiringError {
69
+ constructor() {
70
+ super("REENTRANT_DOMAIN_STATE_MACHINE_EVALUATION", "Domain state machine callbacks cannot evaluate the same machine.");
71
+ }
72
+ };
73
+
74
+ //#endregion
75
+ //#region src/domain/state-machine/machine-data.ts
76
+ const DOMAIN_MACHINE_DATA_MAX_DEPTH = 256;
77
+ const DOMAIN_MACHINE_DATA_MAX_NODES = 1e4;
78
+ const DOMAIN_MACHINE_DATA_MAX_PROPERTIES = 1e5;
79
+ function copyDomainMachineOutputs(outputs) {
80
+ try {
81
+ const copiedOutputs = cloneDomainMachineDataValue(outputs ?? [], createDomainTransitionOutputError);
82
+ return deepFreeze(copiedOutputs);
83
+ } catch (cause) {
84
+ if (cause instanceof InvalidDomainTransitionResultError) throw cause;
85
+ throw new InvalidDomainTransitionResultError("Domain transition result outputs must contain cloneable, deeply immutable data.", cause);
86
+ }
87
+ }
88
+ function copyDomainMachineInput(input) {
89
+ try {
90
+ return deepFreeze(cloneDomainMachineDataValue(input, createDomainMachineInputError));
91
+ } catch (cause) {
92
+ if (cause instanceof InvalidDomainMachineInputError) throw cause;
93
+ throw new InvalidDomainMachineInputError("Domain machine input must contain cloneable, deeply immutable data.", cause);
94
+ }
95
+ }
96
+ function copyDomainMachineContext(context) {
97
+ try {
98
+ return deepFreeze(cloneDomainMachineDataValue(context, createDomainMachineContextError));
99
+ } catch (cause) {
100
+ if (cause instanceof InvalidDomainMachineContextError) throw cause;
101
+ throw new InvalidDomainMachineContextError("Domain machine context must contain cloneable, deeply immutable data.", cause);
102
+ }
103
+ }
104
+ function createDomainMachineContextError(message, cause) {
105
+ return new InvalidDomainMachineContextError(message, cause);
106
+ }
107
+ function createDomainMachineInputError(message, cause) {
108
+ return new InvalidDomainMachineInputError(message, cause);
109
+ }
110
+ function createDomainTransitionOutputError(message, cause) {
111
+ return new InvalidDomainTransitionResultError(message, cause);
112
+ }
113
+ function cloneDomainMachineDataValue(value, errorFactory, seen = /* @__PURE__ */ new WeakMap(), traversal = {
114
+ nodes: 0,
115
+ properties: 0
116
+ }, depth = 0) {
117
+ if (typeof value === "function") throw errorFactory("Domain machine data cannot contain function values.");
118
+ if (value === null || typeof value !== "object") return value;
119
+ const source = value;
120
+ if (depth > DOMAIN_MACHINE_DATA_MAX_DEPTH) throw errorFactory(`Domain machine data exceeds the maximum depth of ${DOMAIN_MACHINE_DATA_MAX_DEPTH}.`);
121
+ const existing = seen.get(source);
122
+ if (existing !== void 0) return existing;
123
+ traversal.nodes += 1;
124
+ if (traversal.nodes > DOMAIN_MACHINE_DATA_MAX_NODES) throw errorFactory(`Domain machine data contains more than ${DOMAIN_MACHINE_DATA_MAX_NODES.toLocaleString("en-US")} object nodes.`);
125
+ const toStringTagDescriptor = findPropertyDescriptor(source, Symbol.toStringTag);
126
+ if (toStringTagDescriptor !== void 0 && !("value" in toStringTagDescriptor)) throw errorFactory("Domain machine data cannot contain accessor properties.");
127
+ if (Array.isArray(value)) {
128
+ if (!isIntrinsicArrayPrototype(Object.getPrototypeOf(value))) throw errorFactory("Domain machine data cannot contain custom Array instances.");
129
+ const cloned = new Array(value.length);
130
+ seen.set(source, cloned);
131
+ for (const key of readDomainMachineDataKeys(source, errorFactory, traversal)) {
132
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
133
+ if (!descriptor) continue;
134
+ if (!("value" in descriptor)) throw errorFactory("Domain machine data cannot contain accessor properties.");
135
+ if (key === "length") continue;
136
+ descriptor.value = cloneDomainMachineDataValue(descriptor.value, errorFactory, seen, traversal, depth + 1);
137
+ Object.defineProperty(cloned, key, descriptor);
138
+ }
139
+ return cloned;
140
+ }
141
+ const prototype = Object.getPrototypeOf(source);
142
+ if (prototype !== null && !isIntrinsicObjectPrototype(prototype)) throw errorFactory("Domain machine data cannot contain custom class instances.");
143
+ const tag = Object.prototype.toString.call(source);
144
+ if (isBuiltInObject(source, tag) || ArrayBuffer.isView(source)) throw errorFactory(`Domain machine data cannot contain ${tag.slice(8, -1)} object values.`);
145
+ const cloned = Object.create(prototype === null ? null : Object.prototype);
146
+ seen.set(source, cloned);
147
+ for (const key of readDomainMachineDataKeys(source, errorFactory, traversal)) {
148
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
149
+ if (!descriptor) continue;
150
+ if (!("value" in descriptor)) throw errorFactory("Domain machine data cannot contain accessor properties.");
151
+ descriptor.value = cloneDomainMachineDataValue(descriptor.value, errorFactory, seen, traversal, depth + 1);
152
+ Object.defineProperty(cloned, key, descriptor);
153
+ }
154
+ return cloned;
155
+ }
156
+ function readDomainMachineDataKeys(value, errorFactory, traversal) {
157
+ const keys = Reflect.ownKeys(value);
158
+ traversal.properties += keys.length;
159
+ if (traversal.properties > DOMAIN_MACHINE_DATA_MAX_PROPERTIES) throw errorFactory(`Domain machine data contains more than ${DOMAIN_MACHINE_DATA_MAX_PROPERTIES.toLocaleString("en-US")} own properties.`);
160
+ return keys;
161
+ }
162
+ function isRecord(value) {
163
+ return typeof value === "object" && value !== null && !Array.isArray(value);
164
+ }
165
+ function isPlainRecord(value) {
166
+ if (!isRecord(value)) return false;
167
+ const prototype = Object.getPrototypeOf(value);
168
+ return prototype === null || isIntrinsicObjectPrototype(prototype);
169
+ }
170
+ function isIntrinsicArrayPrototype(prototype) {
171
+ if (prototype === null || !Array.isArray(prototype)) return false;
172
+ if (!isIntrinsicConstructorPrototype(prototype, "Array")) return false;
173
+ const parentPrototype = Object.getPrototypeOf(prototype);
174
+ return parentPrototype !== null && isIntrinsicObjectPrototype(parentPrototype);
175
+ }
176
+ function isIntrinsicObjectPrototype(prototype) {
177
+ return Object.getPrototypeOf(prototype) === null && isIntrinsicConstructorPrototype(prototype, "Object");
178
+ }
179
+ function hasOwn(value, key) {
180
+ return Object.hasOwn(value, key);
181
+ }
182
+
183
+ //#endregion
184
+ //#region src/domain/state-machine/definition.ts
185
+ const DOMAIN_MACHINE_DEFINITION_KEYS = /* @__PURE__ */ new Set([
186
+ "initial",
187
+ "initialContext",
188
+ "validateSnapshot",
189
+ "states"
190
+ ]);
191
+ const DOMAIN_MACHINE_STATE_NODE_KEYS = /* @__PURE__ */ new Set([
192
+ "terminal",
193
+ "validateContext",
194
+ "on"
195
+ ]);
196
+ const DOMAIN_MACHINE_TRANSITION_KEYS = /* @__PURE__ */ new Set([
197
+ "target",
198
+ "guard",
199
+ "reduce"
200
+ ]);
201
+ function copyDomainMachineDefinition(definition) {
202
+ const states = readDomainMachineDefinitionProperty(definition, "states");
203
+ const copiedStates = Object.create(null);
204
+ for (const state of Object.keys(states)) {
205
+ const node = readDomainMachineDefinitionProperty(states, state);
206
+ const transitions = readOptionalDomainMachineDefinitionProperty(node, "on");
207
+ const copiedTransitions = Object.create(null);
208
+ for (const inputType of Object.keys(transitions ?? {})) {
209
+ const transition = readDomainMachineDefinitionProperty(transitions, inputType);
210
+ if (transition) {
211
+ const copiedTransition = Object.freeze({
212
+ target: readDomainMachineDefinitionProperty(transition, "target"),
213
+ guard: readOptionalDomainMachineDefinitionProperty(transition, "guard"),
214
+ reduce: readOptionalDomainMachineDefinitionProperty(transition, "reduce")
215
+ });
216
+ Object.defineProperty(copiedTransitions, inputType, {
217
+ value: copiedTransition,
218
+ enumerable: true
219
+ });
220
+ }
221
+ }
222
+ Object.defineProperty(copiedStates, state, {
223
+ value: Object.freeze({
224
+ terminal: readOptionalDomainMachineDefinitionProperty(node, "terminal"),
225
+ validateContext: readOptionalDomainMachineDefinitionProperty(node, "validateContext"),
226
+ on: Object.freeze(copiedTransitions)
227
+ }),
228
+ enumerable: true
229
+ });
230
+ }
231
+ return Object.freeze({
232
+ initial: readDomainMachineDefinitionProperty(definition, "initial"),
233
+ initialContext: readDomainMachineDefinitionProperty(definition, "initialContext"),
234
+ validateSnapshot: readOptionalDomainMachineDefinitionProperty(definition, "validateSnapshot"),
235
+ states: Object.freeze(copiedStates)
236
+ });
237
+ }
238
+ /**
239
+ * Registry of definitions produced by `prepareDomainMachineDefinition`:
240
+ * validated, defensively copied, deeply frozen. The runtime membership
241
+ * proof lives in this module-private WeakSet (the stable copies are
242
+ * frozen and must stay pure data, so no runtime brand property); the
243
+ * compile-time proof is the required type brand on
244
+ * {@link PreparedDomainMachineDefinition}. Only
245
+ * `prepareDomainMachineDefinition` below adds to the set, so membership
246
+ * always implies validated + copied + frozen.
247
+ */
248
+ const preparedDefinitions = /* @__PURE__ */ new WeakSet();
249
+ /**
250
+ * The entry-point normalization every pure function and the
251
+ * `DomainStateMachine` constructor share: a prepared definition passes
252
+ * through untouched (already validated, copied, frozen); anything else
253
+ * pays the documented per-call validate-and-copy.
254
+ */
255
+ function ensureStableDomainMachineDefinition(definition) {
256
+ if (preparedDefinitions.has(definition)) return definition;
257
+ validateDomainMachineDefinition(definition);
258
+ const stable = copyDomainMachineDefinition(definition);
259
+ validateDomainMachineDefinition(stable);
260
+ return stable;
261
+ }
262
+ /**
263
+ * Validates and stabilizes a machine definition ONCE, for repeated use
264
+ * with the pure functions. Without it, `transitionDomainState` and
265
+ * `canTransitionDomainState` re-validate and defensively re-copy the
266
+ * WHOLE definition on every call (the documented safety of the raw
267
+ * path); on a hot dispatch path that is avoidable O(definition) work.
268
+ * The `DomainStateMachine` class does the equivalent once in its
269
+ * constructor; this export brings the same amortization to pure-API
270
+ * users:
271
+ *
272
+ * ```ts
273
+ * const prepared = prepareDomainMachineDefinition(orderLifecycle);
274
+ * // per dispatch: no re-validation, no definition copy
275
+ * const outcome = transitionDomainState(prepared, snapshot, input);
276
+ * ```
277
+ *
278
+ * The returned definition is a deeply frozen copy, isolated from later
279
+ * mutation of the input object. Preparing an already-prepared
280
+ * definition returns it unchanged.
281
+ */
282
+ function prepareDomainMachineDefinition(definition) {
283
+ const stable = ensureStableDomainMachineDefinition(definition);
284
+ preparedDefinitions.add(stable);
285
+ return stable;
286
+ }
287
+ function getTransition(definition, state, input) {
288
+ const transitions = definition.states[state].on;
289
+ if (!transitions || !hasOwn(transitions, input.type)) return void 0;
290
+ return transitions[input.type];
291
+ }
292
+ function validateDomainMachineDefinition(definition) {
293
+ const candidate = definition;
294
+ if (!isPlainRecord(candidate)) throw new InvalidDomainMachineDefinitionError("Domain machine definition must be a plain object.");
295
+ assertDomainMachineDefinitionDataProperties(candidate, DOMAIN_MACHINE_DEFINITION_KEYS);
296
+ const initial = readDomainMachineDefinitionProperty(candidate, "initial");
297
+ if (typeof initial !== "string") throw new InvalidDomainMachineDefinitionError("Domain machine initial state must be a string data property.");
298
+ if (typeof readDomainMachineDefinitionProperty(candidate, "initialContext") !== "function") throw new InvalidDomainMachineDefinitionError("Domain machine initialContext must be a function data property.");
299
+ const validateSnapshot = readOptionalDomainMachineDefinitionProperty(candidate, "validateSnapshot");
300
+ if (validateSnapshot !== void 0 && typeof validateSnapshot !== "function") throw new InvalidDomainMachineDefinitionError("Domain machine validateSnapshot must be a function data property.");
301
+ const statesCandidate = readDomainMachineDefinitionProperty(candidate, "states");
302
+ if (!isPlainRecord(statesCandidate)) throw new InvalidDomainMachineDefinitionError("Domain machine states must be a plain object data property.");
303
+ const states = statesCandidate;
304
+ assertDomainMachineDefinitionEntryMap(states, "state");
305
+ if (!hasOwn(states, initial)) throw new InvalidDomainMachineDefinitionError(`Initial domain machine state "${initial}" is not defined.`);
306
+ for (const state of Object.keys(states)) {
307
+ const node = readDomainMachineDefinitionProperty(states, state);
308
+ if (!isPlainRecord(node)) throw new InvalidDomainMachineDefinitionError(`Domain machine state "${state}" must be a plain object data property.`);
309
+ assertDomainMachineDefinitionDataProperties(node, DOMAIN_MACHINE_STATE_NODE_KEYS);
310
+ const terminal = readOptionalDomainMachineDefinitionProperty(node, "terminal");
311
+ if (terminal !== void 0 && typeof terminal !== "boolean") throw new InvalidDomainMachineDefinitionError(`Domain machine state "${state}" terminal flag must be a boolean.`);
312
+ const validateContext = readOptionalDomainMachineDefinitionProperty(node, "validateContext");
313
+ if (validateContext !== void 0 && typeof validateContext !== "function") throw new InvalidDomainMachineDefinitionError(`Domain machine state "${state}" validateContext must be a function.`);
314
+ const transitions = readOptionalDomainMachineDefinitionProperty(node, "on");
315
+ if (transitions !== void 0 && !isPlainRecord(transitions)) throw new InvalidDomainMachineDefinitionError(`Domain machine state "${state}" transitions must be a plain object.`);
316
+ if (isPlainRecord(transitions)) assertDomainMachineDefinitionEntryMap(transitions, "input");
317
+ const inputTypes = Object.keys(transitions ?? {});
318
+ if (terminal === true && inputTypes.length > 0) throw new InvalidDomainMachineDefinitionError(`Terminal domain machine state "${state}" cannot declare transitions.`);
319
+ for (const inputType of inputTypes) {
320
+ const transition = readDomainMachineDefinitionProperty(transitions, inputType);
321
+ if (!isPlainRecord(transition)) throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" must be a plain object.`);
322
+ assertDomainMachineDefinitionDataProperties(transition, DOMAIN_MACHINE_TRANSITION_KEYS);
323
+ const target = readDomainMachineDefinitionProperty(transition, "target");
324
+ if (typeof target !== "string") throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" must target a string state.`);
325
+ if (!hasOwn(states, target)) throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" targets unknown state "${target}".`);
326
+ const guard = readOptionalDomainMachineDefinitionProperty(transition, "guard");
327
+ if (guard !== void 0 && typeof guard !== "function") throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" guard must be a function.`);
328
+ const reduce = readOptionalDomainMachineDefinitionProperty(transition, "reduce");
329
+ if (reduce !== void 0 && typeof reduce !== "function") throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" reduce must be a function.`);
330
+ }
331
+ }
332
+ }
333
+ function assertDomainMachineDefinitionDataProperties(value, allowedKeys) {
334
+ for (const key of Reflect.ownKeys(value)) {
335
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
336
+ if (descriptor !== void 0 && !("value" in descriptor)) throw new InvalidDomainMachineDefinitionError("Domain machine definition must contain data properties only.");
337
+ if (allowedKeys !== void 0 && !allowedKeys.has(key)) throw new InvalidDomainMachineDefinitionError(`Domain machine definition contains unknown property "${String(key)}".`);
338
+ }
339
+ }
340
+ function assertDomainMachineDefinitionEntryMap(value, entryName) {
341
+ assertDomainMachineDefinitionDataProperties(value);
342
+ for (const key of Reflect.ownKeys(value)) {
343
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
344
+ if (typeof key !== "string" || descriptor?.enumerable !== true) throw new InvalidDomainMachineDefinitionError(`Domain machine ${entryName} names must be enumerable string properties.`);
345
+ }
346
+ }
347
+ function readDomainMachineDefinitionProperty(value, key) {
348
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
349
+ if (descriptor === void 0 || !("value" in descriptor)) throw new InvalidDomainMachineDefinitionError("Domain machine definition must contain data properties only.");
350
+ return descriptor.value;
351
+ }
352
+ function readOptionalDomainMachineDefinitionProperty(value, key) {
353
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
354
+ if (descriptor === void 0) return void 0;
355
+ if (!("value" in descriptor)) throw new InvalidDomainMachineDefinitionError("Domain machine definition must contain data properties only.");
356
+ return descriptor.value;
357
+ }
358
+
359
+ //#endregion
360
+ //#region src/domain/state-machine/snapshot.ts
361
+ const DOMAIN_TRANSITION_RESULT_KEYS = /* @__PURE__ */ new Set(["context", "outputs"]);
362
+ function prepareDomainMachineSnapshot(definition, snapshot) {
363
+ validateDomainMachineSnapshot(definition, snapshot);
364
+ const preparedSnapshot = createDomainMachineSnapshot(snapshot);
365
+ validateDomainMachineSnapshot(definition, preparedSnapshot);
366
+ validateDomainMachineSnapshotInvariant(definition, preparedSnapshot);
367
+ return preparedSnapshot;
368
+ }
369
+ function createDomainMachineSnapshotFromPreparedContext(state, context) {
370
+ return Object.freeze({
371
+ state,
372
+ context
373
+ });
374
+ }
375
+ function createDomainMachineSnapshot(snapshot) {
376
+ return Object.freeze({
377
+ state: readDomainMachineSnapshotState(snapshot),
378
+ context: copyDomainMachineContext(readDomainMachineSnapshotContext(snapshot))
379
+ });
380
+ }
381
+ function validateDomainMachineSnapshot(definition, snapshot) {
382
+ if (!isRecord(snapshot)) throw new InvalidDomainMachineSnapshotError("Domain machine snapshot must be an object.");
383
+ const state = readDomainMachineSnapshotState(snapshot);
384
+ readDomainMachineSnapshotContext(snapshot);
385
+ if (!hasOwn(definition.states, state)) throw new InvalidDomainMachineSnapshotError(`Domain machine snapshot state "${state}" is not defined.`);
386
+ }
387
+ function validateDomainMachineSnapshotInvariant(definition, snapshot) {
388
+ const stateNode = definition.states[snapshot.state];
389
+ if (stateNode.validateContext !== void 0) {
390
+ const validContext = stateNode.validateContext({
391
+ state: snapshot.state,
392
+ context: snapshot.context
393
+ });
394
+ if (typeof validContext !== "boolean") throw new InvalidDomainMachineDefinitionError(`Domain machine state "${snapshot.state}" validateContext must return a boolean.`);
395
+ if (!validContext) throw new InvalidDomainMachineSnapshotError(`Domain machine snapshot violates the context invariant for state "${snapshot.state}".`);
396
+ }
397
+ if (definition.validateSnapshot === void 0) return;
398
+ const valid = definition.validateSnapshot(snapshot);
399
+ if (typeof valid !== "boolean") throw new InvalidDomainMachineDefinitionError("Domain machine validateSnapshot must return a boolean.");
400
+ if (!valid) throw new InvalidDomainMachineSnapshotError(`Domain machine snapshot violates invariants for state "${snapshot.state}".`);
401
+ }
402
+ function readDomainMachineSnapshotState(snapshot) {
403
+ const stateDescriptor = Object.getOwnPropertyDescriptor(snapshot, "state");
404
+ if (stateDescriptor === void 0 || !("value" in stateDescriptor) || typeof stateDescriptor.value !== "string") throw new InvalidDomainMachineSnapshotError("Domain machine snapshot state must be a string data property.");
405
+ return stateDescriptor.value;
406
+ }
407
+ function readDomainMachineSnapshotContext(snapshot) {
408
+ const contextDescriptor = Object.getOwnPropertyDescriptor(snapshot, "context");
409
+ if (contextDescriptor === void 0 || !("value" in contextDescriptor)) throw new InvalidDomainMachineSnapshotError("Domain machine snapshot context must be present as a data property.");
410
+ return contextDescriptor.value;
411
+ }
412
+ function validateDomainMachineInput(input) {
413
+ if (!isDomainMachineInput(input)) throw new InvalidDomainMachineInputError("Domain machine input must be an object with a string type.");
414
+ }
415
+ function isDomainMachineInput(input) {
416
+ if (!isRecord(input)) return false;
417
+ const typeDescriptor = Object.getOwnPropertyDescriptor(input, "type");
418
+ return typeDescriptor !== void 0 && "value" in typeDescriptor && typeof typeDescriptor.value === "string";
419
+ }
420
+ function resolveDomainTransitionGuardResult(result) {
421
+ if (typeof result === "boolean") return { allowed: result };
422
+ if (result instanceof DomainError) return {
423
+ allowed: false,
424
+ rejection: result
425
+ };
426
+ throw new InvalidDomainTransitionGuardResultError("Domain transition guard must return a boolean or DomainError.");
427
+ }
428
+ function validateDomainTransitionResult(result) {
429
+ if (result === void 0) return;
430
+ if (!isPlainRecord(result)) throw new InvalidDomainTransitionResultError("Domain transition result must be a plain object when returned.");
431
+ for (const key of Reflect.ownKeys(result)) {
432
+ if (!DOMAIN_TRANSITION_RESULT_KEYS.has(key)) throw new InvalidDomainTransitionResultError(`Domain transition result contains unknown property "${String(key)}".`);
433
+ const descriptor = Object.getOwnPropertyDescriptor(result, key);
434
+ if (descriptor !== void 0 && !("value" in descriptor)) throw new InvalidDomainTransitionResultError("Domain transition result must contain data properties only.");
435
+ }
436
+ const outputs = readDomainTransitionResultOutputs(result);
437
+ if (outputs !== void 0 && !Array.isArray(outputs)) throw new InvalidDomainTransitionResultError("Domain transition result outputs must be an array when provided.");
438
+ }
439
+ function readDomainTransitionResultContext(result) {
440
+ if (result === void 0) return { hasContext: false };
441
+ const contextDescriptor = Object.getOwnPropertyDescriptor(result, "context");
442
+ if (contextDescriptor === void 0) return { hasContext: false };
443
+ if (!("value" in contextDescriptor)) throw new InvalidDomainTransitionResultError("Domain transition result context must be a data property when provided.");
444
+ return {
445
+ hasContext: true,
446
+ context: contextDescriptor.value
447
+ };
448
+ }
449
+ function readDomainTransitionResultOutputs(result) {
450
+ if (result === void 0) return void 0;
451
+ const outputsDescriptor = Object.getOwnPropertyDescriptor(result, "outputs");
452
+ if (outputsDescriptor === void 0) return void 0;
453
+ if (!("value" in outputsDescriptor)) throw new InvalidDomainTransitionResultError("Domain transition result outputs must be a data property when provided.");
454
+ return outputsDescriptor.value;
455
+ }
456
+
457
+ //#endregion
458
+ //#region src/domain/state-machine/transition.ts
459
+ /** Creates and validates a fresh initial snapshot from a machine definition. */
460
+ function createInitialDomainMachineSnapshot(definition) {
461
+ return createInitialDomainMachineSnapshotFromPrepared(ensureStableDomainMachineDefinition(definition));
462
+ }
463
+ function createInitialDomainMachineSnapshotFromPrepared(definition) {
464
+ const snapshot = createDomainMachineSnapshot({
465
+ state: definition.initial,
466
+ context: definition.initialContext()
467
+ });
468
+ validateDomainMachineSnapshotInvariant(definition, snapshot);
469
+ return snapshot;
470
+ }
471
+ /**
472
+ * Checks whether an input currently has an allowed transition.
473
+ *
474
+ * Returns `false` for missing transitions, terminal states, rejected guards,
475
+ * and inputs without an own string `type` property. Invalid payload data for a
476
+ * matching transition and broken guard code still throw structured errors.
477
+ */
478
+ function canTransitionDomainState(definition, snapshot, input) {
479
+ const stableDefinition = ensureStableDomainMachineDefinition(definition);
480
+ return canTransitionPreparedDomainState(stableDefinition, prepareDomainMachineSnapshot(stableDefinition, snapshot), input);
481
+ }
482
+ function canTransitionPreparedDomainState(definition, snapshot, input) {
483
+ if (!isDomainMachineInput(input)) return false;
484
+ if (definition.states[snapshot.state].terminal === true) return false;
485
+ const transition = getTransition(definition, snapshot.state, input);
486
+ if (!transition) return false;
487
+ const currentInput = copyDomainMachineInput(input);
488
+ if (!transition.guard) return true;
489
+ const guardResult = transition.guard({
490
+ state: snapshot.state,
491
+ context: snapshot.context,
492
+ input: currentInput
493
+ });
494
+ return resolveDomainTransitionGuardResult(guardResult).allowed;
495
+ }
496
+ /**
497
+ * Applies one input without mutating the input definition or snapshot.
498
+ *
499
+ * @throws {@link InvalidDomainTransitionError} when no transition is defined.
500
+ * @throws {@link DomainTransitionGuardRejectedError} when its guard rejects.
501
+ * @throws A concrete `DomainError` returned by a rejecting guard.
502
+ */
503
+ function transitionDomainState(definition, snapshot, input) {
504
+ const stableDefinition = ensureStableDomainMachineDefinition(definition);
505
+ return transitionPreparedDomainState(stableDefinition, prepareDomainMachineSnapshot(stableDefinition, snapshot), input);
506
+ }
507
+ function transitionPreparedDomainState(definition, snapshot, input) {
508
+ validateDomainMachineInput(input);
509
+ const from = snapshot.state;
510
+ const transition = definition.states[from].terminal === true ? void 0 : getTransition(definition, from, input);
511
+ if (!transition) throw new InvalidDomainTransitionError(from, input.type);
512
+ const currentInput = copyDomainMachineInput(input);
513
+ const guardResult = transition.guard === void 0 ? true : transition.guard({
514
+ state: from,
515
+ context: snapshot.context,
516
+ input: currentInput
517
+ });
518
+ const guardDecision = resolveDomainTransitionGuardResult(guardResult);
519
+ if (!guardDecision.allowed) {
520
+ if (guardDecision.rejection !== void 0) throw guardDecision.rejection;
521
+ throw new DomainTransitionGuardRejectedError(from, currentInput.type);
522
+ }
523
+ const result = transition.reduce?.({
524
+ state: from,
525
+ context: snapshot.context,
526
+ input: currentInput
527
+ });
528
+ validateDomainTransitionResult(result);
529
+ const contextResult = readDomainTransitionResultContext(result);
530
+ const nextContext = contextResult.hasContext ? contextResult.context : snapshot.context;
531
+ const nextSnapshot = nextContext === snapshot.context ? createDomainMachineSnapshotFromPreparedContext(transition.target, snapshot.context) : createDomainMachineSnapshot({
532
+ state: transition.target,
533
+ context: nextContext
534
+ });
535
+ validateDomainMachineSnapshotInvariant(definition, nextSnapshot);
536
+ return Object.freeze({
537
+ from,
538
+ to: transition.target,
539
+ snapshot: nextSnapshot,
540
+ outputs: copyDomainMachineOutputs(readDomainTransitionResultOutputs(result))
541
+ });
542
+ }
543
+
544
+ //#endregion
545
+ //#region src/domain/state-machine/analyzer.ts
546
+ /**
547
+ * Inspects the declarative transition graph without executing definition callbacks.
548
+ * Guarded edges are treated as possible edges, so diagnostics never claim more
549
+ * runtime reachability than the static graph can prove.
550
+ */
551
+ function analyzeDomainMachineDefinition(definition) {
552
+ const stableDefinition = ensureStableDomainMachineDefinition(definition);
553
+ const states = Object.keys(stableDefinition.states).sort(compareStrings);
554
+ const outgoing = /* @__PURE__ */ new Map();
555
+ const incoming = /* @__PURE__ */ new Map();
556
+ const transitions = [];
557
+ for (const state of states) {
558
+ outgoing.set(state, []);
559
+ incoming.set(state, []);
560
+ }
561
+ for (const state of states) {
562
+ const stateTransitions = stableDefinition.states[state].on;
563
+ const inputTypes = Object.keys(stateTransitions ?? {}).sort(compareStrings);
564
+ for (const inputType of inputTypes) {
565
+ const transition = stateTransitions?.[inputType];
566
+ if (transition === void 0) continue;
567
+ outgoing.get(state)?.push(transition.target);
568
+ incoming.get(transition.target)?.push(state);
569
+ transitions.push(Object.freeze({
570
+ state,
571
+ inputType,
572
+ target: transition.target,
573
+ guarded: transition.guard !== void 0
574
+ }));
575
+ }
576
+ }
577
+ const structurallyReachable = visitGraph([stableDefinition.initial], outgoing);
578
+ const statesWithTerminalPath = visitGraph(states.filter((state) => stableDefinition.states[state].terminal === true), incoming);
579
+ const diagnostics = [];
580
+ for (const state of states) if (!structurallyReachable.has(state)) diagnostics.push(Object.freeze({
581
+ code: "unreachable-state",
582
+ state
583
+ }));
584
+ for (const state of states) if (stableDefinition.states[state].terminal !== true && outgoing.get(state)?.length === 0) diagnostics.push(Object.freeze({
585
+ code: "structural-dead-end",
586
+ state
587
+ }));
588
+ for (const state of states) if (!statesWithTerminalPath.has(state)) diagnostics.push(Object.freeze({
589
+ code: "no-terminal-path",
590
+ state
591
+ }));
592
+ return Object.freeze({
593
+ diagnostics: Object.freeze(diagnostics),
594
+ transitions: Object.freeze(transitions),
595
+ structurallyReachableStates: Object.freeze(states.filter((state) => structurallyReachable.has(state))),
596
+ statesWithTerminalPath: Object.freeze(states.filter((state) => statesWithTerminalPath.has(state)))
597
+ });
598
+ }
599
+ function visitGraph(startStates, edges) {
600
+ const visited = /* @__PURE__ */ new Set();
601
+ const pending = [...startStates];
602
+ while (pending.length > 0) {
603
+ const state = pending.pop();
604
+ if (state === void 0 || visited.has(state)) continue;
605
+ visited.add(state);
606
+ for (const next of edges.get(state) ?? []) if (!visited.has(next)) pending.push(next);
607
+ }
608
+ return visited;
609
+ }
610
+ function compareStrings(left, right) {
611
+ return left < right ? -1 : left > right ? 1 : 0;
612
+ }
613
+
614
+ //#endregion
615
+ //#region src/domain/state-machine/domain-state-machine.ts
616
+ /**
617
+ * Stateful convenience wrapper around the pure domain transition functions.
618
+ *
619
+ * Persist {@link snapshot}, not the machine instance. Pass a restored snapshot
620
+ * to the second constructor overload to validate and reconstitute a machine.
621
+ */
622
+ var DomainStateMachine = class {
623
+ definition;
624
+ #snapshot;
625
+ #evaluating = false;
626
+ constructor(definition, ...snapshotInput) {
627
+ this.definition = ensureStableDomainMachineDefinition(definition);
628
+ const [suppliedSnapshot] = snapshotInput;
629
+ if (suppliedSnapshot !== void 0) this.#snapshot = prepareDomainMachineSnapshot(this.definition, suppliedSnapshot);
630
+ else this.#snapshot = createInitialDomainMachineSnapshotFromPrepared(this.definition);
631
+ }
632
+ /** Returns a defensive, deeply frozen copy of the current snapshot. */
633
+ get snapshot() {
634
+ return createDomainMachineSnapshot(this.#snapshot);
635
+ }
636
+ /** Current named control state. */
637
+ get state() {
638
+ return this.#snapshot.state;
639
+ }
640
+ /** Current deeply readonly context. */
641
+ get context() {
642
+ return this.#snapshot.context;
643
+ }
644
+ /** Whether the current state permanently forbids outgoing transitions. */
645
+ isTerminal() {
646
+ return this.definition.states[this.state].terminal === true;
647
+ }
648
+ /** Checks a transition without changing the current snapshot. */
649
+ can(input) {
650
+ return this.evaluate(() => canTransitionPreparedDomainState(this.definition, this.#snapshot, input));
651
+ }
652
+ /** Applies an input and advances the current snapshot on success. */
653
+ dispatch(input) {
654
+ return this.evaluate(() => {
655
+ const result = transitionPreparedDomainState(this.definition, this.#snapshot, input);
656
+ this.#snapshot = result.snapshot;
657
+ return result;
658
+ });
659
+ }
660
+ evaluate(operation) {
661
+ if (this.#evaluating) throw new ReentrantDomainStateMachineEvaluationError();
662
+ this.#evaluating = true;
663
+ try {
664
+ return operation();
665
+ } finally {
666
+ this.#evaluating = false;
667
+ }
668
+ }
669
+ };
670
+
671
+ //#endregion
672
+ export { DomainStateMachine, DomainTransitionGuardRejectedError, InvalidDomainMachineContextError, InvalidDomainMachineDefinitionError, InvalidDomainMachineInputError, InvalidDomainMachineSnapshotError, InvalidDomainTransitionError, InvalidDomainTransitionGuardResultError, InvalidDomainTransitionResultError, ReentrantDomainStateMachineEvaluationError, analyzeDomainMachineDefinition, canTransitionDomainState, createInitialDomainMachineSnapshot, prepareDomainMachineDefinition, transitionDomainState };
673
+ //# sourceMappingURL=state-machine.js.map