@spine-event-engine/proto-tools 2.0.0-snapshot.10

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 (89) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +103 -0
  3. package/REFERENCE.md +90 -0
  4. package/bin/spine-proto.mjs +2 -0
  5. package/dist/src/cli/spine-proto-bootstrap.d.ts +4 -0
  6. package/dist/src/cli/spine-proto-bootstrap.d.ts.map +1 -0
  7. package/dist/src/cli/spine-proto-bootstrap.js +52 -0
  8. package/dist/src/cli/spine-proto-bootstrap.js.map +1 -0
  9. package/dist/src/cli/spine-proto.d.ts +3 -0
  10. package/dist/src/cli/spine-proto.d.ts.map +1 -0
  11. package/dist/src/cli/spine-proto.js +32 -0
  12. package/dist/src/cli/spine-proto.js.map +1 -0
  13. package/dist/src/generation/authored-interface-provider.d.ts +28 -0
  14. package/dist/src/generation/authored-interface-provider.d.ts.map +1 -0
  15. package/dist/src/generation/authored-interface-provider.js +244 -0
  16. package/dist/src/generation/authored-interface-provider.js.map +1 -0
  17. package/dist/src/generation/build-time-handler-analyzer.d.ts +195 -0
  18. package/dist/src/generation/build-time-handler-analyzer.d.ts.map +1 -0
  19. package/dist/src/generation/build-time-handler-analyzer.js +1308 -0
  20. package/dist/src/generation/build-time-handler-analyzer.js.map +1 -0
  21. package/dist/src/generation/generated-registry-writer.d.ts +58 -0
  22. package/dist/src/generation/generated-registry-writer.d.ts.map +1 -0
  23. package/dist/src/generation/generated-registry-writer.js +465 -0
  24. package/dist/src/generation/generated-registry-writer.js.map +1 -0
  25. package/dist/src/generation/generated-source-policy.d.ts +21 -0
  26. package/dist/src/generation/generated-source-policy.d.ts.map +1 -0
  27. package/dist/src/generation/generated-source-policy.js +126 -0
  28. package/dist/src/generation/generated-source-policy.js.map +1 -0
  29. package/dist/src/generation/generation-reuse.mjs +106 -0
  30. package/dist/src/generation/generator.d.ts +195 -0
  31. package/dist/src/generation/generator.d.ts.map +1 -0
  32. package/dist/src/generation/generator.js +566 -0
  33. package/dist/src/generation/generator.js.map +1 -0
  34. package/dist/src/generation/handler-codegen.d.ts +48 -0
  35. package/dist/src/generation/handler-codegen.d.ts.map +1 -0
  36. package/dist/src/generation/handler-codegen.js +214 -0
  37. package/dist/src/generation/handler-codegen.js.map +1 -0
  38. package/dist/src/generation/handler-generator.d.ts +7 -0
  39. package/dist/src/generation/handler-generator.d.ts.map +1 -0
  40. package/dist/src/generation/handler-generator.js +55 -0
  41. package/dist/src/generation/handler-generator.js.map +1 -0
  42. package/dist/src/generation/interface-generator.d.ts +21 -0
  43. package/dist/src/generation/interface-generator.d.ts.map +1 -0
  44. package/dist/src/generation/interface-generator.js +277 -0
  45. package/dist/src/generation/interface-generator.js.map +1 -0
  46. package/dist/src/generation/interface-provider.d.ts +30 -0
  47. package/dist/src/generation/interface-provider.d.ts.map +1 -0
  48. package/dist/src/generation/interface-provider.js +15 -0
  49. package/dist/src/generation/interface-provider.js.map +1 -0
  50. package/dist/src/generation/rejection-generator.d.ts +9 -0
  51. package/dist/src/generation/rejection-generator.d.ts.map +1 -0
  52. package/dist/src/generation/rejection-generator.js +62 -0
  53. package/dist/src/generation/rejection-generator.js.map +1 -0
  54. package/dist/src/generation/source-view.d.ts +103 -0
  55. package/dist/src/generation/source-view.d.ts.map +1 -0
  56. package/dist/src/generation/source-view.js +298 -0
  57. package/dist/src/generation/source-view.js.map +1 -0
  58. package/dist/src/index.d.ts +139 -0
  59. package/dist/src/index.d.ts.map +1 -0
  60. package/dist/src/index.js +546 -0
  61. package/dist/src/index.js.map +1 -0
  62. package/dist/src/io/atomic-manifest.d.ts +32 -0
  63. package/dist/src/io/atomic-manifest.d.ts.map +1 -0
  64. package/dist/src/io/atomic-manifest.js +56 -0
  65. package/dist/src/io/atomic-manifest.js.map +1 -0
  66. package/dist/src/io/manifest-reader.d.ts +8 -0
  67. package/dist/src/io/manifest-reader.d.ts.map +1 -0
  68. package/dist/src/io/manifest-reader.js +19 -0
  69. package/dist/src/io/manifest-reader.js.map +1 -0
  70. package/dist/src/model/model-graph.d.ts +68 -0
  71. package/dist/src/model/model-graph.d.ts.map +1 -0
  72. package/dist/src/model/model-graph.js +220 -0
  73. package/dist/src/model/model-graph.js.map +1 -0
  74. package/dist/src/model/npm-package-name.d.ts +7 -0
  75. package/dist/src/model/npm-package-name.d.ts.map +1 -0
  76. package/dist/src/model/npm-package-name.js +33 -0
  77. package/dist/src/model/npm-package-name.js.map +1 -0
  78. package/dist/src/model/registry-dependency.d.ts +10 -0
  79. package/dist/src/model/registry-dependency.d.ts.map +1 -0
  80. package/dist/src/model/registry-dependency.js +66 -0
  81. package/dist/src/model/registry-dependency.js.map +1 -0
  82. package/dist/src/testing/index.d.ts +8 -0
  83. package/dist/src/testing/index.d.ts.map +1 -0
  84. package/dist/src/testing/index.js +20 -0
  85. package/dist/src/testing/index.js.map +1 -0
  86. package/dist/tsconfig.tsbuildinfo +1 -0
  87. package/package.json +54 -0
  88. package/scripts/clean-dist.mjs +7 -0
  89. package/scripts/copy-runtime-modules.mjs +23 -0
