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

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.
package/README.md CHANGED
@@ -80,6 +80,8 @@ the generated handler registry for decorated application classes. Its emitted
80
80
  source imports the Server handler-registry contract as a type from
81
81
  `@spine-event-engine/server/spi/handler-registry`; the CLI itself has no Server
82
82
  runtime dependency. Run both after the related model or handler changes.
83
+ The handler analyzer records `@Throws(...)` declarations as rejection outcomes;
84
+ the generated rejection companion supplies the schema used by that metadata.
83
85
 
84
86
  The public CLI is `spine-proto`. Programmatic config and manifest readers are
85
87
  for build tooling that needs the same validated package contracts; application
@@ -100,24 +100,46 @@ export interface BuildHandlerRecord {
100
100
  /**
101
101
  * Generated schema accepted by the first handler parameter.
102
102
  */
103
- readonly signalSchema: SchemaReference;
103
+ readonly input: BuildHandlerInput;
104
104
  /**
105
- * Generated schemas emitted by the handler return type.
105
+ * Generated schemas returned normally or declared as thrown rejections.
106
106
  */
107
- readonly emittedSchemas: readonly SchemaReference[];
107
+ readonly outcomes: BuildHandlerOutcomes;
108
108
  /**
109
109
  * Public method arity: `handler(signal)` or `handler(signal, context)`.
110
110
  */
111
111
  readonly parameterCount: GeneratedHandlerParameterCount;
112
+ }
113
+ /**
114
+ * Input metadata for one generated handler.
115
+ */
116
+ export interface BuildHandlerInput {
112
117
  /**
113
- * Origin declared on the first receptor parameter.
118
+ * Generated schema accepted by the handler.
119
+ */
120
+ readonly schema: SchemaReference;
121
+ /**
122
+ * Origin required for an Event input.
114
123
  */
115
124
  readonly origin: "domestic" | "external";
116
125
  /**
117
- * Optional statically declared Event field filter.
126
+ * Optional generated Event field filter.
118
127
  */
119
128
  readonly where?: BuildWhereOptions;
120
129
  }
130
+ /**
131
+ * Normal and declared rejection outcomes for one generated handler.
132
+ */
133
+ export interface BuildHandlerOutcomes {
134
+ /**
135
+ * Generated schemas returned normally by the handler.
136
+ */
137
+ readonly returned: readonly SchemaReference[];
138
+ /**
139
+ * Generated rejection schemas that the handler may throw.
140
+ */
141
+ readonly thrown: readonly SchemaReference[];
142
+ }
121
143
  /**
122
144
  * Build-time representation of one `@Where` declaration.
123
145
  */
