@forgeax/engine-state 0.1.2

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 (44) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +87 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/cli-state.d.ts +10 -0
  5. package/dist/cli-state.d.ts.map +1 -0
  6. package/dist/cli-state.mjs +275 -0
  7. package/dist/cli-state.mjs.map +1 -0
  8. package/dist/conditions.d.ts +31 -0
  9. package/dist/conditions.d.ts.map +1 -0
  10. package/dist/define-state.d.ts +71 -0
  11. package/dist/define-state.d.ts.map +1 -0
  12. package/dist/errors.d.ts +70 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/index.d.ts +11 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.mjs +406 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/on-enter-on-exit.d.ts +65 -0
  19. package/dist/on-enter-on-exit.d.ts.map +1 -0
  20. package/dist/plugin-factory.d.ts +9 -0
  21. package/dist/plugin-factory.d.ts.map +1 -0
  22. package/dist/register-plugin.d.ts +27 -0
  23. package/dist/register-plugin.d.ts.map +1 -0
  24. package/dist/resources.d.ts +23 -0
  25. package/dist/resources.d.ts.map +1 -0
  26. package/dist/scoped-component.d.ts +38 -0
  27. package/dist/scoped-component.d.ts.map +1 -0
  28. package/dist/set-next-state.d.ts +48 -0
  29. package/dist/set-next-state.d.ts.map +1 -0
  30. package/dist/transition-system.d.ts +3 -0
  31. package/dist/transition-system.d.ts.map +1 -0
  32. package/package.json +62 -0
  33. package/src/cli-state.ts +232 -0
  34. package/src/conditions.ts +60 -0
  35. package/src/define-state.ts +158 -0
  36. package/src/errors.ts +147 -0
  37. package/src/index.ts +42 -0
  38. package/src/on-enter-on-exit.ts +156 -0
  39. package/src/plugin-factory.ts +19 -0
  40. package/src/register-plugin.ts +114 -0
  41. package/src/resources.ts +45 -0
  42. package/src/scoped-component.ts +159 -0
  43. package/src/set-next-state.ts +105 -0
  44. package/src/transition-system.ts +153 -0
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env node
2
+ import { defineComponent } from '@forgeax/engine-ecs';
3
+
4
+ // src/errors.ts
5
+ function makeError(code, expected, hint, detail) {
6
+ const error = {
7
+ code,
8
+ expected,
9
+ hint,
10
+ detail,
11
+ get message() {
12
+ return `[${code}] ${hint}`;
13
+ }
14
+ };
15
+ return error;
16
+ }
17
+ function stateNotRegistered(name) {
18
+ return makeError(
19
+ "state-not-registered",
20
+ "registerStatesPlugin(world) must be called before using setNextState / getState",
21
+ `State "${name}" has not been registered via registerStatesPlugin. createApp auto-registers the plugin in both canvas and assemble forms.`,
22
+ { code: "state-not-registered", name }
23
+ );
24
+ }
25
+ function invalidVariant(name, got, valid) {
26
+ const validSnapshot = [...valid];
27
+ return makeError(
28
+ "invalid-variant",
29
+ `Variant must be one of: ${validSnapshot.join(", ")}`,
30
+ `"${got}" is not a valid variant for state "${name}". Did you mean one of: ${validSnapshot.join(", ")}? Check for typos.`,
31
+ { code: "invalid-variant", name, got, valid: validSnapshot }
32
+ );
33
+ }
34
+
35
+ // src/define-state.ts
36
+ var STATE_REGISTRY = /* @__PURE__ */ new Map();
37
+ function getRegisteredTokens() {
38
+ return STATE_REGISTRY;
39
+ }
40
+ var SCOPED_COMPONENTS = /* @__PURE__ */ new Map();
41
+ var SCOPED_MODE_VALUE = { exit: 0, enter: 1 };
42
+ function getOrCreateScopedComponent(token) {
43
+ const existing = SCOPED_COMPONENTS.get(token.name);
44
+ if (existing) return existing;
45
+ const valueLabels = {};
46
+ token.variants.forEach((v, i) => {
47
+ valueLabels[v] = i;
48
+ });
49
+ const comp = defineComponent(`__scopedTo__${token.name}`, {
50
+ value: { type: "enum", default: 0, labels: valueLabels },
51
+ mode: { type: "enum", default: SCOPED_MODE_VALUE.exit, labels: SCOPED_MODE_VALUE }
52
+ });
53
+ SCOPED_COMPONENTS.set(token.name, comp);
54
+ return comp;
55
+ }
56
+ function countScopedEntitiesByVariant(world, token) {
57
+ const counts = new Array(token.variants.length).fill(0);
58
+ const scoped = getOrCreateScopedComponent(token);
59
+ const query = world.query({ read: [scoped] }).unwrap();
60
+ for (const row of query) {
61
+ const idx = row.get(scoped).value;
62
+ if (typeof idx !== "number") continue;
63
+ if (idx >= 0 && idx < counts.length) counts[idx] = (counts[idx] ?? 0) + 1;
64
+ }
65
+ return counts;
66
+ }
67
+
68
+ // src/resources.ts
69
+ var STATE_PREFIX = "__state__";
70
+ var PREVIOUS_STATE_PREFIX = "__previousState__";
71
+ function stateResourceKey(token) {
72
+ return `${STATE_PREFIX}${token.name}`;
73
+ }
74
+ function previousStateResourceKey(token) {
75
+ return `${PREVIOUS_STATE_PREFIX}${token.name}`;
76
+ }
77
+
78
+ // src/set-next-state.ts
79
+ function errWrap(err) {
80
+ return { ok: false, error: err };
81
+ }
82
+ function getState(world, token) {
83
+ const key = stateResourceKey(token);
84
+ if (!world.hasResource(key)) {
85
+ return errWrap(stateNotRegistered(token.name));
86
+ }
87
+ const idx = world.getResource(key);
88
+ const variant = token.variants[idx];
89
+ if (variant === void 0) {
90
+ return errWrap(invalidVariant(token.name, String(idx), token.variants));
91
+ }
92
+ return { ok: true, value: variant };
93
+ }
94
+ function getPreviousState(world, token) {
95
+ const key = previousStateResourceKey(token);
96
+ if (!world.hasResource(key)) {
97
+ return errWrap(stateNotRegistered(token.name));
98
+ }
99
+ const idx = world.getResource(key);
100
+ const variant = token.variants[idx];
101
+ if (variant === void 0) {
102
+ return errWrap(invalidVariant(token.name, String(idx), token.variants));
103
+ }
104
+ return { ok: true, value: variant };
105
+ }
106
+
107
+ // src/cli-state.ts
108
+ function helpBody() {
109
+ return [
110
+ "forgeax-engine-remote-state - inspect forgeax state machines",
111
+ "",
112
+ "Usage:",
113
+ " forgeax-engine-remote-state <subcommand> [args]",
114
+ "",
115
+ "Subcommands:",
116
+ " list list all registered tokens: name, current, previous, default, variants",
117
+ " get <name> print one token: current, previous, default, variants + per-variant scoped entity counts",
118
+ "",
119
+ "Flags:",
120
+ " --help, -h show this help and exit 0",
121
+ "",
122
+ "Examples:",
123
+ " forgeax-engine-remote-state list",
124
+ " forgeax-engine-remote-state get LevelId",
125
+ ""
126
+ ].join("\n");
127
+ }
128
+ function subcommandHelp(subcommand) {
129
+ if (subcommand === "list") {
130
+ return [
131
+ "forgeax-engine-remote-state list - list all registered state tokens",
132
+ "",
133
+ "Usage:",
134
+ " forgeax-engine-remote-state list",
135
+ "",
136
+ "Output format: one line per token:",
137
+ " <tokenName>: <current> (previous: <previous>, default: <default>, variants: <v1>, <v2>, ...)",
138
+ "",
139
+ "Examples:",
140
+ " forgeax-engine-remote-state list",
141
+ ""
142
+ ].join("\n");
143
+ }
144
+ if (subcommand === "get") {
145
+ return [
146
+ "forgeax-engine-remote-state get - inspect one state token",
147
+ "",
148
+ "Usage:",
149
+ " forgeax-engine-remote-state get <tokenName>",
150
+ "",
151
+ "Output: current / previous / default variant, the full variants list,",
152
+ "and the count of ScopedTo entities currently scoped to each variant.",
153
+ "",
154
+ "Examples:",
155
+ " forgeax-engine-remote-state get LevelId",
156
+ " # current: main-menu",
157
+ " # previous: main-menu",
158
+ " # default: main-menu",
159
+ " # variants: main-menu (0), tutorial (0), street-a (0)",
160
+ ""
161
+ ].join("\n");
162
+ }
163
+ return helpBody();
164
+ }
165
+ function runList(world, stdout) {
166
+ const tokens = getRegisteredTokens();
167
+ if (tokens.size === 0) {
168
+ stdout("(no state tokens registered)");
169
+ return;
170
+ }
171
+ for (const [, token] of tokens) {
172
+ const current = getCurrentStateString(world, token);
173
+ const previous = getPreviousStateString(world, token);
174
+ const variantsStr = token.variants.join(", ");
175
+ stdout(
176
+ `${token.name}: ${current} (previous: ${previous}, default: ${token.defaultValue}, variants: ${variantsStr})`
177
+ );
178
+ }
179
+ }
180
+ function runGet(world, tokenName, stdout, stderr) {
181
+ const tokens = getRegisteredTokens();
182
+ const token = tokens.get(tokenName);
183
+ if (token === void 0) {
184
+ stderr(
185
+ [
186
+ `forgeax: unknown state token "${tokenName}"`,
187
+ ` expected: one of ${[...tokens.keys()].map((k) => `"${k}"`).join(", ") || "(none registered)"}`,
188
+ " hint: use 'forgeax-engine-remote-state list' to see registered tokens"
189
+ ].join("\n")
190
+ );
191
+ return 1;
192
+ }
193
+ const result = getState(world, token);
194
+ if (!result.ok) {
195
+ stderr(
196
+ [
197
+ `forgeax: ${result.error.code}`,
198
+ ` expected: ${result.error.expected}`,
199
+ ` hint: ${result.error.hint}`
200
+ ].join("\n")
201
+ );
202
+ return 1;
203
+ }
204
+ const previous = getPreviousStateString(world, token);
205
+ const counts = countScopedEntitiesByVariant(world, token);
206
+ const variantsLine = token.variants.map((v, i) => `${v} (${counts[i] ?? 0})`).join(", ");
207
+ stdout(`current: ${result.value}`);
208
+ stdout(`previous: ${previous}`);
209
+ stdout(`default: ${token.defaultValue}`);
210
+ stdout(`variants: ${variantsLine}`);
211
+ return 0;
212
+ }
213
+ function getCurrentStateString(world, token) {
214
+ const result = getState(world, token);
215
+ if (result.ok) return result.value;
216
+ return "<error>";
217
+ }
218
+ function getPreviousStateString(world, token) {
219
+ const result = getPreviousState(world, token);
220
+ if (result.ok) return result.value;
221
+ return "<error>";
222
+ }
223
+ async function dispatch(opts) {
224
+ const { argv, stdoutWrite, stderrWrite, world } = opts;
225
+ const args = argv.slice(2);
226
+ const subcommand = args[0];
227
+ if (subcommand === void 0) {
228
+ stderrWrite("forgeax: expected subcommand (list or get); use --help for usage");
229
+ return 1;
230
+ }
231
+ if (subcommand === "--help" || subcommand === "-h") {
232
+ stdoutWrite(helpBody());
233
+ return 0;
234
+ }
235
+ if (subcommand === "list") {
236
+ const listArgs = args.slice(1);
237
+ if (listArgs[0] === "--help" || listArgs[0] === "-h") {
238
+ stdoutWrite(subcommandHelp("list"));
239
+ return 0;
240
+ }
241
+ runList(world, stdoutWrite);
242
+ return 0;
243
+ }
244
+ if (subcommand === "get") {
245
+ const getArgs = args.slice(1);
246
+ if (getArgs[0] === "--help" || getArgs[0] === "-h") {
247
+ stdoutWrite(subcommandHelp("get"));
248
+ return 0;
249
+ }
250
+ const tokenName = getArgs[0];
251
+ if (tokenName === void 0) {
252
+ stderrWrite(
253
+ [
254
+ "forgeax: state get requires a <tokenName> positional argument",
255
+ " expected: forgeax-engine-remote-state get <tokenName>",
256
+ " hint: use 'forgeax-engine-remote-state list' to see registered tokens"
257
+ ].join("\n")
258
+ );
259
+ return 1;
260
+ }
261
+ return runGet(world, tokenName, stdoutWrite, stderrWrite);
262
+ }
263
+ stderrWrite(
264
+ [
265
+ `forgeax: unknown subcommand "${subcommand}"`,
266
+ " expected: list or get",
267
+ " hint: run 'forgeax-engine-remote-state --help' for usage"
268
+ ].join("\n")
269
+ );
270
+ return 1;
271
+ }
272
+
273
+ export { dispatch };
274
+ //# sourceMappingURL=cli-state.mjs.map
275
+ //# sourceMappingURL=cli-state.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/define-state.ts","../src/scoped-component.ts","../src/resources.ts","../src/set-next-state.ts","../src/cli-state.ts"],"names":[],"mappings":"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","file":"cli-state.mjs","sourcesContent":["// @forgeax/engine-state -- error model SSOT (feat-20260616-engine-state-and-state-scoped-entities M1 / m1w4)\n//\n// Closed union StateErrorCode (4 members), discriminated detail union,\n// and structured StateError carrying .code / .expected / .hint / .detail.\n//\n// Decision anchors:\n// - plan-strategy D-4: 4-code order-locked closed union + discriminated detail\n// - requirements sec 2.7: error code union, defineState throws (programmer error),\n// setNextState / getState return Result.err (runtime AI user calls)\n// - AGENTS.md Error model: structured errors with .expected / .hint / .detail,\n// never throw for runtime paths; exhaustive switch without default\n\n/** {@link state-already-defined} payload: carries the conflicting name and optional first-definition site. */\nexport interface StateAlreadyDefinedDetail {\n readonly code: 'state-already-defined';\n readonly name: string;\n readonly firstDefinedAt: string | undefined;\n}\n\n/** {@link state-not-registered} payload: carries the token name that has no plugin registration. */\nexport interface StateNotRegisteredDetail {\n readonly code: 'state-not-registered';\n readonly name: string;\n}\n\n/** {@link invalid-variant} payload: carries the token name, the invalid variant string, and the valid variants list. */\nexport interface InvalidVariantDetail {\n readonly code: 'invalid-variant';\n readonly name: string;\n readonly got: string;\n readonly valid: readonly string[];\n}\n\n/** {@link state-default-required} payload: carries the token name whose variants array was empty. */\nexport interface StateDefaultRequiredDetail {\n readonly code: 'state-default-required';\n readonly name: string;\n}\n\ninterface StateErrorDetailByCode {\n 'state-already-defined': StateAlreadyDefinedDetail;\n 'state-not-registered': StateNotRegisteredDetail;\n 'invalid-variant': InvalidVariantDetail;\n 'state-default-required': StateDefaultRequiredDetail;\n}\n\n/**\n * Closed {@link StateErrorCode} union -- 4 members, order-locked.\n * Exhaustive `switch (err.code)` needs no default fallback.\n *\n * | code | trigger |\n * |:--|:--|\n * | `'state-already-defined'` | `defineState()` called with a name already registered |\n * | `'state-not-registered'` | `setNextState()` / `getState()` called before `registerStatesPlugin()` |\n * | `'invalid-variant'` | `setNextState()` called with a variant string not in the token's variants tuple |\n * | `'state-default-required'` | `defineState()` called with empty variants array |\n */\nexport type StateErrorCode = keyof StateErrorDetailByCode;\n\n/**\n * Discriminated detail union for {@link StateError}, narrowed per\n * `StateError.code`. AI users obtain the concrete shape via\n * `switch (err.code)` without a fallback `as` cast.\n */\nexport type StateErrorDetail = StateErrorDetailByCode[StateErrorCode];\n\n/**\n * Structured state-machine error -- four-field surface\n * (`.code` / `.expected` / `.hint` / `.detail`).\n *\n * AI users consume the structured triple by fields, not by parsing `.message`.\n */\ntype StateErrorVariant<C extends StateErrorCode> = {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: StateErrorDetailByCode[C];\n};\n\nexport type StateError = {\n [C in StateErrorCode]: StateErrorVariant<C>;\n}[StateErrorCode];\n\nfunction makeError<C extends StateErrorCode>(\n code: C,\n expected: string,\n hint: string,\n detail: StateErrorDetailByCode[C],\n): StateErrorVariant<C> {\n const error = {\n code,\n expected,\n hint,\n detail,\n get message(): string {\n return `[${code}] ${hint}`;\n },\n };\n return error;\n}\n\n/** Convenience throw wrapper for programmer errors (defineState constructor phase). */\nexport function throwStateError<C extends StateErrorCode>(\n code: C,\n expected: string,\n hint: string,\n detail: StateErrorDetailByCode[C],\n): never {\n throw makeError(code, expected, hint, detail);\n}\n\nexport function stateAlreadyDefined(name: string, firstDefinedAt?: string): StateError {\n return makeError(\n 'state-already-defined',\n 'Each StateToken name must be registered exactly once at module level',\n `State \"${name}\" is already defined${firstDefinedAt ? ` (first defined at ${firstDefinedAt})` : ''}. Use the existing token.`,\n { code: 'state-already-defined', name, firstDefinedAt },\n );\n}\n\nexport function stateNotRegistered(name: string): StateError {\n return makeError(\n 'state-not-registered',\n 'registerStatesPlugin(world) must be called before using setNextState / getState',\n `State \"${name}\" has not been registered via registerStatesPlugin. createApp auto-registers the plugin in both canvas and assemble forms.`,\n { code: 'state-not-registered', name },\n );\n}\n\nexport function invalidVariant(name: string, got: string, valid: readonly string[]): StateError {\n const validSnapshot = [...valid];\n return makeError(\n 'invalid-variant',\n `Variant must be one of: ${validSnapshot.join(', ')}`,\n `\"${got}\" is not a valid variant for state \"${name}\". Did you mean one of: ${validSnapshot.join(', ')}? Check for typos.`,\n { code: 'invalid-variant', name, got, valid: validSnapshot },\n );\n}\n\nexport function stateDefaultRequired(name: string): StateError {\n return makeError(\n 'state-default-required',\n 'defineState requires at least one variant (non-empty array)',\n `State \"${name}\" was defined with an empty variants array. Provide at least one variant, e.g. defineState(\"${name}\", [\"default\"] as const).`,\n { code: 'state-default-required', name },\n );\n}\n","// @forgeax/engine-state -- defineState + StateToken SSOT (feat-20260616 M1 / m1w2)\n//\n// Module-level state registration: defineState(name, variants as const) returns a\n// branded StateToken. The token holds the variants vocabulary (name <-> idx lookup)\n// and is used both as a compile-time type witness and as a runtime key for Resource\n// registration.\n//\n// Decision anchors:\n// - plan-strategy D-1: variants vocabulary lives in StateToken, not in ECS types\n// - plan-strategy D-4: defineState throws on programmer errors (duplicate name / empty variants)\n// - plan-strategy sec 8.1: as const + readonly tuple for compile-time variant narrowing\n// - defineState registers schemas at module level; active state plugins project them into Worlds\n\nimport { throwStateError } from './errors';\n\n/**\n * Opaque branded type for type-level state-machine tokens.\n *\n * Use {@link defineState} to create a token; never construct manually.\n * The {@link __forgeaxState} brand prevents plain-object assignment and\n * enables TypeScript narrowing of variant literal types.\n *\n * @typeParam Name - The string literal name of the state machine.\n * @typeParam V - The union of variant string literals derived from the const tuple.\n */\nexport interface StateToken<Name extends string = string, V extends string = string> {\n /** Brand -- prevents structural compatibility with plain objects. */\n readonly __forgeaxState: typeof FORGEAX_STATE_BRAND;\n /** The user-supplied state-machine name. */\n readonly name: Name;\n /** The ordered, read-only variants tuple. */\n readonly variants: readonly V[];\n /** Fast lookup: variant string -> its zero-based index in `variants`. */\n readonly nameToIdx: ReadonlyMap<V, number>;\n /** Convenience: `variants[0]`, the default / initial state value. */\n readonly defaultValue: V;\n}\n\n/**\n * Brand symbol for {@link StateToken}. Declared (not runtime-initialised) so\n * the token interface carries nominal identity without a runtime allocation.\n */\ndeclare const FORGEAX_STATE_BRAND: unique symbol;\n\n/**\n * Extract the variant union from a {@link StateToken}.\n *\n * ```ts\n * const L = defineState('LevelId', ['menu', 'game'] as const);\n * type LevelVariant = StateTokenVariant<typeof L>;\n * // ^? 'menu' | 'game'\n * ```\n */\nexport type StateTokenVariant<T extends StateToken> =\n T extends StateToken<infer _Name, infer V> ? V : never;\n\n/**\n * Extract the name literal from a {@link StateToken}.\n */\nexport type StateTokenName<T extends StateToken> =\n T extends StateToken<infer Name, infer _V> ? Name : never;\n\n/**\n * Global registry of all state tokens, keyed by token name.\n *\n * `defineState` writes here; active state plugins project it into Worlds;\n * cli-state reflection also reads it.\n */\nconst STATE_REGISTRY = new Map<string, StateToken>();\nconst STATE_DEFINED_LISTENERS = new Set<(token: StateToken) => void>();\n\n/**\n * Internal: get the read-only snapshot of all registered tokens.\n * Exported for M2 registerStatesPlugin and M6 cli-state.\n */\nexport function getRegisteredTokens(): ReadonlyMap<string, StateToken> {\n return STATE_REGISTRY;\n}\n\n/** @internal Subscribe one active World adapter to future module-level tokens. */\nexport function onStateDefined(listener: (token: StateToken) => void): () => void {\n STATE_DEFINED_LISTENERS.add(listener);\n return () => STATE_DEFINED_LISTENERS.delete(listener);\n}\n\n/**\n * Define a typed state machine.\n *\n * Must be called at module level. The `as const` assertion on the variants\n * array enables TypeScript to infer the exact literal tuple type, giving\n * compile-time narrowing on variant parameters (e.g. `setNextState(world,\n * token, 'misspell')` is a type error).\n *\n * @param name - Unique state-machine identifier (e.g. `'LevelId'`).\n * @param variants - Readonly tuple of variant string literals. Must be non-empty.\n * @returns A branded {@link StateToken} for use with `setNextState`, `getState`, etc.\n * @throws StateError if `name` is already registered or `variants` is empty.\n *\n * ```ts\n * export const LevelId = defineState('LevelId', ['main-menu', 'tutorial', 'street-a'] as const);\n * // LevelId.variants -> readonly ['main-menu', 'tutorial', 'street-a']\n * // LevelId.nameToIdx.get('tutorial') -> 1\n * // LevelId.defaultValue -> 'main-menu'\n * ```\n */\nexport function defineState<Name extends string, const Variants extends readonly string[]>(\n name: Name,\n variants: Variants,\n): StateToken<Name, Variants[number]> {\n if (STATE_REGISTRY.has(name)) {\n throwStateError(\n 'state-already-defined',\n 'Each StateToken name must be registered exactly once at module level',\n `State \"${name}\" is already defined. Use the existing token.`,\n { code: 'state-already-defined', name, firstDefinedAt: undefined },\n );\n }\n\n if (variants.length === 0) {\n throwStateError(\n 'state-default-required',\n 'defineState requires at least one variant (non-empty array)',\n `State \"${name}\" was defined with an empty variants array. Provide at least one variant, e.g. defineState(\"${name}\", [\"default\"] as const).`,\n { code: 'state-default-required', name },\n );\n }\n\n // Check for duplicate variants within the array\n const seen = new Set<string>();\n for (const v of variants) {\n if (seen.has(v)) {\n throwStateError(\n 'state-default-required',\n 'Variants must be unique within a state token',\n `State \"${name}\" has duplicate variant \"${v}\". Each variant must appear exactly once.`,\n { code: 'state-default-required', name },\n );\n }\n seen.add(v);\n }\n\n const nameToIdx = new Map<Variants[number], number>();\n for (let i = 0; i < variants.length; i++) {\n nameToIdx.set(variants[i] as Variants[number], i);\n }\n\n const token = {\n __forgeaxState: undefined as unknown as typeof FORGEAX_STATE_BRAND,\n name,\n variants,\n nameToIdx,\n defaultValue: variants[0] as Variants[number],\n } as StateToken<Name, Variants[number]>;\n\n STATE_REGISTRY.set(name, token as unknown as StateToken);\n for (const listener of STATE_DEFINED_LISTENERS) listener(token as unknown as StateToken);\n return token;\n}\n","// @forgeax/engine-state -- ScopedTo components + despawnOnExit/Enter (M3 / m3w2)\n//\n// Per-token __scopedTo__<name> components: defineComponent with two enum fields\n// (value = u32 variant index, mode = exit/enter enum). Components are lazily\n// created on first use via getOrCreateScopedComponent.\n//\n// despawnOnExit / despawnOnEnter are free functions that add the corresponding\n// ScopedTo component to an entity. On duplicate add the ECS returns\n// ComponentAlreadyPresentError, which we throw.\n//\n// Decision anchors:\n// - plan-strategy D-1: value field uses 'enum' (u32 index into token.variants)\n// - requirements F-7/F-8: despawnOnExit/despawnOnEnter free functions\n// - requirements AC-11: duplicate add fail-fast via ECS default exclusive=false\n// - research F-2: 'enum' is already a ScalarFieldType\n\nimport { defineComponent, type EntityHandle, type World } from '@forgeax/engine-ecs';\nimport type { StateToken, StateTokenVariant } from './define-state';\nimport { getRegisteredTokens } from './define-state';\n\nconst SCOPED_COMPONENTS = new Map<string, ReturnType<typeof defineComponent>>();\n\n/** Fixed label-to-value map for the ScopedTo `mode` enum field. */\nexport const SCOPED_MODE_VALUE = { exit: 0, enter: 1 } as const;\n\nfunction getOrCreateScopedComponent(token: StateToken): ReturnType<typeof defineComponent> {\n const existing = SCOPED_COMPONENTS.get(token.name);\n if (existing) return existing;\n\n // `value` is the token's variant index — derive its label→index map from\n // `token.variants` so reflection (describeComponent) can name each variant;\n // `mode` uses the fixed exit/enter map. Both attach labels to the enum field\n // (Derive, don't Duplicate: the variant order IS the label source).\n const valueLabels: Record<string, number> = {};\n token.variants.forEach((v, i) => {\n valueLabels[v] = i;\n });\n\n const comp = defineComponent(`__scopedTo__${token.name}`, {\n value: { type: 'enum', default: 0, labels: valueLabels },\n mode: { type: 'enum', default: SCOPED_MODE_VALUE.exit, labels: SCOPED_MODE_VALUE },\n });\n SCOPED_COMPONENTS.set(token.name, comp);\n return comp;\n}\n\n/** Resolve the world-local ScopedTo token for a state. */\nexport function getScopedComponent(token: StateToken): ReturnType<typeof defineComponent> {\n return getOrCreateScopedComponent(token);\n}\n\nfunction resolveVariantIndex(token: StateToken, variant: string): number {\n const idx = token.nameToIdx.get(variant as never);\n if (idx === undefined) {\n throw new Error(\n `Invalid variant \"${variant}\" for state \"${token.name}\". Valid: ${token.variants.join(', ')}`,\n );\n }\n return idx;\n}\n\n/**\n * Mark `entity` to be despawned when `token` leaves `variant`.\n *\n * Adds a `__scopedTo__<token.name>` component with mode=exit\n * and value=<variant index>. When transitionStatesSystem detects\n * the token transitions away from `variant`, it despawns the entity.\n *\n * Throws if `entity` already carries this token's ScopedTo component\n * (ECS default exclusive=false fail-fast).\n */\nfunction addScopedComponent(\n world: World,\n entity: EntityHandle,\n scoped: ReturnType<typeof defineComponent>,\n value: number,\n mode: number,\n): ReturnType<typeof world.addComponent> {\n return world.addComponent(entity, {\n component: scoped,\n // generic ComponentSchema loses the concrete {value, mode} enum-field\n // types; data is u32 at runtime.\n data: { value, mode } as Record<string, unknown> as Parameters<\n typeof world.addComponent\n >[1]['data'],\n });\n}\n\nexport function despawnOnExit<T extends StateToken>(\n world: World,\n entity: EntityHandle,\n token: T,\n variant: StateTokenVariant<T>,\n): void {\n const idx = resolveVariantIndex(token, variant);\n const scoped = getOrCreateScopedComponent(token);\n const result = addScopedComponent(world, entity, scoped, idx, SCOPED_MODE_VALUE.exit);\n if (!result.ok) {\n throw result.error;\n }\n}\n\n/**\n * Mark `entity` to be despawned when `token` enters `variant`.\n *\n * Adds a `__scopedTo__<token.name>` component with mode=enter\n * and value=<variant index>. When transitionStatesSystem detects\n * the token transitions into `variant`, it despawns the entity.\n *\n * Throws if `entity` already carries this token's ScopedTo component\n * (ECS default exclusive=false fail-fast).\n */\nexport function despawnOnEnter<T extends StateToken>(\n world: World,\n entity: EntityHandle,\n token: T,\n variant: StateTokenVariant<T>,\n): void {\n const idx = resolveVariantIndex(token, variant);\n const scoped = getOrCreateScopedComponent(token);\n const result = addScopedComponent(world, entity, scoped, idx, SCOPED_MODE_VALUE.enter);\n if (!result.ok) {\n throw result.error;\n }\n}\n\n/**\n * Pre-register scoped components for all state tokens in the global registry.\n * Called by registerStatesPlugin during boot; idempotent.\n *\n * @internal\n */\nexport function registerScopedComponents(): void {\n for (const token of getRegisteredTokens().values()) {\n getOrCreateScopedComponent(token);\n }\n}\n\n/**\n * Count entities carrying `token`'s ScopedTo component, grouped by the variant\n * index they are scoped to (irrespective of exit/enter mode). Returns an array\n * aligned to `token.variants` (index i = count for `token.variants[i]`).\n *\n * Used by the `state get <name>` CLI inspector to report per-variant scoped\n * entity counts (requirements AC-15). Reflection only — does not mutate World.\n */\nexport function countScopedEntitiesByVariant(world: World, token: StateToken): number[] {\n const counts = new Array<number>(token.variants.length).fill(0);\n const scoped = getOrCreateScopedComponent(token);\n const query = world.query({ read: [scoped] }).unwrap();\n for (const row of query) {\n const idx = row.get(scoped).value;\n if (typeof idx !== 'number') continue;\n if (idx >= 0 && idx < counts.length) counts[idx] = (counts[idx] ?? 0) + 1;\n }\n return counts;\n}\n\n// resolveScopedComponent removed — transitionStatesSystem uses the World-local catalog.\n","// @forgeax/engine-state -- Resource key constructors (feat-20260616 M1 / m1w2)\n//\n// Three Resource keys per StateToken: State (current value), NextState (pending\n// transition request), PreviousState (cached last-frame value for OnExit / getPreviousState).\n//\n// Decision anchors:\n// - plan-strategy D-1: Resources are per-token, keyed by __state__ prefix\n// - plan-strategy D-4: keys are pure string constructors; Resource CRUD is in registerStatesPlugin M2\n// - requirements F-4/F-5/F-6: State / NextState / PreviousState as Resources\n\nimport type { StateToken } from './define-state';\n\nconst STATE_PREFIX = '__state__';\nconst NEXT_STATE_PREFIX = '__nextState__';\nconst PREVIOUS_STATE_PREFIX = '__previousState__';\n\n/**\n * Resource key for the current state value of a token.\n *\n * The {@link StateTokenVariant} is stored; `getState(world, token)` reads\n * this Resource and decodes the index to a variant string.\n */\nexport function stateResourceKey(token: StateToken): string {\n return `${STATE_PREFIX}${token.name}`;\n}\n\n/**\n * Resource key for the pending next-state transition request.\n *\n * Written by `setNextState` / `setNextStateForce` (M2); consumed by\n * `transitionStatesSystem` (M3).\n */\nexport function nextStateResourceKey(token: StateToken): string {\n return `${NEXT_STATE_PREFIX}${token.name}`;\n}\n\n/**\n * Resource key for the previous-frame state value.\n *\n * Written by `transitionStatesSystem` before flipping `State`; read by\n * `getPreviousState(world, token)` (M2).\n */\nexport function previousStateResourceKey(token: StateToken): string {\n return `${PREVIOUS_STATE_PREFIX}${token.name}`;\n}\n","// @forgeax/engine-state -- setNextState / getState / getPreviousState (M2 / m2w4)\n//\n// Free functions that read and write per-token Resource slots. All return\n// Result<T, StateError> (never throw for AI-user call sites per AGENTS.md\n// Error model).\n//\n// Decision anchors:\n// - requirements C-3: setNextState returns Result.err, not throw\n// - requirements C-4: free functions, not world.x methods\n// - requirements F-5/F-6: State / NextState / PreviousState as Resources\n// - plan-strategy D-4: State stores variant index (u32), decoded via token.variants\n\nimport type { World } from '@forgeax/engine-ecs';\nimport type { StateToken, StateTokenVariant } from './define-state';\nimport type { StateError } from './errors';\nimport { invalidVariant, stateNotRegistered } from './errors';\nimport { nextStateResourceKey, previousStateResourceKey, stateResourceKey } from './resources';\n\ninterface NextStatePayload {\n value: number;\n force: boolean;\n}\n\nfunction errWrap(err: StateError): { ok: false; error: StateError } {\n return { ok: false, error: err };\n}\n\n/**\n * Request a state transition for `token` to `variant` at the next frame.\n *\n * `variant` is narrowed to the token's variant union: a misspelled variant is\n * a compile-time error (`StateTokenVariant<T>`), not just a runtime\n * `invalid-variant` Result.\n */\nexport function setNextState<T extends StateToken>(\n world: World,\n token: T,\n variant: StateTokenVariant<T>,\n): { ok: true; value: undefined } | { ok: false; error: StateError } {\n return _runCheckAndWrite(world, token, variant, false);\n}\n\n/**\n * Like {@link setNextState} but with `force=true`.\n */\nexport function setNextStateForce<T extends StateToken>(\n world: World,\n token: T,\n variant: StateTokenVariant<T>,\n): { ok: true; value: undefined } | { ok: false; error: StateError } {\n return _runCheckAndWrite(world, token, variant, true);\n}\n\nfunction _runCheckAndWrite(world: World, token: StateToken, variant: string, force: boolean) {\n const nsKey = nextStateResourceKey(token);\n if (!world.hasResource(nsKey)) {\n return errWrap(stateNotRegistered(token.name));\n }\n\n const idx = token.nameToIdx.get(variant as never);\n if (idx === undefined) {\n return errWrap(invalidVariant(token.name, variant, token.variants));\n }\n\n world.insertResource<NextStatePayload>(nsKey, { value: idx, force });\n return { ok: true as const, value: undefined };\n}\n\n/**\n * Read the current state value for `token`.\n */\nexport function getState(\n world: World,\n token: StateToken,\n): { ok: true; value: string } | { ok: false; error: StateError } {\n const key = stateResourceKey(token);\n if (!world.hasResource(key)) {\n return errWrap(stateNotRegistered(token.name));\n }\n const idx = world.getResource<number>(key);\n const variant = token.variants[idx];\n if (variant === undefined) {\n return errWrap(invalidVariant(token.name, String(idx), token.variants));\n }\n return { ok: true as const, value: variant };\n}\n\n/**\n * Read the previous-frame state value for `token`.\n */\nexport function getPreviousState(\n world: World,\n token: StateToken,\n): { ok: true; value: string } | { ok: false; error: StateError } {\n const key = previousStateResourceKey(token);\n if (!world.hasResource(key)) {\n return errWrap(stateNotRegistered(token.name));\n }\n const idx = world.getResource<number>(key);\n const variant = token.variants[idx];\n if (variant === undefined) {\n return errWrap(invalidVariant(token.name, String(idx), token.variants));\n }\n return { ok: true as const, value: variant };\n}\n","#!/usr/bin/env node\n// @forgeax/engine-state/src/cli-state - forgeax-engine-remote-state plugin bin\n// (feat-20260616-engine-state-and-state-scoped-entities M6 / m6w2).\n//\n// Two subcommands:\n// state list — iterates getRegisteredTokens() and prints name + variants + current state\n// state get <name> — calls getState and prints the variant string\n//\n// Discovery: kubectl 4th-path. The base bin `forgeax-engine-console` finds\n// this binary on PATH via the `forgeax-engine-remote-` prefix scan and\n// forwards stdio + exit code (see packages/console/src/discoverPlugins.ts).\n//\n// The `connect` field in DispatchOptions is optional — this CLI plugin reads\n// from a directly supplied World reference (not through JSON-RPC over WS)\n// because state introspection is a local ECS operation, not a remote inspector\n// call. When the bin is invoked via PATH discovery from the base console CLI,\n// stdin forwarding is the standard plugin channel; the World reference is\n// provided by `world` in the script context injected via `vm.runInContext`.\n//\n// Decision anchors:\n// - M6 spec: console state list iterates getRegisteredTokens()\n// - M6 spec: console state get <name> calls getState and prints variant string\n// - plan-strategy: cli-state reflects the state registry (M2) + current state (M2)\n// - Console plugin pattern: argv-based dispatch with stdoutWrite/stderrWrite\n\nimport type { World } from '@forgeax/engine-ecs';\nimport type { StateToken } from './define-state';\nimport { getRegisteredTokens } from './define-state';\nimport { countScopedEntitiesByVariant } from './scoped-component';\nimport { getPreviousState, getState } from './set-next-state';\n\n// ─── Dispatch options (test-injectable) ─────────────────────────────────────\n\nexport interface DispatchOptions {\n readonly argv: readonly string[];\n readonly stdoutWrite: (line: string) => void;\n readonly stderrWrite: (line: string) => void;\n readonly world: World;\n}\n\n// ─── Help renderer ──────────────────────────────────────────────────────────\n\nfunction helpBody(): string {\n return [\n 'forgeax-engine-remote-state - inspect forgeax state machines',\n '',\n 'Usage:',\n ' forgeax-engine-remote-state <subcommand> [args]',\n '',\n 'Subcommands:',\n ' list list all registered tokens: name, current, previous, default, variants',\n ' get <name> print one token: current, previous, default, variants + per-variant scoped entity counts',\n '',\n 'Flags:',\n ' --help, -h show this help and exit 0',\n '',\n 'Examples:',\n ' forgeax-engine-remote-state list',\n ' forgeax-engine-remote-state get LevelId',\n '',\n ].join('\\n');\n}\n\nfunction subcommandHelp(subcommand: string): string {\n if (subcommand === 'list') {\n return [\n 'forgeax-engine-remote-state list - list all registered state tokens',\n '',\n 'Usage:',\n ' forgeax-engine-remote-state list',\n '',\n 'Output format: one line per token:',\n ' <tokenName>: <current> (previous: <previous>, default: <default>, variants: <v1>, <v2>, ...)',\n '',\n 'Examples:',\n ' forgeax-engine-remote-state list',\n '',\n ].join('\\n');\n }\n if (subcommand === 'get') {\n return [\n 'forgeax-engine-remote-state get - inspect one state token',\n '',\n 'Usage:',\n ' forgeax-engine-remote-state get <tokenName>',\n '',\n 'Output: current / previous / default variant, the full variants list,',\n 'and the count of ScopedTo entities currently scoped to each variant.',\n '',\n 'Examples:',\n ' forgeax-engine-remote-state get LevelId',\n ' # current: main-menu',\n ' # previous: main-menu',\n ' # default: main-menu',\n ' # variants: main-menu (0), tutorial (0), street-a (0)',\n '',\n ].join('\\n');\n }\n return helpBody();\n}\n\n// ─── Subcommand impl ────────────────────────────────────────────────────────\n\nfunction runList(world: World, stdout: (line: string) => void): void {\n const tokens = getRegisteredTokens();\n if (tokens.size === 0) {\n stdout('(no state tokens registered)');\n return;\n }\n for (const [, token] of tokens) {\n const current = getCurrentStateString(world, token);\n const previous = getPreviousStateString(world, token);\n const variantsStr = (token.variants as readonly string[]).join(', ');\n stdout(\n `${token.name}: ${current} (previous: ${previous}, default: ${token.defaultValue}, variants: ${variantsStr})`,\n );\n }\n}\n\nfunction runGet(\n world: World,\n tokenName: string,\n stdout: (line: string) => void,\n stderr: (line: string) => void,\n): number {\n const tokens = getRegisteredTokens();\n const token = tokens.get(tokenName);\n if (token === undefined) {\n stderr(\n [\n `forgeax: unknown state token \"${tokenName}\"`,\n ` expected: one of ${[...tokens.keys()].map((k) => `\"${k}\"`).join(', ') || '(none registered)'}`,\n \" hint: use 'forgeax-engine-remote-state list' to see registered tokens\",\n ].join('\\n'),\n );\n return 1;\n }\n\n const result = getState(world, token);\n if (!result.ok) {\n stderr(\n [\n `forgeax: ${result.error.code}`,\n ` expected: ${result.error.expected}`,\n ` hint: ${result.error.hint}`,\n ].join('\\n'),\n );\n return 1;\n }\n\n const previous = getPreviousStateString(world, token);\n const counts = countScopedEntitiesByVariant(world, token);\n const variantsLine = (token.variants as readonly string[])\n .map((v, i) => `${v} (${counts[i] ?? 0})`)\n .join(', ');\n\n stdout(`current: ${result.value}`);\n stdout(`previous: ${previous}`);\n stdout(`default: ${token.defaultValue}`);\n stdout(`variants: ${variantsLine}`);\n return 0;\n}\n\nfunction getCurrentStateString(world: World, token: StateToken): string {\n const result = getState(world, token);\n if (result.ok) return result.value;\n return '<error>';\n}\n\nfunction getPreviousStateString(world: World, token: StateToken): string {\n const result = getPreviousState(world, token);\n if (result.ok) return result.value;\n return '<error>';\n}\n\n// ─── Dispatch (test-injectable) ─────────────────────────────────────────────\n\nexport async function dispatch(opts: DispatchOptions): Promise<number> {\n const { argv, stdoutWrite, stderrWrite, world } = opts;\n // argv[0] = node, argv[1] = script path; subcommand starts at argv[2].\n const args = argv.slice(2);\n const subcommand = args[0];\n\n if (subcommand === undefined) {\n stderrWrite('forgeax: expected subcommand (list or get); use --help for usage');\n return 1;\n }\n\n if (subcommand === '--help' || subcommand === '-h') {\n stdoutWrite(helpBody());\n return 0;\n }\n\n if (subcommand === 'list') {\n const listArgs = args.slice(1);\n if (listArgs[0] === '--help' || listArgs[0] === '-h') {\n stdoutWrite(subcommandHelp('list'));\n return 0;\n }\n runList(world, stdoutWrite);\n return 0;\n }\n\n if (subcommand === 'get') {\n const getArgs = args.slice(1);\n if (getArgs[0] === '--help' || getArgs[0] === '-h') {\n stdoutWrite(subcommandHelp('get'));\n return 0;\n }\n const tokenName = getArgs[0];\n if (tokenName === undefined) {\n stderrWrite(\n [\n 'forgeax: state get requires a <tokenName> positional argument',\n ' expected: forgeax-engine-remote-state get <tokenName>',\n \" hint: use 'forgeax-engine-remote-state list' to see registered tokens\",\n ].join('\\n'),\n );\n return 1;\n }\n return runGet(world, tokenName, stdoutWrite, stderrWrite);\n }\n\n stderrWrite(\n [\n `forgeax: unknown subcommand \"${subcommand}\"`,\n ' expected: list or get',\n \" hint: run 'forgeax-engine-remote-state --help' for usage\",\n ].join('\\n'),\n );\n return 1;\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import type { World } from '@forgeax/engine-ecs';
2
+ import type { StateToken } from './define-state';
3
+ /**
4
+ * Create a run-condition predicate that passes when the state machine
5
+ * identified by `token` is in the given `variant`.
6
+ *
7
+ * The predicate reads the current state index from the World resource store
8
+ * (keyed by {@link stateResourceKey}) and compares it against the variant's
9
+ * index in `token.nameToIdx`.
10
+ *
11
+ * If the state resource has not been inserted yet (state not activated), the
12
+ * predicate returns `false` -- the system is skipped silently.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const GameState = defineState('GameState', ['Menu', 'Playing', 'Paused'] as const);
17
+ *
18
+ * const S = defineSystem({
19
+ * name: 'gameplay',
20
+ * queries: [{ with: [Transform] }],
21
+ * runIf: inState(GameState, 'Playing'),
22
+ * fn: (world, queryResults, commands) => { ... },
23
+ * });
24
+ * ```
25
+ *
26
+ * @param token - The state token returned by {@link defineState}.
27
+ * @param variant - The variant name to check against (must be a member of token.variants).
28
+ * @returns A predicate `(world: World) => boolean` suitable for `SystemDescriptor.runIf`.
29
+ */
30
+ export declare function inState(token: StateToken, variant: string): (world: World) => boolean;
31
+ //# sourceMappingURL=conditions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conditions.d.ts","sourceRoot":"","sources":["../src/conditions.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAgBrF"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Opaque branded type for type-level state-machine tokens.
3
+ *
4
+ * Use {@link defineState} to create a token; never construct manually.
5
+ * The {@link __forgeaxState} brand prevents plain-object assignment and
6
+ * enables TypeScript narrowing of variant literal types.
7
+ *
8
+ * @typeParam Name - The string literal name of the state machine.
9
+ * @typeParam V - The union of variant string literals derived from the const tuple.
10
+ */
11
+ export interface StateToken<Name extends string = string, V extends string = string> {
12
+ /** Brand -- prevents structural compatibility with plain objects. */
13
+ readonly __forgeaxState: typeof FORGEAX_STATE_BRAND;
14
+ /** The user-supplied state-machine name. */
15
+ readonly name: Name;
16
+ /** The ordered, read-only variants tuple. */
17
+ readonly variants: readonly V[];
18
+ /** Fast lookup: variant string -> its zero-based index in `variants`. */
19
+ readonly nameToIdx: ReadonlyMap<V, number>;
20
+ /** Convenience: `variants[0]`, the default / initial state value. */
21
+ readonly defaultValue: V;
22
+ }
23
+ /**
24
+ * Brand symbol for {@link StateToken}. Declared (not runtime-initialised) so
25
+ * the token interface carries nominal identity without a runtime allocation.
26
+ */
27
+ declare const FORGEAX_STATE_BRAND: unique symbol;
28
+ /**
29
+ * Extract the variant union from a {@link StateToken}.
30
+ *
31
+ * ```ts
32
+ * const L = defineState('LevelId', ['menu', 'game'] as const);
33
+ * type LevelVariant = StateTokenVariant<typeof L>;
34
+ * // ^? 'menu' | 'game'
35
+ * ```
36
+ */
37
+ export type StateTokenVariant<T extends StateToken> = T extends StateToken<infer _Name, infer V> ? V : never;
38
+ /**
39
+ * Extract the name literal from a {@link StateToken}.
40
+ */
41
+ export type StateTokenName<T extends StateToken> = T extends StateToken<infer Name, infer _V> ? Name : never;
42
+ /**
43
+ * Internal: get the read-only snapshot of all registered tokens.
44
+ * Exported for M2 registerStatesPlugin and M6 cli-state.
45
+ */
46
+ export declare function getRegisteredTokens(): ReadonlyMap<string, StateToken>;
47
+ /** @internal Subscribe one active World adapter to future module-level tokens. */
48
+ export declare function onStateDefined(listener: (token: StateToken) => void): () => void;
49
+ /**
50
+ * Define a typed state machine.
51
+ *
52
+ * Must be called at module level. The `as const` assertion on the variants
53
+ * array enables TypeScript to infer the exact literal tuple type, giving
54
+ * compile-time narrowing on variant parameters (e.g. `setNextState(world,
55
+ * token, 'misspell')` is a type error).
56
+ *
57
+ * @param name - Unique state-machine identifier (e.g. `'LevelId'`).
58
+ * @param variants - Readonly tuple of variant string literals. Must be non-empty.
59
+ * @returns A branded {@link StateToken} for use with `setNextState`, `getState`, etc.
60
+ * @throws StateError if `name` is already registered or `variants` is empty.
61
+ *
62
+ * ```ts
63
+ * export const LevelId = defineState('LevelId', ['main-menu', 'tutorial', 'street-a'] as const);
64
+ * // LevelId.variants -> readonly ['main-menu', 'tutorial', 'street-a']
65
+ * // LevelId.nameToIdx.get('tutorial') -> 1
66
+ * // LevelId.defaultValue -> 'main-menu'
67
+ * ```
68
+ */
69
+ export declare function defineState<Name extends string, const Variants extends readonly string[]>(name: Name, variants: Variants): StateToken<Name, Variants[number]>;
70
+ export {};
71
+ //# sourceMappingURL=define-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-state.d.ts","sourceRoot":"","sources":["../src/define-state.ts"],"names":[],"mappings":"AAeA;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM;IACjF,qEAAqE;IACrE,QAAQ,CAAC,cAAc,EAAE,OAAO,mBAAmB,CAAC;IACpD,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3C,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;CAC1B;AAED;;;GAGG;AACH,OAAO,CAAC,MAAM,mBAAmB,EAAE,OAAO,MAAM,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,UAAU,IAChD,CAAC,SAAS,UAAU,CAAC,MAAM,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,UAAU,IAC7C,CAAC,SAAS,UAAU,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC;AAW5D;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAErE;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI,CAGhF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,IAAI,SAAS,MAAM,EAAE,KAAK,CAAC,QAAQ,SAAS,SAAS,MAAM,EAAE,EACvF,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,QAAQ,GACjB,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAiDpC"}
@@ -0,0 +1,70 @@
1
+ /** {@link state-already-defined} payload: carries the conflicting name and optional first-definition site. */
2
+ export interface StateAlreadyDefinedDetail {
3
+ readonly code: 'state-already-defined';
4
+ readonly name: string;
5
+ readonly firstDefinedAt: string | undefined;
6
+ }
7
+ /** {@link state-not-registered} payload: carries the token name that has no plugin registration. */
8
+ export interface StateNotRegisteredDetail {
9
+ readonly code: 'state-not-registered';
10
+ readonly name: string;
11
+ }
12
+ /** {@link invalid-variant} payload: carries the token name, the invalid variant string, and the valid variants list. */
13
+ export interface InvalidVariantDetail {
14
+ readonly code: 'invalid-variant';
15
+ readonly name: string;
16
+ readonly got: string;
17
+ readonly valid: readonly string[];
18
+ }
19
+ /** {@link state-default-required} payload: carries the token name whose variants array was empty. */
20
+ export interface StateDefaultRequiredDetail {
21
+ readonly code: 'state-default-required';
22
+ readonly name: string;
23
+ }
24
+ interface StateErrorDetailByCode {
25
+ 'state-already-defined': StateAlreadyDefinedDetail;
26
+ 'state-not-registered': StateNotRegisteredDetail;
27
+ 'invalid-variant': InvalidVariantDetail;
28
+ 'state-default-required': StateDefaultRequiredDetail;
29
+ }
30
+ /**
31
+ * Closed {@link StateErrorCode} union -- 4 members, order-locked.
32
+ * Exhaustive `switch (err.code)` needs no default fallback.
33
+ *
34
+ * | code | trigger |
35
+ * |:--|:--|
36
+ * | `'state-already-defined'` | `defineState()` called with a name already registered |
37
+ * | `'state-not-registered'` | `setNextState()` / `getState()` called before `registerStatesPlugin()` |
38
+ * | `'invalid-variant'` | `setNextState()` called with a variant string not in the token's variants tuple |
39
+ * | `'state-default-required'` | `defineState()` called with empty variants array |
40
+ */
41
+ export type StateErrorCode = keyof StateErrorDetailByCode;
42
+ /**
43
+ * Discriminated detail union for {@link StateError}, narrowed per
44
+ * `StateError.code`. AI users obtain the concrete shape via
45
+ * `switch (err.code)` without a fallback `as` cast.
46
+ */
47
+ export type StateErrorDetail = StateErrorDetailByCode[StateErrorCode];
48
+ /**
49
+ * Structured state-machine error -- four-field surface
50
+ * (`.code` / `.expected` / `.hint` / `.detail`).
51
+ *
52
+ * AI users consume the structured triple by fields, not by parsing `.message`.
53
+ */
54
+ type StateErrorVariant<C extends StateErrorCode> = {
55
+ readonly code: C;
56
+ readonly expected: string;
57
+ readonly hint: string;
58
+ readonly detail: StateErrorDetailByCode[C];
59
+ };
60
+ export type StateError = {
61
+ [C in StateErrorCode]: StateErrorVariant<C>;
62
+ }[StateErrorCode];
63
+ /** Convenience throw wrapper for programmer errors (defineState constructor phase). */
64
+ export declare function throwStateError<C extends StateErrorCode>(code: C, expected: string, hint: string, detail: StateErrorDetailByCode[C]): never;
65
+ export declare function stateAlreadyDefined(name: string, firstDefinedAt?: string): StateError;
66
+ export declare function stateNotRegistered(name: string): StateError;
67
+ export declare function invalidVariant(name: string, got: string, valid: readonly string[]): StateError;
68
+ export declare function stateDefaultRequired(name: string): StateError;
69
+ export {};
70
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAYA,8GAA8G;AAC9G,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,oGAAoG;AACpG,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wHAAwH;AACxH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,qGAAqG;AACrG,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,sBAAsB;IAC9B,uBAAuB,EAAE,yBAAyB,CAAC;IACnD,sBAAsB,EAAE,wBAAwB,CAAC;IACjD,iBAAiB,EAAE,oBAAoB,CAAC;IACxC,wBAAwB,EAAE,0BAA0B,CAAC;CACtD;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,sBAAsB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,cAAc,CAAC,CAAC;AAEtE;;;;;GAKG;AACH,KAAK,iBAAiB,CAAC,CAAC,SAAS,cAAc,IAAI;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;KACtB,CAAC,IAAI,cAAc,GAAG,iBAAiB,CAAC,CAAC,CAAC;CAC5C,CAAC,cAAc,CAAC,CAAC;AAoBlB,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,CAAC,SAAS,cAAc,EACtD,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAChC,KAAK,CAEP;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,UAAU,CAOrF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAO3D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAQ9F;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAO7D"}
@@ -0,0 +1,11 @@
1
+ export { inState } from './conditions';
2
+ export type { StateToken, StateTokenName, StateTokenVariant, } from './define-state';
3
+ export { defineState } from './define-state';
4
+ export type { InvalidVariantDetail, StateAlreadyDefinedDetail, StateDefaultRequiredDetail, StateError, StateErrorCode, StateErrorDetail, StateNotRegisteredDetail, } from './errors';
5
+ export type { StateCallback, UnsubscribeHandle, } from './on-enter-on-exit';
6
+ export { addOnEnter, addOnExit, OnEnter, OnExit, } from './on-enter-on-exit';
7
+ export { statePlugin } from './plugin-factory';
8
+ export { registerStatesPlugin, StateSet } from './register-plugin';
9
+ export { despawnOnEnter, despawnOnExit, } from './scoped-component';
10
+ export { getPreviousState, getState, setNextState, setNextStateForce, } from './set-next-state';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EACV,UAAU,EACV,cAAc,EACd,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EACV,oBAAoB,EACpB,yBAAyB,EACzB,0BAA0B,EAC1B,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,aAAa,EACb,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,UAAU,EACV,SAAS,EACT,OAAO,EACP,MAAM,GACP,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EACL,cAAc,EACd,aAAa,GACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,QAAQ,EACR,YAAY,EACZ,iBAAiB,GAClB,MAAM,kBAAkB,CAAC"}