@@ -0,0 +1,1308 @@
1
+ /*
2
+ * Copyright 2026, CodeMatters. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
+ * in compliance with the License. You may obtain a copy of the License at
6
+ *
7
+ * https://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
10
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ * or implied. See the License for the specific language governing permissions and limitations under
12
+ * the License.
13
+ */
14
+ import { Buffer } from "node:buffer";
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ import { createRequire } from "node:module";
17
+ import { dirname, join, resolve } from "node:path";
18
+ import ts from "typescript";
19
+ /**
20
+ * Provides build-time analysis for bare Spine handler decorators.
21
+ */
22
+ export const BuildHandlerAnalyzer = Object.freeze({
23
+ // prettier-ignore
24
+ /**
25
+ * Inspects source files and returns receiver handler records with deterministic diagnostics.
26
+ *
27
+ * @param program TypeScript program that owns the source files and diagnostics.
28
+ * @param sourceFiles Application source files to inspect; defaults to program files.
29
+ * @returns Receiver handler records and deterministic diagnostics.
30
+ */
31
+ analyze(program, sourceFiles = HandlerSources.appSourceFiles(program)) {
32
+ PackageDependencies.load(program);
33
+ const receivers = [];
34
+ const diagnostics = [];
35
+ for (const source of sourceFiles) {
36
+ const syntaxDiagnostics = program.getSyntacticDiagnostics(source);
37
+ if (syntaxDiagnostics.length > 0) {
38
+ diagnostics.push(...syntaxDiagnostics.map((diagnostic) => HandlerTypes.syntaxDiagnostic(source, diagnostic)));
39
+ continue;
40
+ }
41
+ const scope = {
42
+ program,
43
+ source,
44
+ imports: HandlerSources.buildImportState(source, program),
45
+ diagnostics,
46
+ };
47
+ const sourceReceivers = HandlerSources.analyzeSource(scope);
48
+ receivers.push(...sourceReceivers);
49
+ }
50
+ return { receivers, diagnostics };
51
+ },
52
+ });
53
+ const handlerDecorators = new Set(["Assign", "Command", "React", "Subscribe"]);
54
+ const entityBaseNames = new Set(["Aggregate", "Projection", "ProcessManager"]);
55
+ const standaloneBaseNames = new Set([
56
+ "AbstractAssignee",
57
+ "AbstractCommander",
58
+ "AbstractEventReactor",
59
+ "AbstractEventSubscriber",
60
+ ]);
61
+ const maxAliasDepth = 50;
62
+ // `spine.options.entity` in the frozen `spine/options.proto` contract.
63
+ const entityOptionFieldNumber = 73903;
64
+ let packageDependencies;
65
+ const HandlerSources = Object.freeze({
66
+ appSourceFiles(program) {
67
+ return program.getSourceFiles().filter((source) => !source.isDeclarationFile);
68
+ },
69
+ analyzeSource(scope) {
70
+ const receivers = [];
71
+ for (const statement of scope.source.statements) {
72
+ if (!ts.isClassDeclaration(statement) ||
73
+ (statement.name === undefined &&
74
+ !HandlerTypes.hasModifier(statement, ts.SyntaxKind.DefaultKeyword))) {
75
+ continue;
76
+ }
77
+ const receiver = HandlerSources.analyzeClass(statement, scope);
78
+ if (receiver !== undefined) {
79
+ receivers.push(receiver);
80
+ }
81
+ }
82
+ return receivers;
83
+ },
84
+ analyzeClass(node, scope) {
85
+ const className = node.name?.text ?? "DefaultReceiver";
86
+ const exportIssue = HandlerSources.receiverExportIssue(node, scope.source);
87
+ if (HandlerSources.hasDecoratedMethod(node, scope.imports) && exportIssue !== undefined) {
88
+ HandlerTypes.pushDiagnostic(scope, exportIssue.code, node.name ?? node, exportIssue.message, className);
89
+ return undefined;
90
+ }
91
+ const lineage = HandlerSources.receiverLineage(node, scope);
92
+ const entityBase = lineage?.base;
93
+ const stateSchema = lineage?.stateSchema;
94
+ const handlers = [];
95
+ for (const member of node.members) {
96
+ if (!ts.isMethodDeclaration(member)) {
97
+ continue;
98
+ }
99
+ const handler = HandlerSources.analyzeMethod(member, className, entityBase, stateSchema, lineage?.receiverKind, scope);
100
+ if (handler !== undefined) {
101
+ handlers.push(handler);
102
+ }
103
+ }
104
+ if (lineage === undefined || handlers.length === 0) {
105
+ return undefined;
106
+ }
107
+ return lineage.receiverKind === "entity"
108
+ ? stateSchema === undefined
109
+ ? undefined
110
+ : {
111
+ receiverKind: "entity",
112
+ className,
113
+ ...(HandlerTypes.hasModifier(node, ts.SyntaxKind.DefaultKeyword)
114
+ ? { defaultExport: true }
115
+ : {}),
116
+ sourceFile: scope.source.fileName,
117
+ stateSchema,
118
+ handlers,
119
+ }
120
+ : {
121
+ receiverKind: "standalone",
122
+ className,
123
+ ...(HandlerTypes.hasModifier(node, ts.SyntaxKind.DefaultKeyword)
124
+ ? { defaultExport: true }
125
+ : {}),
126
+ sourceFile: scope.source.fileName,
127
+ handlers,
128
+ };
129
+ },
130
+ analyzeMethod(node, className, entityBase, stateSchema, receiverKind, scope) {
131
+ const decorators = HandlerSources.methodDecorators(node, scope.imports);
132
+ const apply = decorators.find((decorator) => decorator.name === "Apply");
133
+ const handler = decorators.find(HandlerSources.isHandlerUse);
134
+ const whereUses = decorators.filter((decorator) => decorator.name === "Where");
135
+ if (apply !== undefined) {
136
+ HandlerTypes.pushDiagnostic(scope, "APPLY_DECORATOR", apply.node, "Generated registries do not support @Apply.", className, HandlerTypes.methodName(node));
137
+ }
138
+ if (handler === undefined) {
139
+ if (whereUses.length > 0) {
140
+ HandlerTypes.pushDiagnostic(scope, "INVALID_WHERE", whereUses[0]?.node ?? node, "@Where requires an Event-consuming @Subscribe, @React, or @Command handler.", className, HandlerTypes.methodName(node));
141
+ }
142
+ return undefined;
143
+ }
144
+ const method = HandlerTypes.methodName(node);
145
+ if ((entityBase === "Aggregate" || entityBase === "Projection") && handler.name === "Command") {
146
+ HandlerTypes.pushDiagnostic(scope, "UNSUPPORTED_COMMAND_HANDLER", handler.node, "Only Process Managers support @Command handlers.", className, method);
147
+ return undefined;
148
+ }
149
+ if (receiverKind === "standalone" &&
150
+ !HandlerSources.allowsStandaloneDecorator(entityBase, handler.name)) {
151
+ HandlerTypes.pushDiagnostic(scope, "UNSUPPORTED_COMMAND_HANDLER", handler.node, `@${handler.name} is not legal for standalone ${entityBase ?? "receiver"}.`, className, method);
152
+ return undefined;
153
+ }
154
+ const invalid = HandlerSources.validateHandlerNode(node, handler.name, receiverKind === "entity" ? stateSchema : undefined, receiverKind !== "standalone", scope, className, method);
155
+ if (handler.hasArguments) {
156
+ HandlerTypes.pushDiagnostic(scope, "SCHEMA_BEARING_DECORATOR", handler.node, `@${handler.name}(...) is not supported in analyzed app source.`, className, method);
157
+ return undefined;
158
+ }
159
+ if (invalid) {
160
+ return undefined;
161
+ }
162
+ const origin = HandlerSources.externalOrigin(node.parameters, scope, className, method);
163
+ if (origin === undefined)
164
+ return undefined;
165
+ const signal = HandlerSources.schemaUseFromType(origin.type, scope.imports);
166
+ const emittedSchemas = HandlerSources.emittedSchemaUses(node.type === undefined ? undefined : HandlerSources.unwrapOuterPromise(node.type, scope), handler.name, scope.imports)?.map((schema) => schema.reference);
167
+ if (signal === undefined || emittedSchemas === undefined || method === undefined) {
168
+ return undefined;
169
+ }
170
+ if (!HandlerSources.validContextParameter(node.parameters, signal.kind, scope, className, method)) {
171
+ return undefined;
172
+ }
173
+ const where = HandlerSources.whereDeclaration(whereUses, handler, signal.kind, scope, className, method);
174
+ if (whereUses.length > 0 && where === undefined) {
175
+ return undefined;
176
+ }
177
+ return {
178
+ kind: HandlerSources.handlerKind(handler.name, signal.kind),
179
+ methodName: method,
180
+ signalSchema: signal.reference,
181
+ emittedSchemas,
182
+ parameterCount: node.parameters.length,
183
+ origin: origin.value,
184
+ ...(where === undefined ? {} : { where }),
185
+ };
186
+ },
187
+ whereDeclaration(uses, handler, signalKind, scope, className, methodName) {
188
+ if (uses.length === 0)
189
+ return undefined;
190
+ const use = uses[0];
191
+ if (uses.length !== 1 ||
192
+ (handler.name !== "Subscribe" && handler.name !== "React" && handler.name !== "Command") ||
193
+ (signalKind !== "event" && signalKind !== "rejection") ||
194
+ use === undefined) {
195
+ HandlerTypes.pushDiagnostic(scope, "INVALID_WHERE", use?.node ?? handler.node, "@Where is allowed once on an Event-consuming @Subscribe, @React, or @Command handler.", className, methodName);
196
+ return undefined;
197
+ }
198
+ const expression = use.node.expression;
199
+ if (!ts.isCallExpression(expression) || expression.arguments.length !== 1) {
200
+ HandlerTypes.pushDiagnostic(scope, "INVALID_WHERE", use.node, "@Where requires one object literal.", className, methodName);
201
+ return undefined;
202
+ }
203
+ const options = expression.arguments[0];
204
+ if (options === undefined || !ts.isObjectLiteralExpression(options)) {
205
+ HandlerTypes.pushDiagnostic(scope, "INVALID_WHERE", expression, "@Where options must be an object literal.", className, methodName);
206
+ return undefined;
207
+ }
208
+ const values = new Map();
209
+ for (const property of options.properties) {
210
+ if (!ts.isPropertyAssignment(property) ||
211
+ (property.name.kind !== ts.SyntaxKind.Identifier &&
212
+ !ts.isStringLiteralLike(property.name)) ||
213
+ !ts.isStringLiteralLike(property.initializer)) {
214
+ HandlerTypes.pushDiagnostic(scope, "INVALID_WHERE", property, "@Where accepts only eventField and equals string literals.", className, methodName);
215
+ return undefined;
216
+ }
217
+ const name = property.name.text;
218
+ if ((name !== "eventField" && name !== "equals") || values.has(name)) {
219
+ HandlerTypes.pushDiagnostic(scope, "INVALID_WHERE", property, "@Where accepts each of eventField and equals exactly once.", className, methodName);
220
+ return undefined;
221
+ }
222
+ values.set(name, property.initializer.text);
223
+ }
224
+ const eventField = values.get("eventField");
225
+ const equals = values.get("equals");
226
+ if (eventField === undefined || eventField.trim().length === 0 || equals === undefined) {
227
+ HandlerTypes.pushDiagnostic(scope, "INVALID_WHERE", options, "@Where requires non-empty eventField and string equals.", className, methodName);
228
+ return undefined;
229
+ }
230
+ return Object.freeze({ eventField, equals });
231
+ },
232
+ hasDecoratedMethod(node, imports) {
233
+ return node.members.some((member) => ts.isMethodDeclaration(member) &&
234
+ HandlerSources.methodDecorators(member, imports).length > 0);
235
+ },
236
+ receiverExportIssue(node, source) {
237
+ if (HandlerTypes.hasModifier(node, ts.SyntaxKind.DefaultKeyword))
238
+ return undefined;
239
+ if (HandlerTypes.hasModifier(node, ts.SyntaxKind.ExportKeyword)) {
240
+ return undefined;
241
+ }
242
+ if (node.name !== undefined && HandlerSources.hasNamedClassExport(node.name.text, source)) {
243
+ return undefined;
244
+ }
245
+ return {
246
+ code: "NON_EXPORTED_RECEIVER_CLASS",
247
+ message: "Decorated receiver classes must be exported for generated registry imports.",
248
+ };
249
+ },
250
+ hasNamedClassExport(className, source) {
251
+ return source.statements.some((statement) => ts.isExportDeclaration(statement) &&
252
+ statement.moduleSpecifier === undefined &&
253
+ statement.exportClause !== undefined &&
254
+ HandlerSources.namedExportIncludes(statement.exportClause, className));
255
+ },
256
+ namedExportIncludes(clause, className) {
257
+ return (ts.isNamedExports(clause) &&
258
+ clause.elements.some((element) => element.name.text === className &&
259
+ (element.propertyName === undefined || element.propertyName.text === className)));
260
+ },
261
+ validateHandlerNode(node, decorator, stateSchema, requiresEntityState, scope, className, method) {
262
+ return [
263
+ HandlerSources.validateEntityState(node, stateSchema, requiresEntityState, scope, className, method),
264
+ HandlerSources.validateName(node, scope, className),
265
+ HandlerSources.validateVisibility(node, scope, className, method),
266
+ HandlerSources.validateParameters(node, decorator, scope, className, method),
267
+ HandlerSources.validateReturn(node, decorator, scope, className, method),
268
+ ].some(Boolean);
269
+ },
270
+ validateEntityState(node, stateSchema, requiresEntityState, scope, className, method) {
271
+ if (!requiresEntityState || stateSchema !== undefined) {
272
+ return false;
273
+ }
274
+ HandlerTypes.pushDiagnostic(scope, "MISSING_ENTITY_STATE_SCHEMA", node, "Decorated handlers must be declared on an entity class with an inferred state schema.", className, method);
275
+ return true;
276
+ },
277
+ validateName(node, scope, className) {
278
+ if (HandlerTypes.methodName(node) !== undefined) {
279
+ return false;
280
+ }
281
+ HandlerTypes.pushDiagnostic(scope, "INVALID_HANDLER_NAME", node.name, "Decorated handlers must use a string method name.", className);
282
+ return true;
283
+ },
284
+ validateVisibility(node, scope, className, method) {
285
+ if (!HandlerTypes.hasModifier(node, ts.SyntaxKind.StaticKeyword) &&
286
+ !HandlerTypes.hasModifier(node, ts.SyntaxKind.PrivateKeyword) &&
287
+ !HandlerTypes.hasModifier(node, ts.SyntaxKind.ProtectedKeyword)) {
288
+ return false;
289
+ }
290
+ HandlerTypes.pushDiagnostic(scope, "INVALID_HANDLER_VISIBILITY", node.name, "Decorated handlers must be public instance methods.", className, method);
291
+ return true;
292
+ },
293
+ validateParameters(node, decorator, scope, className, method) {
294
+ if (node.parameters.length !== 1 && node.parameters.length !== 2) {
295
+ HandlerTypes.pushDiagnostic(scope, "INVALID_PARAMETER_COUNT", node, "Decorated handlers must declare one or two public parameters.", className, method);
296
+ return true;
297
+ }
298
+ if (node.parameters[0]?.type === undefined) {
299
+ HandlerTypes.pushDiagnostic(scope, "MISSING_SIGNAL_TYPE", node, `@${decorator} handlers require an explicit first parameter type.`, className, method);
300
+ return true;
301
+ }
302
+ const origin = HandlerSources.externalOrigin(node.parameters, scope, className, method);
303
+ if (origin === undefined)
304
+ return true;
305
+ const signal = HandlerSources.schemaUseFromType(origin.type, scope.imports);
306
+ if (signal !== undefined && HandlerSources.acceptsSignalKind(decorator, signal.kind)) {
307
+ if (origin.value === "external" && signal.kind === "command") {
308
+ HandlerTypes.pushDiagnostic(scope, "EXTERNAL_COMMAND_RECEIVER", node.parameters[0].type, "Command receivers cannot declare External<Command>.", className, method);
309
+ return true;
310
+ }
311
+ return false;
312
+ }
313
+ HandlerTypes.pushDiagnostic(scope, "INVALID_SIGNAL_TYPE", node.parameters[0].type, `@${decorator} first parameter must be ${HandlerSources.signalMessage(decorator)}.`, className, method);
314
+ return true;
315
+ },
316
+ validateReturn(node, decorator, scope, className, method) {
317
+ if (decorator === "Subscribe") {
318
+ return HandlerSources.validateSubscribeReturn(node, scope, className, method);
319
+ }
320
+ if (node.type === undefined) {
321
+ HandlerTypes.pushDiagnostic(scope, "MISSING_RETURN_TYPE", node, `@${decorator} handlers require an explicit return type.`, className, method);
322
+ return true;
323
+ }
324
+ const returnType = HandlerSources.unwrapOuterPromise(node.type, scope);
325
+ const envelope = HandlerSources.frameworkEnvelope(returnType, scope.imports);
326
+ if (envelope !== undefined) {
327
+ HandlerTypes.pushDiagnostic(scope, "FRAMEWORK_ENVELOPE_RETURN", node.type, `Handler return type must not be framework ${envelope}.`, className, method);
328
+ return true;
329
+ }
330
+ return HandlerSources.validateEmittedReturn(node, decorator, scope, className, method, returnType);
331
+ },
332
+ validateSubscribeReturn(node, scope, className, method) {
333
+ if (node.type !== undefined &&
334
+ HandlerSources.isExplicitVoidType(HandlerSources.unwrapOuterPromise(node.type, scope))) {
335
+ return false;
336
+ }
337
+ HandlerTypes.pushDiagnostic(scope, "INVALID_SUBSCRIBE_RETURN", node, "@Subscribe handlers must return explicit void.", className, method);
338
+ return true;
339
+ },
340
+ validateEmittedReturn(node, decorator, scope, className, method, returnType = node.type) {
341
+ const schemas = HandlerSources.emittedSchemaUses(returnType, decorator, scope.imports);
342
+ if (schemas === undefined) {
343
+ HandlerTypes.pushDiagnostic(scope, "UNSUPPORTED_RETURN_TYPE", returnType ?? node, `@${decorator} return type must resolve to generated schema references.`, className, method);
344
+ return true;
345
+ }
346
+ const expected = HandlerSources.emittedSignalKind(decorator);
347
+ if (expected !== undefined && schemas.some((schema) => schema.kind !== expected)) {
348
+ HandlerTypes.pushDiagnostic(scope, "INVALID_EMITTED_SCHEMA", returnType ?? node, `@${decorator} return type must emit generated ${expected} schemas.`, className, method);
349
+ return true;
350
+ }
351
+ if ((decorator === "Assign" || decorator === "Command") && schemas.length === 0) {
352
+ HandlerTypes.pushDiagnostic(scope, "MISSING_EMITTED_SCHEMAS", returnType ?? node, `@${decorator} handlers must emit at least one schema.`, className, method);
353
+ return true;
354
+ }
355
+ if (decorator === "React" &&
356
+ schemas.length === 0 &&
357
+ !HandlerSources.isExplicitVoidType(returnType)) {
358
+ HandlerTypes.pushDiagnostic(scope, "MISSING_EMITTED_SCHEMAS", returnType ?? node, "@React handlers must emit at least one schema unless they return explicit void.", className, method);
359
+ return true;
360
+ }
361
+ return false;
362
+ },
363
+ methodDecorators(node, imports) {
364
+ return (ts.getDecorators(node) ?? []).flatMap((decorator) => {
365
+ const expression = ts.isCallExpression(decorator.expression)
366
+ ? decorator.expression.expression
367
+ : decorator.expression;
368
+ const name = HandlerSources.serverDecoratorName(expression, imports);
369
+ return name === undefined
370
+ ? []
371
+ : [{ hasArguments: ts.isCallExpression(decorator.expression), name, node: decorator }];
372
+ });
373
+ },
374
+ serverDecoratorName(expression, imports) {
375
+ if (ts.isIdentifier(expression)) {
376
+ return HandlerTypes.serverDecorator(imports.serverSymbols.get(expression.text));
377
+ }
378
+ if (ts.isPropertyAccessExpression(expression)) {
379
+ const namespace = HandlerTypes.expressionName(expression.expression);
380
+ if (namespace !== undefined && imports.serverNamespaces.has(namespace)) {
381
+ return HandlerTypes.serverDecorator(expression.name.text);
382
+ }
383
+ }
384
+ return undefined;
385
+ },
386
+ isHandlerUse(decorator) {
387
+ return handlerDecorators.has(decorator.name);
388
+ },
389
+ allowsStandaloneDecorator(base, decorator) {
390
+ return ((base === "AbstractAssignee" && decorator === "Assign") ||
391
+ (base === "AbstractCommander" && decorator === "Command") ||
392
+ (base === "AbstractEventReactor" && decorator === "React") ||
393
+ (base === "AbstractEventSubscriber" && decorator === "Subscribe"));
394
+ },
395
+ receiverLineage(node, scope, seen = new Set()) {
396
+ if (seen.has(node))
397
+ return undefined;
398
+ const nextSeen = new Set(seen);
399
+ nextSeen.add(node);
400
+ for (const clause of node.heritageClauses ?? []) {
401
+ for (const type of clause.types) {
402
+ const direct = HandlerSources.directReceiverLineage(type, scope);
403
+ if (direct !== undefined)
404
+ return direct;
405
+ const inherited = HandlerSources.inheritedReceiverLineage(type.expression, scope, nextSeen);
406
+ if (inherited !== undefined)
407
+ return inherited;
408
+ }
409
+ }
410
+ return undefined;
411
+ },
412
+ directReceiverLineage(type, scope) {
413
+ const base = HandlerSources.directEntityBaseName(type.expression, scope.imports);
414
+ if (base !== undefined) {
415
+ const stateType = type.typeArguments?.[1];
416
+ const stateSchema = stateType === undefined
417
+ ? undefined
418
+ : HandlerSources.schemaFromTypeQuery(stateType, scope.imports, HandlerSources.newTypeWalk());
419
+ return { receiverKind: "entity", base, stateSchema };
420
+ }
421
+ const standalone = HandlerSources.directStandaloneBaseName(type.expression, scope.imports);
422
+ if (standalone !== undefined)
423
+ return { receiverKind: "standalone", base: standalone, stateSchema: undefined };
424
+ },
425
+ inheritedReceiverLineage(expression, scope, seen) {
426
+ const parent = HandlerSources.classDeclarationFor(expression, scope);
427
+ if (parent === undefined)
428
+ return undefined;
429
+ const source = parent.getSourceFile();
430
+ return HandlerSources.receiverLineage(parent, { ...scope, source, imports: HandlerSources.buildImportState(source, scope.program) }, seen);
431
+ },
432
+ directEntityBaseName(expression, imports) {
433
+ if (ts.isIdentifier(expression)) {
434
+ const base = imports.serverSymbols.get(expression.text);
435
+ return base !== undefined && entityBaseNames.has(base) ? base : undefined;
436
+ }
437
+ if (ts.isPropertyAccessExpression(expression)) {
438
+ const namespace = HandlerTypes.expressionName(expression.expression);
439
+ return namespace !== undefined &&
440
+ imports.serverNamespaces.has(namespace) &&
441
+ entityBaseNames.has(expression.name.text)
442
+ ? expression.name.text
443
+ : undefined;
444
+ }
445
+ return undefined;
446
+ },
447
+ directStandaloneBaseName(expression, imports) {
448
+ if (ts.isIdentifier(expression)) {
449
+ const base = imports.serverSymbols.get(expression.text);
450
+ return base !== undefined && standaloneBaseNames.has(base) ? base : undefined;
451
+ }
452
+ if (ts.isPropertyAccessExpression(expression)) {
453
+ const namespace = HandlerTypes.expressionName(expression.expression);
454
+ return namespace !== undefined &&
455
+ imports.serverNamespaces.has(namespace) &&
456
+ standaloneBaseNames.has(expression.name.text)
457
+ ? expression.name.text
458
+ : undefined;
459
+ }
460
+ return undefined;
461
+ },
462
+ classDeclarationFor(expression, scope) {
463
+ const checker = scope.program.getTypeChecker();
464
+ const location = ts.isPropertyAccessExpression(expression) ? expression.name : expression;
465
+ let symbol = checker.getSymbolAtLocation(location);
466
+ if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
467
+ symbol = checker.getAliasedSymbol(symbol);
468
+ }
469
+ const declaration = symbol?.declarations?.find(ts.isClassDeclaration);
470
+ if (declaration !== undefined)
471
+ return declaration;
472
+ const declarations = checker.getTypeAtLocation(expression).getSymbol()?.declarations;
473
+ return declarations === undefined ? undefined : declarations.find(ts.isClassDeclaration);
474
+ },
475
+ isEntityBase(expression, imports) {
476
+ return HandlerSources.directEntityBaseName(expression, imports) !== undefined;
477
+ },
478
+ schemaFromTypeQuery(typeNode, imports, walk) {
479
+ if (!HandlerSources.consumeTypeWalk(walk)) {
480
+ return undefined;
481
+ }
482
+ if (ts.isParenthesizedTypeNode(typeNode)) {
483
+ return HandlerSources.schemaFromTypeQuery(typeNode.type, imports, walk);
484
+ }
485
+ if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
486
+ const alias = imports.localTypeAliases.get(typeNode.typeName.text);
487
+ return alias === undefined
488
+ ? undefined
489
+ : HandlerSources.resolveAlias(typeNode.typeName.text, alias, walk, (resolved) => HandlerSources.schemaFromTypeQuery(resolved, imports, walk));
490
+ }
491
+ if (!ts.isTypeQueryNode(typeNode)) {
492
+ return undefined;
493
+ }
494
+ return HandlerSources.schemaFromEntityName(typeNode.exprName, imports);
495
+ },
496
+ emittedSchemaUses(typeNode, decorator, imports) {
497
+ if (decorator === "Subscribe") {
498
+ return typeNode?.kind === ts.SyntaxKind.VoidKeyword ? [] : undefined;
499
+ }
500
+ if (typeNode?.kind === ts.SyntaxKind.VoidKeyword) {
501
+ return [];
502
+ }
503
+ if (typeNode === undefined) {
504
+ return undefined;
505
+ }
506
+ return HandlerSources.schemaListFromType(typeNode, imports, HandlerSources.newTypeWalk());
507
+ },
508
+ schemaListFromType(typeNode, imports, walk) {
509
+ if (!HandlerSources.consumeTypeWalk(walk)) {
510
+ return undefined;
511
+ }
512
+ const unwrapped = HandlerSources.unwrapReadonly(typeNode);
513
+ if (ts.isArrayTypeNode(unwrapped)) {
514
+ const item = HandlerSources.schemaUseFromType(unwrapped.elementType, imports, walk);
515
+ return item === undefined ? undefined : [item];
516
+ }
517
+ if (ts.isTupleTypeNode(unwrapped)) {
518
+ return HandlerSources.schemaListFromTuple(unwrapped, imports, walk);
519
+ }
520
+ if (ts.isTypeReferenceNode(unwrapped) && HandlerSources.isArrayReferenceType(unwrapped)) {
521
+ const item = HandlerSources.schemaUseFromType(unwrapped.typeArguments?.[0], imports, walk);
522
+ return item === undefined ? undefined : [item];
523
+ }
524
+ const schema = HandlerSources.schemaUseFromType(unwrapped, imports, walk);
525
+ return schema === undefined ? undefined : [schema];
526
+ },
527
+ schemaListFromTuple(typeNode, imports, walk) {
528
+ const schemas = [];
529
+ for (const element of typeNode.elements) {
530
+ const schema = HandlerSources.schemaFromTupleElement(element, imports, walk);
531
+ if (schema === undefined) {
532
+ return undefined;
533
+ }
534
+ schemas.push(schema);
535
+ }
536
+ return schemas;
537
+ },
538
+ schemaFromTupleElement(typeNode, imports, walk) {
539
+ if (ts.isNamedTupleMember(typeNode)) {
540
+ return typeNode.questionToken === undefined && typeNode.dotDotDotToken === undefined
541
+ ? HandlerSources.schemaUseFromType(typeNode.type, imports, walk)
542
+ : undefined;
543
+ }
544
+ if (ts.isRestTypeNode(typeNode) || ts.isOptionalTypeNode(typeNode)) {
545
+ return undefined;
546
+ }
547
+ return HandlerSources.schemaUseFromType(typeNode, imports, walk);
548
+ },
549
+ schemaUseFromType(typeNode, imports, walk = HandlerSources.newTypeWalk()) {
550
+ if (typeNode === undefined) {
551
+ return undefined;
552
+ }
553
+ if (!HandlerSources.consumeTypeWalk(walk)) {
554
+ return undefined;
555
+ }
556
+ const unwrapped = HandlerSources.unwrapReadonly(typeNode);
557
+ if (!ts.isTypeReferenceNode(unwrapped)) {
558
+ return undefined;
559
+ }
560
+ if (ts.isIdentifier(unwrapped.typeName)) {
561
+ const alias = imports.localTypeAliases.get(unwrapped.typeName.text);
562
+ if (alias !== undefined) {
563
+ return HandlerSources.resolveAlias(unwrapped.typeName.text, alias, walk, (resolved) => HandlerSources.schemaUseFromType(resolved, imports, walk));
564
+ }
565
+ return HandlerSources.schemaUseFromSymbol(unwrapped.typeName.text, imports);
566
+ }
567
+ if (ts.isQualifiedName(unwrapped.typeName)) {
568
+ return HandlerSources.schemaUseFromName(unwrapped.typeName, imports);
569
+ }
570
+ return undefined;
571
+ },
572
+ schemaUseFromSymbol(name, imports) {
573
+ const symbol = imports.generatedSymbols.get(name);
574
+ if (symbol?.schemaExportName === undefined) {
575
+ return undefined;
576
+ }
577
+ return {
578
+ kind: symbol.kind,
579
+ reference: { moduleSpecifier: symbol.moduleSpecifier, exportName: symbol.schemaExportName },
580
+ };
581
+ },
582
+ schemaUseFromName(name, imports) {
583
+ if (!ts.isIdentifier(name.left)) {
584
+ return undefined;
585
+ }
586
+ const namespace = imports.generatedNamespaces.get(name.left.text);
587
+ const schemaName = `${name.right.text}Schema`;
588
+ return namespace === undefined ||
589
+ !HandlerTypes.hasGeneratedType(namespace.exports, name.right.text) ||
590
+ !namespace.exports.values.has(schemaName)
591
+ ? undefined
592
+ : {
593
+ kind: namespace.exports.schemaRoles.get(schemaName),
594
+ reference: { moduleSpecifier: namespace.moduleSpecifier, exportName: schemaName },
595
+ };
596
+ },
597
+ schemaFromEntityName(name, imports) {
598
+ if (ts.isIdentifier(name)) {
599
+ const symbol = imports.generatedSymbols.get(name.text);
600
+ return symbol?.schemaValue !== true
601
+ ? undefined
602
+ : { moduleSpecifier: symbol.moduleSpecifier, exportName: symbol.exportName };
603
+ }
604
+ if (ts.isQualifiedName(name) && ts.isIdentifier(name.left)) {
605
+ const namespace = imports.generatedNamespaces.get(name.left.text);
606
+ return namespace?.exports.values.has(name.right.text) !== true
607
+ ? undefined
608
+ : { moduleSpecifier: namespace.moduleSpecifier, exportName: name.right.text };
609
+ }
610
+ return undefined;
611
+ },
612
+ frameworkEnvelope(typeNode, imports, walk = HandlerSources.newTypeWalk()) {
613
+ if (!HandlerSources.consumeTypeWalk(walk)) {
614
+ return undefined;
615
+ }
616
+ const unwrapped = HandlerSources.unwrapReadonly(typeNode);
617
+ if (!ts.isTypeReferenceNode(unwrapped)) {
618
+ return undefined;
619
+ }
620
+ if (ts.isIdentifier(unwrapped.typeName)) {
621
+ const alias = imports.localTypeAliases.get(unwrapped.typeName.text);
622
+ if (alias !== undefined) {
623
+ return HandlerSources.resolveAlias(unwrapped.typeName.text, alias, walk, (resolved) => HandlerSources.frameworkEnvelope(resolved, imports, walk));
624
+ }
625
+ return imports.protoSymbols.has(unwrapped.typeName.text)
626
+ ? unwrapped.typeName.text
627
+ : undefined;
628
+ }
629
+ if (ts.isQualifiedName(unwrapped.typeName) && ts.isIdentifier(unwrapped.typeName.left)) {
630
+ const namespace = unwrapped.typeName.left.text;
631
+ const name = unwrapped.typeName.right.text;
632
+ return imports.protoNamespaces.has(namespace) && (name === "Event" || name === "Command")
633
+ ? `${namespace}.${name}`
634
+ : undefined;
635
+ }
636
+ return undefined;
637
+ },
638
+ unwrapReadonly(typeNode) {
639
+ if (ts.isParenthesizedTypeNode(typeNode)) {
640
+ return HandlerSources.unwrapReadonly(typeNode.type);
641
+ }
642
+ if (ts.isTypeOperatorNode(typeNode) && typeNode.operator === ts.SyntaxKind.ReadonlyKeyword) {
643
+ return HandlerSources.unwrapReadonly(typeNode.type);
644
+ }
645
+ return typeNode;
646
+ },
647
+ unwrapOuterPromise(typeNode, scope, walk = HandlerSources.newTypeWalk()) {
648
+ if (!HandlerSources.consumeTypeWalk(walk)) {
649
+ return typeNode;
650
+ }
651
+ const unwrapped = HandlerSources.unwrapReadonly(typeNode);
652
+ if (!ts.isTypeReferenceNode(unwrapped) || !ts.isIdentifier(unwrapped.typeName)) {
653
+ return typeNode;
654
+ }
655
+ const alias = scope.imports.localTypeAliases.get(unwrapped.typeName.text);
656
+ if (alias !== undefined) {
657
+ return (HandlerSources.resolveAlias(unwrapped.typeName.text, alias, walk, (resolved) => HandlerSources.unwrapOuterPromise(resolved, scope, walk)) ?? typeNode);
658
+ }
659
+ if (unwrapped.typeName.text !== "Promise" ||
660
+ unwrapped.typeArguments?.length !== 1 ||
661
+ !HandlerSources.isBuiltInPromise(unwrapped, scope.program)) {
662
+ return typeNode;
663
+ }
664
+ return unwrapped.typeArguments[0] ?? typeNode;
665
+ },
666
+ isBuiltInPromise(typeNode, program) {
667
+ const symbol = program.getTypeChecker().getTypeFromTypeNode(typeNode).getSymbol();
668
+ return (symbol?.getName() === "Promise" &&
669
+ symbol.declarations?.some((declaration) => program.isSourceFileDefaultLibrary(declaration.getSourceFile())) === true);
670
+ },
671
+ isExplicitVoidType(typeNode) {
672
+ if (typeNode === undefined) {
673
+ return false;
674
+ }
675
+ if (ts.isParenthesizedTypeNode(typeNode)) {
676
+ return HandlerSources.isExplicitVoidType(typeNode.type);
677
+ }
678
+ return typeNode.kind === ts.SyntaxKind.VoidKeyword;
679
+ },
680
+ isArrayReferenceType(typeNode) {
681
+ return (ts.isIdentifier(typeNode.typeName) &&
682
+ (typeNode.typeName.text === "Array" || typeNode.typeName.text === "ReadonlyArray"));
683
+ },
684
+ handlerKind(decorator, signalKind) {
685
+ switch (decorator) {
686
+ case "Assign":
687
+ return "command-assignment";
688
+ case "Command":
689
+ return signalKind === "command" ? "command-substitution" : "command-reaction";
690
+ case "React":
691
+ return "event-reaction";
692
+ case "Subscribe":
693
+ return signalKind === "state" ? "state-subscription" : "event-subscription";
694
+ }
695
+ },
696
+ acceptsSignalKind(decorator, kind) {
697
+ if (decorator === "Command") {
698
+ return kind === "command" || kind === "event" || kind === "rejection";
699
+ }
700
+ if (decorator === "Assign") {
701
+ return kind === "command";
702
+ }
703
+ return (kind === "event" || kind === "rejection" || (decorator === "Subscribe" && kind === "state"));
704
+ },
705
+ signalMessage(decorator) {
706
+ if (decorator === "Command") {
707
+ return "a generated command, event, or rejection type";
708
+ }
709
+ if (decorator === "Assign") {
710
+ return "a generated command type";
711
+ }
712
+ return decorator === "Subscribe"
713
+ ? "a generated event, rejection, or Entity state type"
714
+ : "a generated event or rejection type";
715
+ },
716
+ emittedSignalKind(decorator) {
717
+ if (decorator === "Command") {
718
+ return "command";
719
+ }
720
+ if (decorator === "Assign" || decorator === "React") {
721
+ return "event";
722
+ }
723
+ return undefined;
724
+ },
725
+ externalOrigin(parameters, scope, className, method) {
726
+ const first = parameters[0]?.type;
727
+ if (first === undefined)
728
+ return undefined;
729
+ const marker = HandlerSources.externalMarker(first, scope);
730
+ const laterMarker = parameters
731
+ .slice(1)
732
+ .some((parameter) => parameter.type === undefined
733
+ ? false
734
+ : HandlerSources.containsExternalMarker(parameter.type, scope));
735
+ const nestedMarker = marker === undefined && HandlerSources.containsExternalMarker(first, scope);
736
+ if (laterMarker || nestedMarker || (marker !== undefined && !marker.direct)) {
737
+ HandlerTypes.pushDiagnostic(scope, "INVALID_EXTERNAL_ORIGIN", laterMarker ? (parameters[1]?.type ?? first) : first, "External<T> is valid only as the direct first receptor parameter type.", className, method);
738
+ return undefined;
739
+ }
740
+ if (marker?.direct === true && marker.type !== undefined) {
741
+ return { value: "external", type: marker.type };
742
+ }
743
+ return { value: "domestic", type: first };
744
+ },
745
+ validContextParameter(parameters, signalKind, scope, className, method) {
746
+ if (parameters.length !== 2)
747
+ return true;
748
+ const context = parameters[1]?.type;
749
+ const expected = signalKind === "command" ? "CommandContext" : "EventContext";
750
+ if (context !== undefined && HandlerSources.isCanonicalContext(context, expected, scope)) {
751
+ return true;
752
+ }
753
+ HandlerTypes.pushDiagnostic(scope, "INVALID_HANDLER_CONTEXT", context ?? parameters[1] ?? scope.source, `Two-argument handlers receiving ${signalKind === "command" ? "Commands" : "Events, rejections, or Entity states"} must declare ${expected} as their second parameter.`, className, method);
754
+ return false;
755
+ },
756
+ isCanonicalContext(type, expected, scope) {
757
+ if (!ts.isTypeReferenceNode(type))
758
+ return false;
759
+ if (ts.isIdentifier(type.typeName)) {
760
+ return scope.imports.protoContextSymbols.get(type.typeName.text) === expected;
761
+ }
762
+ return (ts.isIdentifier(type.typeName.left) &&
763
+ scope.imports.protoNamespaces.has(type.typeName.left.text) &&
764
+ type.typeName.right.text === expected);
765
+ },
766
+ externalMarker(type, scope) {
767
+ if (!ts.isTypeReferenceNode(type))
768
+ return undefined;
769
+ const canonical = (ts.isIdentifier(type.typeName) &&
770
+ scope.imports.serverSymbols.get(type.typeName.text) === "External") ||
771
+ (ts.isQualifiedName(type.typeName) &&
772
+ ts.isIdentifier(type.typeName.left) &&
773
+ type.typeName.right.text === "External" &&
774
+ scope.imports.serverNamespaces.has(type.typeName.left.text));
775
+ if (!canonical || !HandlerSources.isCanonicalExternal(type.typeName, scope.program))
776
+ return undefined;
777
+ const argument = type.typeArguments?.[0];
778
+ return argument === undefined
779
+ ? { direct: false }
780
+ : { direct: type.typeArguments?.length === 1, type: argument };
781
+ },
782
+ containsExternalMarker(type, scope, seen = new Set()) {
783
+ if (HandlerSources.externalMarker(type, scope) !== undefined)
784
+ return true;
785
+ if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) {
786
+ const alias = scope.imports.localTypeAliases.get(type.typeName.text);
787
+ if (alias !== undefined && !seen.has(type.typeName.text)) {
788
+ const next = new Set(seen);
789
+ next.add(type.typeName.text);
790
+ if (HandlerSources.containsExternalMarker(alias, scope, next))
791
+ return true;
792
+ }
793
+ }
794
+ let found = false;
795
+ const visit = (node) => {
796
+ if (found)
797
+ return;
798
+ if (ts.isTypeNode(node) && HandlerSources.externalMarker(node, scope) !== undefined) {
799
+ found = true;
800
+ return;
801
+ }
802
+ ts.forEachChild(node, visit);
803
+ };
804
+ ts.forEachChild(type, visit);
805
+ return found;
806
+ },
807
+ isCanonicalExternal(name, program) {
808
+ const checker = program.getTypeChecker();
809
+ const location = ts.isQualifiedName(name) ? name.right : name;
810
+ let symbol = checker.getSymbolAtLocation(location);
811
+ if (symbol === undefined)
812
+ return false;
813
+ if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
814
+ symbol = checker.getAliasedSymbol(symbol);
815
+ }
816
+ return (symbol.declarations ?? []).some((declaration) => ts.isTypeAliasDeclaration(declaration) &&
817
+ declaration.name.text === "External" &&
818
+ PackageIdentity.nameFor(declaration.getSourceFile().fileName) ===
819
+ "@spine-event-engine/server" &&
820
+ /^external(?:\.d)?\.ts$/u.test(declaration.getSourceFile().fileName.split(/[\\/]/u).at(-1) ?? ""));
821
+ },
822
+ newTypeWalk() {
823
+ return { remaining: maxAliasDepth, seen: new Set() };
824
+ },
825
+ consumeTypeWalk(walk) {
826
+ walk.remaining -= 1;
827
+ return walk.remaining >= 0;
828
+ },
829
+ resolveAlias(name, typeNode, walk, resolveType) {
830
+ if (walk.seen.has(name)) {
831
+ return undefined;
832
+ }
833
+ walk.seen.add(name);
834
+ const resolved = resolveType(typeNode);
835
+ walk.seen.delete(name);
836
+ return resolved;
837
+ },
838
+ buildImportState(source, program) {
839
+ const state = {
840
+ generatedNamespaces: new Map(),
841
+ generatedSymbols: new Map(),
842
+ localTypeAliases: new Map(),
843
+ serverNamespaces: new Set(),
844
+ serverSymbols: new Map(),
845
+ protoNamespaces: new Set(),
846
+ protoSymbols: new Set(),
847
+ protoContextSymbols: new Map(),
848
+ };
849
+ for (const statement of source.statements) {
850
+ HandlerSources.recordImportStatement(statement, source, program, state);
851
+ }
852
+ return state;
853
+ },
854
+ recordImportStatement(statement, source, program, state) {
855
+ if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
856
+ HandlerSources.recordImportDeclaration(statement, statement.moduleSpecifier.text, source, program, state);
857
+ }
858
+ if (ts.isTypeAliasDeclaration(statement)) {
859
+ state.localTypeAliases.set(statement.name.text, statement.type);
860
+ }
861
+ },
862
+ recordImportDeclaration(statement, moduleSpecifier, source, program, state) {
863
+ const bindings = statement.importClause?.namedBindings;
864
+ if (bindings === undefined) {
865
+ return;
866
+ }
867
+ if (moduleSpecifier === "@spine-event-engine/server") {
868
+ HandlerSources.recordServerImport(bindings, state);
869
+ }
870
+ if (moduleSpecifier === "@spine-event-engine/proto") {
871
+ HandlerSources.recordProtoImport(bindings, state);
872
+ }
873
+ if (HandlerSources.isGeneratedModule(moduleSpecifier)) {
874
+ HandlerSources.recordGeneratedImport(bindings, moduleSpecifier, statement.importClause?.phaseModifier !== ts.SyntaxKind.TypeKeyword, source, program, state);
875
+ }
876
+ },
877
+ recordServerImport(bindings, state) {
878
+ if (ts.isNamespaceImport(bindings)) {
879
+ state.serverNamespaces.add(bindings.name.text);
880
+ return;
881
+ }
882
+ for (const element of bindings.elements) {
883
+ state.serverSymbols.set(element.name.text, element.propertyName?.text ?? element.name.text);
884
+ }
885
+ },
886
+ recordProtoImport(bindings, state) {
887
+ if (ts.isNamespaceImport(bindings)) {
888
+ state.protoNamespaces.add(bindings.name.text);
889
+ return;
890
+ }
891
+ for (const element of bindings.elements) {
892
+ const imported = element.propertyName?.text ?? element.name.text;
893
+ if (imported === "Event" || imported === "Command") {
894
+ state.protoSymbols.add(element.name.text);
895
+ }
896
+ if (imported === "CommandContext" || imported === "EventContext") {
897
+ state.protoContextSymbols.set(element.name.text, imported);
898
+ }
899
+ }
900
+ },
901
+ recordGeneratedImport(bindings, moduleSpecifier, valueImport, source, program, state) {
902
+ const exported = HandlerSources.generatedModuleExports(source, moduleSpecifier, program);
903
+ if (exported === undefined) {
904
+ return;
905
+ }
906
+ if (ts.isNamespaceImport(bindings)) {
907
+ state.generatedNamespaces.set(bindings.name.text, { exports: exported, moduleSpecifier });
908
+ return;
909
+ }
910
+ for (const element of bindings.elements) {
911
+ const exportName = element.propertyName?.text ?? element.name.text;
912
+ if (!HandlerTypes.hasGeneratedType(exported, exportName)) {
913
+ continue;
914
+ }
915
+ const schemaExportName = `${exportName}Schema`;
916
+ state.generatedSymbols.set(element.name.text, {
917
+ exports: exported,
918
+ moduleSpecifier,
919
+ exportName,
920
+ kind: exported.schemaRoles.get(schemaExportName),
921
+ schemaExportName: exported.values.has(schemaExportName) ? schemaExportName : undefined,
922
+ schemaValue: valueImport && !element.isTypeOnly && exported.values.has(exportName),
923
+ });
924
+ }
925
+ },
926
+ isGeneratedModule(moduleSpecifier) {
927
+ return /(^|\/)generated\/.+_pb(\.js)?$/.test(moduleSpecifier);
928
+ },
929
+ generatedModuleExports(source, moduleSpecifier, program) {
930
+ const module = HandlerSources.generatedModuleSource(source, moduleSpecifier, program);
931
+ if (module === undefined)
932
+ return undefined;
933
+ const declarations = HandlerSources.exportedNames(module);
934
+ const runtime = HandlerSources.pairedRuntimeExports(module);
935
+ if (runtime === undefined)
936
+ return declarations;
937
+ return {
938
+ types: declarations.types,
939
+ values: runtime.values,
940
+ schemaRoles: runtime.schemaRoles,
941
+ };
942
+ },
943
+ pairedRuntimeExports(declarations) {
944
+ if (!declarations.isDeclarationFile)
945
+ return undefined;
946
+ const runtimePath = declarations.fileName.replace(/\.d\.ts$/u, ".js");
947
+ try {
948
+ return HandlerSources.exportedNames(ts.createSourceFile(runtimePath, readFileSync(runtimePath, "utf8"), ts.ScriptTarget.Latest));
949
+ }
950
+ catch {
951
+ return undefined;
952
+ }
953
+ },
954
+ generatedModuleSource(source, moduleSpecifier, program) {
955
+ const imported = HandlerSources.importedModuleSource(source, moduleSpecifier, program);
956
+ if (imported !== undefined) {
957
+ return imported;
958
+ }
959
+ const resolved = ts.resolveModuleName(moduleSpecifier, source.fileName, program.getCompilerOptions(), ts.sys).resolvedModule?.resolvedFileName;
960
+ const base = resolve(dirname(source.fileName), moduleSpecifier);
961
+ const candidates = HandlerSources.uniqueStrings([
962
+ ...(resolved === undefined ? [] : [resolved]),
963
+ base.replace(/\.js$/, ".ts"),
964
+ `${base}.ts`,
965
+ base,
966
+ base.replace(/\.js$/, ".d.ts"),
967
+ `${base}.d.ts`,
968
+ ]);
969
+ const sourceFiles = new Map(program.getSourceFiles().map((candidate) => [resolve(candidate.fileName), candidate]));
970
+ for (const candidate of candidates) {
971
+ const sourceFile = sourceFiles.get(candidate);
972
+ if (sourceFile !== undefined) {
973
+ return sourceFile;
974
+ }
975
+ }
976
+ return undefined;
977
+ },
978
+ importedModuleSource(source, moduleSpecifier, program) {
979
+ const declaration = source.statements.find((statement) => ts.isImportDeclaration(statement) &&
980
+ ts.isStringLiteral(statement.moduleSpecifier) &&
981
+ statement.moduleSpecifier.text === moduleSpecifier);
982
+ if (declaration === undefined)
983
+ return undefined;
984
+ const symbol = program.getTypeChecker().getSymbolAtLocation(declaration.moduleSpecifier);
985
+ const imported = symbol?.declarations
986
+ ?.map((candidate) => candidate.getSourceFile())
987
+ .find((candidate) => candidate !== source);
988
+ return imported;
989
+ },
990
+ uniqueStrings(values) {
991
+ return [...new Set(values)];
992
+ },
993
+ exportedNames(source) {
994
+ const files = HandlerSources.generatedFiles(source);
995
+ const schemaRoles = new Map();
996
+ const exports = { types: new Set(), values: new Set(), schemaRoles };
997
+ for (const statement of source.statements) {
998
+ HandlerSources.recordExportedNames(statement, exports, files);
999
+ }
1000
+ return exports;
1001
+ },
1002
+ generatedFiles(source) {
1003
+ const files = new Map();
1004
+ for (const statement of source.statements) {
1005
+ if (!ts.isVariableStatement(statement)) {
1006
+ continue;
1007
+ }
1008
+ for (const declaration of statement.declarationList.declarations) {
1009
+ if (!ts.isIdentifier(declaration.name)) {
1010
+ continue;
1011
+ }
1012
+ const descriptor = HandlerSources.fileDescriptor(declaration.initializer);
1013
+ if (descriptor !== undefined) {
1014
+ files.set(declaration.name.text, descriptor);
1015
+ }
1016
+ }
1017
+ }
1018
+ return files;
1019
+ },
1020
+ recordExportedNames(statement, exports, files) {
1021
+ if (ts.isVariableStatement(statement) &&
1022
+ HandlerTypes.hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
1023
+ for (const declaration of statement.declarationList.declarations) {
1024
+ if (ts.isIdentifier(declaration.name)) {
1025
+ exports.values.add(declaration.name.text);
1026
+ const schemaRole = HandlerSources.schemaRoleFromInitializer(declaration.name.text, declaration.initializer, files);
1027
+ if (schemaRole.found) {
1028
+ exports.schemaRoles.set(declaration.name.text, schemaRole.kind);
1029
+ }
1030
+ }
1031
+ }
1032
+ }
1033
+ if (HandlerTypes.hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
1034
+ HandlerTypes.recordNamedExport(statement, exports);
1035
+ }
1036
+ },
1037
+ schemaRoleFromInitializer(schemaExportName, initializer, files) {
1038
+ const call = HandlerSources.callExpression(initializer, "messageDesc");
1039
+ if (call === undefined) {
1040
+ return { found: false, kind: undefined };
1041
+ }
1042
+ const fileName = call.arguments[0];
1043
+ if (fileName === undefined || !ts.isIdentifier(fileName)) {
1044
+ return { found: false, kind: undefined };
1045
+ }
1046
+ const file = files.get(fileName.text);
1047
+ if (file === undefined) {
1048
+ return { found: true, kind: undefined };
1049
+ }
1050
+ const indexes = HandlerSources.messageDescIndexes(call);
1051
+ const message = HandlerSources.descriptorMessageAt(file, indexes);
1052
+ const messageName = message?.exportName;
1053
+ const expectedName = schemaExportName.replace(/Schema$/, "");
1054
+ if (messageName === undefined || messageName !== expectedName) {
1055
+ return { found: true, kind: undefined };
1056
+ }
1057
+ return {
1058
+ found: true,
1059
+ kind: message?.descriptor.isEntityState
1060
+ ? "state"
1061
+ : HandlerSources.signalKindFromProto(file.sourceFile, indexes),
1062
+ };
1063
+ },
1064
+ messageDescIndexes(call) {
1065
+ const indexes = [];
1066
+ for (const argument of call.arguments.slice(1)) {
1067
+ if (!ts.isNumericLiteral(argument)) {
1068
+ return undefined;
1069
+ }
1070
+ const index = Number(argument.text);
1071
+ if (!Number.isInteger(index) || index < 0) {
1072
+ return undefined;
1073
+ }
1074
+ indexes.push(index);
1075
+ }
1076
+ return indexes.length === 0 ? undefined : indexes;
1077
+ },
1078
+ descriptorMessageAt(file, indexes) {
1079
+ if (indexes === undefined) {
1080
+ return undefined;
1081
+ }
1082
+ let messages = file.messages;
1083
+ let message;
1084
+ const path = [];
1085
+ for (const index of indexes) {
1086
+ message = messages[index];
1087
+ if (message === undefined) {
1088
+ return undefined;
1089
+ }
1090
+ path.push(message.name);
1091
+ messages = message.nested;
1092
+ }
1093
+ return message === undefined ? undefined : { descriptor: message, exportName: path.join("_") };
1094
+ },
1095
+ fileDescriptor(initializer) {
1096
+ const call = HandlerSources.callExpression(initializer, "fileDesc");
1097
+ const descriptor = call?.arguments[0];
1098
+ if (descriptor === undefined || !ts.isStringLiteralLike(descriptor)) {
1099
+ return undefined;
1100
+ }
1101
+ try {
1102
+ const { protobuf, protobufWkt } = PackageDependencies.current();
1103
+ const file = protobuf.fromBinary(protobufWkt.FileDescriptorProtoSchema, Buffer.from(descriptor.text, "base64"));
1104
+ return file.name === ""
1105
+ ? undefined
1106
+ : {
1107
+ sourceFile: file.name,
1108
+ messages: file.messageType.map(HandlerSources.descriptorMessage),
1109
+ };
1110
+ }
1111
+ catch {
1112
+ return undefined;
1113
+ }
1114
+ },
1115
+ callExpression(expression, functionName) {
1116
+ if (expression === undefined) {
1117
+ return undefined;
1118
+ }
1119
+ const unwrapped = HandlerSources.unwrapExpression(expression);
1120
+ if (!ts.isCallExpression(unwrapped) || !ts.isIdentifier(unwrapped.expression)) {
1121
+ return undefined;
1122
+ }
1123
+ return unwrapped.expression.text === functionName ? unwrapped : undefined;
1124
+ },
1125
+ unwrapExpression(expression) {
1126
+ if (ts.isParenthesizedExpression(expression)) {
1127
+ return HandlerSources.unwrapExpression(expression.expression);
1128
+ }
1129
+ if (ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression)) {
1130
+ return HandlerSources.unwrapExpression(expression.expression);
1131
+ }
1132
+ return expression;
1133
+ },
1134
+ signalKindFromProto(sourceFile, messageIndexes) {
1135
+ const sourceName = sourceFile.split(/[\\/]/u).at(-1);
1136
+ if (sourceName === "rejections.proto" || sourceName?.endsWith("_rejections.proto") === true) {
1137
+ return messageIndexes?.length === 1 ? "rejection" : undefined;
1138
+ }
1139
+ if (sourceName === "commands.proto" || sourceName?.endsWith("_commands.proto") === true) {
1140
+ return "command";
1141
+ }
1142
+ if (sourceName === "events.proto" || sourceName?.endsWith("_events.proto") === true) {
1143
+ return "event";
1144
+ }
1145
+ return undefined;
1146
+ },
1147
+ descriptorMessage(message) {
1148
+ return {
1149
+ name: message.name,
1150
+ isEntityState: message.options?.$unknown?.some((field) => field.no === entityOptionFieldNumber) === true,
1151
+ nested: message.nestedType.map(HandlerSources.descriptorMessage),
1152
+ };
1153
+ },
1154
+ });
1155
+ /**
1156
+ * Finds the package identity that owns an analyzed source file.
1157
+ */
1158
+ export const PackageIdentity = Object.freeze({
1159
+ nameFor(sourceFile) {
1160
+ let directory = resolve(dirname(sourceFile));
1161
+ let manifest = join(directory, "package.json");
1162
+ while (!existsSync(manifest)) {
1163
+ const parent = dirname(directory);
1164
+ if (parent === directory)
1165
+ return undefined;
1166
+ directory = parent;
1167
+ manifest = join(directory, "package.json");
1168
+ }
1169
+ try {
1170
+ const { name } = JSON.parse(readFileSync(manifest, "utf8"));
1171
+ return typeof name === "string" ? name : undefined;
1172
+ }
1173
+ catch {
1174
+ return undefined;
1175
+ }
1176
+ },
1177
+ });
1178
+ const PackageDependencies = Object.freeze({
1179
+ load(program) {
1180
+ packageDependencies ??= {
1181
+ protobuf: PackageDependencies.require("@bufbuild/protobuf", program),
1182
+ protobufWkt: PackageDependencies.require("@bufbuild/protobuf/wkt", program),
1183
+ };
1184
+ },
1185
+ current() {
1186
+ if (packageDependencies === undefined)
1187
+ throw new Error("Build handler analyzer dependencies are not loaded.");
1188
+ return packageDependencies;
1189
+ },
1190
+ require(specifier, program) {
1191
+ try {
1192
+ return createRequire(import.meta.url)(specifier);
1193
+ }
1194
+ catch (directError) {
1195
+ const appRoot = program.getRootFileNames()[0];
1196
+ if (appRoot === undefined)
1197
+ throw directError;
1198
+ const applicationRequire = createRequire(appRoot);
1199
+ try {
1200
+ return applicationRequire(specifier);
1201
+ }
1202
+ catch {
1203
+ // The in-repository generator transpiles this module into a temporary
1204
+ // cache directory. A fresh workspace install has the descriptor
1205
+ // package at the application's resolution boundary, while the
1206
+ // temporary module has no package boundary of its own.
1207
+ }
1208
+ try {
1209
+ const toolingEntry = applicationRequire.resolve("@spine-event-engine/proto-tools");
1210
+ return createRequire(join(PackageDependencies.rootFor(toolingEntry), "package.json"))(specifier);
1211
+ }
1212
+ catch {
1213
+ throw directError;
1214
+ }
1215
+ }
1216
+ },
1217
+ rootFor(entry) {
1218
+ let directory = dirname(entry);
1219
+ while (!existsSync(join(directory, "package.json"))) {
1220
+ const parent = dirname(directory);
1221
+ if (parent === directory)
1222
+ throw new Error("Cannot locate proto-tools package manifest.");
1223
+ directory = parent;
1224
+ }
1225
+ return directory;
1226
+ },
1227
+ });
1228
+ const HandlerTypes = Object.freeze({
1229
+ recordNamedExport(statement, exports) {
1230
+ if (ts.isClassDeclaration(statement) || ts.isEnumDeclaration(statement)) {
1231
+ const name = statement.name;
1232
+ if (name !== undefined) {
1233
+ exports.types.add(name.text);
1234
+ exports.values.add(name.text);
1235
+ }
1236
+ return;
1237
+ }
1238
+ if (ts.isFunctionDeclaration(statement)) {
1239
+ const name = statement.name;
1240
+ if (name !== undefined) {
1241
+ exports.values.add(name.text);
1242
+ }
1243
+ return;
1244
+ }
1245
+ if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) {
1246
+ exports.types.add(statement.name.text);
1247
+ }
1248
+ },
1249
+ hasGeneratedType(exports, name) {
1250
+ return exports.types.has(name) || exports.values.has(name);
1251
+ },
1252
+ serverDecorator(name) {
1253
+ return name === "Assign" ||
1254
+ name === "Command" ||
1255
+ name === "React" ||
1256
+ name === "Subscribe" ||
1257
+ name === "Apply" ||
1258
+ name === "Where"
1259
+ ? name
1260
+ : undefined;
1261
+ },
1262
+ methodName(node) {
1263
+ if (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) {
1264
+ return node.name.text;
1265
+ }
1266
+ return undefined;
1267
+ },
1268
+ hasModifier(node, kind) {
1269
+ return (ts.canHaveModifiers(node) &&
1270
+ (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === kind));
1271
+ },
1272
+ expressionName(expression) {
1273
+ if (ts.isIdentifier(expression)) {
1274
+ return expression.text;
1275
+ }
1276
+ if (ts.isPropertyAccessExpression(expression)) {
1277
+ const prefix = HandlerTypes.expressionName(expression.expression);
1278
+ return prefix === undefined ? undefined : `${prefix}.${expression.name.text}`;
1279
+ }
1280
+ return undefined;
1281
+ },
1282
+ pushDiagnostic(scope, code, node, message, className, methodNameValue) {
1283
+ const location = scope.source.getLineAndCharacterOfPosition(node.getStart(scope.source));
1284
+ const base = {
1285
+ code,
1286
+ sourceFile: scope.source.fileName,
1287
+ line: location.line + 1,
1288
+ column: location.character + 1,
1289
+ message,
1290
+ };
1291
+ scope.diagnostics.push({
1292
+ ...base,
1293
+ ...(className === undefined ? {} : { className }),
1294
+ ...(methodNameValue === undefined ? {} : { methodName: methodNameValue }),
1295
+ });
1296
+ },
1297
+ syntaxDiagnostic(source, diagnostic) {
1298
+ const location = source.getLineAndCharacterOfPosition(diagnostic.start);
1299
+ return {
1300
+ code: "TYPESCRIPT_SYNTAX_ERROR",
1301
+ sourceFile: source.fileName,
1302
+ line: location.line + 1,
1303
+ column: location.character + 1,
1304
+ message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
1305
+ };
1306
+ },
1307
+ });
1308
+ //# sourceMappingURL=build-time-handler-analyzer.js.map