@spine-event-engine/proto-tools 2.0.0-snapshot.2 → 2.0.0-snapshot.6

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