@@ -134,7 +156,7 @@ export interface BuildWhereOptions {
134
156
  /**
135
157
  * Stable diagnostic codes emitted by build-time handler analysis.
136
158
  */
137
- export type BuildHandlerDiagnosticCode = "APPLY_DECORATOR" | "FRAMEWORK_ENVELOPE_RETURN" | "INVALID_EMITTED_SCHEMA" | "INVALID_HANDLER_NAME" | "INVALID_HANDLER_CONTEXT" | "INVALID_HANDLER_VISIBILITY" | "INVALID_PARAMETER_COUNT" | "INVALID_SIGNAL_TYPE" | "INVALID_EXTERNAL_ORIGIN" | "EXTERNAL_COMMAND_RECEIVER" | "INVALID_SUBSCRIBE_RETURN" | "INVALID_WHERE" | "MISSING_EMITTED_SCHEMAS" | "MISSING_ENTITY_STATE_SCHEMA" | "MISSING_RETURN_TYPE" | "MISSING_SIGNAL_TYPE" | "NON_EXPORTED_RECEIVER_CLASS" | "SCHEMA_BEARING_DECORATOR" | "TYPESCRIPT_SYNTAX_ERROR" | "UNSUPPORTED_COMMAND_HANDLER" | "UNSUPPORTED_RETURN_TYPE";
159
+ export type BuildHandlerDiagnosticCode = "APPLY_DECORATOR" | "FRAMEWORK_ENVELOPE_RETURN" | "INVALID_EMITTED_SCHEMA" | "INVALID_HANDLER_NAME" | "INVALID_HANDLER_CONTEXT" | "INVALID_HANDLER_VISIBILITY" | "INVALID_PARAMETER_COUNT" | "INVALID_SIGNAL_TYPE" | "INVALID_EXTERNAL_ORIGIN" | "EXTERNAL_COMMAND_RECEIVER" | "INVALID_SUBSCRIBE_RETURN" | "INVALID_THROWS" | "INVALID_WHERE" | "MISSING_EMITTED_SCHEMAS" | "MISSING_ENTITY_STATE_SCHEMA" | "MISSING_RETURN_TYPE" | "MISSING_SIGNAL_TYPE" | "NON_EXPORTED_RECEIVER_CLASS" | "SCHEMA_BEARING_DECORATOR" | "TYPESCRIPT_SYNTAX_ERROR" | "UNSUPPORTED_ASSIGN_HANDLER" | "UNSUPPORTED_COMMAND_HANDLER" | "UNSUPPORTED_RETURN_TYPE";
138
160
  /**
139
161
  * One build-time analyzer diagnostic.
140
162
  */
@@ -1 +1 @@
1
- {"version":3,"file":"build-time-handler-analyzer.d.ts","sourceRoot":"","sources":["../../../src/generation/build-time-handler-analyzer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build-time-handler-analyzer.d.ts","sourceRoot":"","sources":["../../../src/generation/build-time-handler-analyzer.ts"],"names":[],"mappings":"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"}
@@ -89,23 +89,26 @@ const HandlerSources = Object.freeze({
89
89
  return undefined;
90
90
  }
91
91
  const lineage = HandlerSources.receiverLineage(node, scope);
92
- const entityBase = lineage?.base;
93
- const stateSchema = lineage?.stateSchema;
92
+ const handlers = HandlerSources.analyzeMethods(node, className, lineage, scope);
93
+ if (lineage === undefined || handlers.length === 0) {
94
+ return undefined;
95
+ }
96
+ return HandlerSources.receiverRecord(node, className, lineage, handlers, scope.source.fileName);
97
+ },
98
+ analyzeMethods(node, className, lineage, scope) {
94
99
  const handlers = [];
95
100
  for (const member of node.members) {
96
- if (!ts.isMethodDeclaration(member)) {
101
+ if (!ts.isMethodDeclaration(member))
97
102
  continue;
98
- }
99
- const handler = HandlerSources.analyzeMethod(member, className, entityBase, stateSchema, lineage?.receiverKind, scope);
100
- if (handler !== undefined) {
103
+ const handler = HandlerSources.analyzeMethod(member, className, lineage?.base, lineage?.stateSchema, lineage?.receiverKind, scope);
104
+ if (handler !== undefined)
101
105
  handlers.push(handler);
102
- }
103
- }
104
- if (lineage === undefined || handlers.length === 0) {
105
- return undefined;
106
106
  }
107
+ return handlers;
108
+ },
109
+ receiverRecord(node, className, lineage, handlers, sourceFile) {
107
110
  return lineage.receiverKind === "entity"
108
- ? stateSchema === undefined
111
+ ? lineage.stateSchema === undefined
109
112
  ? undefined
110
113
  : {
111
114
  receiverKind: "entity",
@@ -113,8 +116,8 @@ const HandlerSources = Object.freeze({
113
116
  ...(HandlerTypes.hasModifier(node, ts.SyntaxKind.DefaultKeyword)
114
117
  ? { defaultExport: true }
115
118
  : {}),
116
- sourceFile: scope.source.fileName,
117
- stateSchema,
119
+ sourceFile,
120
+ stateSchema: lineage.stateSchema,
118
121
  handlers,
119
122
  }
120
123
  : {
@@ -123,7 +126,7 @@ const HandlerSources = Object.freeze({
123
126
  ...(HandlerTypes.hasModifier(node, ts.SyntaxKind.DefaultKeyword)
124
127
  ? { defaultExport: true }
125
128
  : {}),
126
- sourceFile: scope.source.fileName,
129
+ sourceFile,
127
130
  handlers,
128
131
  };
129
132
  },
@@ -132,58 +135,171 @@ const HandlerSources = Object.freeze({
132
135
  const apply = decorators.find((decorator) => decorator.name === "Apply");
133
136
  const handler = decorators.find(HandlerSources.isHandlerUse);
134
137
  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
- }
138
+ const throwsUses = decorators.filter((decorator) => decorator.name === "Throws");
144
139
  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) {
140
+ const use = {
141
+ node,
142
+ apply,
143
+ handler,
144
+ whereUses,
145
+ throwsUses,
146
+ className,
147
+ entityBase,
148
+ stateSchema,
149
+ receiverKind,
150
+ scope,
151
+ method,
152
+ };
153
+ if (!HandlerSources.validMethodUse(use))
160
154
  return undefined;
161
- }
162
- const origin = HandlerSources.externalOrigin(node.parameters, scope, className, method);
155
+ return HandlerSources.analyzeValidMethod(use);
156
+ },
157
+ analyzeValidMethod(input) {
158
+ const { className, handler, method, node, scope } = input;
159
+ const parameters = node.parameters;
160
+ const origin = HandlerSources.externalOrigin(parameters, scope, className, method);
163
161
  if (origin === undefined)
164
162
  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) {
163
+ const schemas = HandlerSources.methodSchemaUses(node, handler, origin.type, scope);
164
+ if (schemas === undefined || method === undefined)
168
165
  return undefined;
169
- }
170
- if (!HandlerSources.validContextParameter(node.parameters, signal.kind, scope, className, method)) {
166
+ const { signal, returnedSchemas } = schemas;
167
+ if (!HandlerSources.validContextParameter(parameters, signal.kind, scope, className, method))
171
168
  return undefined;
172
- }
173
- const where = HandlerSources.whereDeclaration(whereUses, handler, signal.kind, scope, className, method);
174
- if (whereUses.length > 0 && where === undefined) {
169
+ const declarations = HandlerSources.methodDeclarations(input, signal.kind, method);
170
+ if (declarations === undefined)
175
171
  return undefined;
176
- }
172
+ return HandlerSources.buildHandlerRecord(node, handler, signal, method, returnedSchemas, declarations.thrown, origin.value, declarations.where);
173
+ },
174
+ methodDeclarations(input, signalKind, methodName) {
175
+ const { className, handler, scope, throwsUses, whereUses } = input;
176
+ const where = HandlerSources.whereDeclaration(whereUses, handler, signalKind, scope, className, methodName);
177
+ if (whereUses.length > 0 && where === undefined)
178
+ return undefined;
179
+ const thrown = HandlerSources.throwsDeclaration(throwsUses, handler, signalKind, scope, className, methodName);
180
+ return throwsUses.length > 0 && thrown === undefined
181
+ ? undefined
182
+ : { where, thrown: thrown ?? [] };
183
+ },
184
+ methodSchemaUses(node, handler, signalType, scope) {
185
+ const signal = HandlerSources.schemaUseFromType(signalType, scope.imports);
186
+ const returned = HandlerSources.emittedSchemaUses(node.type === undefined ? undefined : HandlerSources.unwrapOuterPromise(node.type, scope), handler.name, scope.imports);
187
+ return signal === undefined || returned === undefined
188
+ ? undefined
189
+ : { signal, returnedSchemas: returned.map((schema) => schema.reference) };
190
+ },
191
+ buildHandlerRecord(node, handler, signal, method, returnedSchemas, thrownSchemas, origin, where) {
177
192
  return {
178
193
  kind: HandlerSources.handlerKind(handler.name, signal.kind),
179
194
  methodName: method,
180
- signalSchema: signal.reference,
181
- emittedSchemas,
195
+ input: { schema: signal.reference, origin, ...(where === undefined ? {} : { where }) },
196
+ outcomes: { returned: returnedSchemas, thrown: thrownSchemas },
182
197
  parameterCount: node.parameters.length,
183
- origin: origin.value,
184
- ...(where === undefined ? {} : { where }),
185
198
  };
186
199
  },
200
+ validMethodUse(input) {
201
+ HandlerSources.reportUnsupportedApply(input);
202
+ if (input.handler === undefined)
203
+ return HandlerSources.reportMissingHandler(input);
204
+ const { className, handler, method, node, scope } = input;
205
+ if (!HandlerSources.validHandlerKind({ ...input, handler }))
206
+ return false;
207
+ if (!HandlerSources.validBareHandlerDecorator(handler, scope, className, method))
208
+ return false;
209
+ return !HandlerSources.validateHandlerNode(node, handler.name, input.receiverKind === "entity" ? input.stateSchema : undefined, input.receiverKind !== "standalone", scope, className, method);
210
+ },
211
+ throwsDeclaration(uses, handler, signalKind, scope, className, methodName) {
212
+ if (uses.length === 0)
213
+ return [];
214
+ const expression = HandlerSources.throwsCall(uses, handler, signalKind, scope, className, methodName);
215
+ if (expression === undefined)
216
+ return undefined;
217
+ return HandlerSources.thrownSchemas(expression, scope, className, methodName);
218
+ },
219
+ throwsCall(uses, handler, signalKind, scope, className, methodName) {
220
+ const use = uses[0];
221
+ const commandReceptor = handler.name === "Assign" || (handler.name === "Command" && signalKind === "command");
222
+ if (uses.length !== 1 || !commandReceptor || use === undefined) {
223
+ HandlerSources.invalidThrows(scope, use?.node ?? handler.node, "@Throws is allowed once on a command-accepting @Assign or @Command handler.", className, methodName);
224
+ return undefined;
225
+ }
226
+ const expression = use.node.expression;
227
+ if (ts.isCallExpression(expression) && expression.arguments.length > 0)
228
+ return expression;
229
+ HandlerSources.invalidThrows(scope, use.node, "@Throws requires at least one generated rejection companion.", className, methodName);
230
+ return undefined;
231
+ },
232
+ thrownSchemas(expression, scope, className, methodName) {
233
+ const schemas = expression.arguments.map((argument) => HandlerSources.rejectionSchemaFromExpression(argument, scope.imports));
234
+ if (schemas.some((schema) => schema === undefined)) {
235
+ HandlerSources.invalidThrows(scope, expression, "@Throws accepts only generated rejection companions.", className, methodName);
236
+ return undefined;
237
+ }
238
+ const resolved = schemas;
239
+ const keys = resolved.map((schema) => `${schema.moduleSpecifier}\u0000${schema.exportName}`);
240
+ if (new Set(keys).size !== keys.length) {
241
+ HandlerSources.invalidThrows(scope, expression, "@Throws cannot declare the same rejection more than once.", className, methodName);
242
+ return undefined;
243
+ }
244
+ return resolved;
245
+ },
246
+ invalidThrows(scope, node, message, className, methodName) {
247
+ HandlerTypes.pushDiagnostic(scope, "INVALID_THROWS", node, message, className, methodName);
248
+ return undefined;
249
+ },
250
+ rejectionSchemaFromExpression(expression, imports) {
251
+ const unwrapped = HandlerSources.unwrapExpression(expression);
252
+ if (ts.isIdentifier(unwrapped))
253
+ return imports.rejectionSymbols.get(unwrapped.text);
254
+ if (!ts.isPropertyAccessExpression(unwrapped) || !ts.isIdentifier(unwrapped.expression)) {
255
+ return undefined;
256
+ }
257
+ const namespace = imports.rejectionNamespaces.get(unwrapped.expression.text);
258
+ const schemaName = `${unwrapped.name.text}Schema`;
259
+ return namespace?.exports.schemaRoles.get(schemaName) !== "rejection"
260
+ ? undefined
261
+ : { moduleSpecifier: namespace.moduleSpecifier, exportName: schemaName };
262
+ },
263
+ reportUnsupportedApply(input) {
264
+ if (input.apply === undefined)
265
+ return;
266
+ HandlerTypes.pushDiagnostic(input.scope, "APPLY_DECORATOR", input.apply.node, "Generated registries do not support @Apply.", input.className, input.method);
267
+ },
268
+ reportMissingHandler(input) {
269
+ if (input.whereUses.length > 0)
270
+ HandlerTypes.pushDiagnostic(input.scope, "INVALID_WHERE", input.whereUses[0]?.node ?? input.node, "@Where requires an Event-consuming @Subscribe, @React, or @Command handler.", input.className, input.method);
271
+ if (input.throwsUses.length > 0)
272
+ HandlerTypes.pushDiagnostic(input.scope, "INVALID_THROWS", input.throwsUses[0]?.node ?? input.node, "@Throws requires an @Assign handler.", input.className, input.method);
273
+ return false;
274
+ },
275
+ validBareHandlerDecorator(handler, scope, className, method) {
276
+ if (!handler.hasArguments)
277
+ return true;
278
+ HandlerTypes.pushDiagnostic(scope, "SCHEMA_BEARING_DECORATOR", handler.node, `@${handler.name}(...) is not supported in analyzed app source.`, className, method);
279
+ return false;
280
+ },
281
+ validHandlerKind(input) {
282
+ return (HandlerSources.validEntityHandlerKind(input) &&
283
+ HandlerSources.validStandaloneHandlerKind(input));
284
+ },
285
+ validEntityHandlerKind(input) {
286
+ const { className, entityBase, handler, method, scope } = input;
287
+ if (entityBase === "Projection" && handler.name === "Assign")
288
+ return HandlerSources.unsupported(scope, "UNSUPPORTED_ASSIGN_HANDLER", handler.node, "Projection handlers cannot use @Assign.", className, method);
289
+ return ((entityBase !== "Aggregate" && entityBase !== "Projection") ||
290
+ handler.name !== "Command" ||
291
+ HandlerSources.unsupported(scope, "UNSUPPORTED_COMMAND_HANDLER", handler.node, "Only Process Managers support @Command handlers.", className, method));
292
+ },
293
+ validStandaloneHandlerKind(input) {
294
+ const { className, entityBase, handler, method, receiverKind, scope } = input;
295
+ return (receiverKind !== "standalone" ||
296
+ HandlerSources.allowsStandaloneDecorator(entityBase, handler.name) ||
297
+ HandlerSources.unsupported(scope, "UNSUPPORTED_COMMAND_HANDLER", handler.node, `@${handler.name} is not legal for standalone ${entityBase ?? "receiver"}.`, className, method));
298
+ },
299
+ unsupported(scope, code, node, message, className, method) {
300
+ HandlerTypes.pushDiagnostic(scope, code, node, message, className, method);
301
+ return false;
302
+ },
187
303
  whereDeclaration(uses, handler, signalKind, scope, className, methodName) {
188
304
  if (uses.length === 0)
189
305
  return undefined;
@@ -839,6 +955,8 @@ const HandlerSources = Object.freeze({
839
955
  const state = {
840
956
  generatedNamespaces: new Map(),
841
957
  generatedSymbols: new Map(),
958
+ rejectionNamespaces: new Map(),
959
+ rejectionSymbols: new Map(),
842
960
  localTypeAliases: new Map(),
843
961
  serverNamespaces: new Set(),
844
962
  serverSymbols: new Map(),
@@ -861,18 +979,64 @@ const HandlerSources = Object.freeze({
861
979
  },
862
980
  recordImportDeclaration(statement, moduleSpecifier, source, program, state) {
863
981
  const bindings = statement.importClause?.namedBindings;
864
- if (bindings === undefined) {
982
+ if (bindings === undefined)
865
983
  return;
984
+ HandlerSources.recordFrameworkImport(bindings, moduleSpecifier, state);
985
+ if (HandlerSources.isGeneratedModule(moduleSpecifier)) {
986
+ HandlerSources.recordGeneratedImport(bindings, moduleSpecifier, statement.importClause?.phaseModifier !== ts.SyntaxKind.TypeKeyword, source, program, state);
866
987
  }
988
+ if (!HandlerSources.isRejectionCompanion(moduleSpecifier))
989
+ return;
990
+ HandlerSources.recordRejectionCompanionImport(bindings, moduleSpecifier, source, program, state);
991
+ },
992
+ recordFrameworkImport(bindings, moduleSpecifier, state) {
867
993
  if (moduleSpecifier === "@spine-event-engine/server") {
868
994
  HandlerSources.recordServerImport(bindings, state);
869
995
  }
870
996
  if (moduleSpecifier === "@spine-event-engine/proto") {
871
997
  HandlerSources.recordProtoImport(bindings, state);
872
998
  }
873
- if (HandlerSources.isGeneratedModule(moduleSpecifier)) {
874
- HandlerSources.recordGeneratedImport(bindings, moduleSpecifier, statement.importClause?.phaseModifier !== ts.SyntaxKind.TypeKeyword, source, program, state);
999
+ },
1000
+ recordRejectionCompanionImport(bindings, moduleSpecifier, source, program, state) {
1001
+ const schemaModule = HandlerSources.rejectionSchemaModule(moduleSpecifier);
1002
+ const companion = HandlerSources.generatedModuleSource(source, moduleSpecifier, program);
1003
+ const companionSchemaModule = companion === undefined ? undefined : HandlerSources.rejectionSchemaImport(companion);
1004
+ const exported = HandlerSources.generatedModuleExports(companion ?? source, companionSchemaModule ?? schemaModule, program);
1005
+ if (exported === undefined)
1006
+ return;
1007
+ if (ts.isNamespaceImport(bindings)) {
1008
+ state.rejectionNamespaces.set(bindings.name.text, {
1009
+ exports: exported,
1010
+ moduleSpecifier: schemaModule,
1011
+ });
1012
+ return;
1013
+ }
1014
+ for (const element of bindings.elements) {
1015
+ if (element.isTypeOnly)
1016
+ continue;
1017
+ const exportName = element.propertyName?.text ?? element.name.text;
1018
+ const schemaName = `${exportName}Schema`;
1019
+ if (exported.schemaRoles.get(schemaName) !== "rejection")
1020
+ continue;
1021
+ state.rejectionSymbols.set(element.name.text, {
1022
+ moduleSpecifier: schemaModule,
1023
+ exportName: schemaName,
1024
+ });
1025
+ }
1026
+ },
1027
+ rejectionSchemaImport(source) {
1028
+ for (const statement of source.statements) {
1029
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
1030
+ continue;
1031
+ }
1032
+ const bindings = statement.importClause?.namedBindings;
1033
+ if (bindings !== undefined &&
1034
+ !ts.isNamespaceImport(bindings) &&
1035
+ bindings.elements.some((element) => (element.propertyName?.text ?? element.name.text).endsWith("Schema"))) {
1036
+ return statement.moduleSpecifier.text;
1037
+ }
875
1038
  }
1039
+ return undefined;
876
1040
  },
877
1041
  recordServerImport(bindings, state) {
878
1042
  if (ts.isNamespaceImport(bindings)) {
@@ -926,6 +1090,14 @@ const HandlerSources = Object.freeze({
926
1090
  isGeneratedModule(moduleSpecifier) {
927
1091
  return /(^|\/)generated\/.+_pb(\.js)?$/.test(moduleSpecifier);
928
1092
  },
1093
+ isRejectionCompanion(moduleSpecifier) {
1094
+ return /(^|\/)generated\/(?:.+\/)?(?:[^/]+_)?rejections(\.js)?$/u.test(moduleSpecifier);
1095
+ },
1096
+ rejectionSchemaModule(moduleSpecifier) {
1097
+ return moduleSpecifier.endsWith(".js")
1098
+ ? moduleSpecifier.replace(/\.js$/u, "_pb.js")
1099
+ : `${moduleSpecifier}_pb`;
1100
+ },
929
1101
  generatedModuleExports(source, moduleSpecifier, program) {
930
1102
  const module = HandlerSources.generatedModuleSource(source, moduleSpecifier, program);
931
1103
  if (module === undefined)
@@ -1255,7 +1427,8 @@ const HandlerTypes = Object.freeze({
1255
1427
  name === "React" ||
1256
1428
  name === "Subscribe" ||
1257
1429
  name === "Apply" ||
1258
- name === "Where"
1430
+ name === "Where" ||
1431
+ name === "Throws"
1259
1432
  ? name
1260
1433
  : undefined;
1261
1434
  },