@apifuse/provider-sdk 2.2.0-beta.53 → 2.2.0-beta.55

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 (43) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cli/migrate-operation-declaration.d.ts +5 -1
  3. package/dist/cli/migrate-operation-declaration.js +645 -46
  4. package/dist/runtime/auth-flow.d.ts +1 -0
  5. package/dist/runtime/auth-flow.js +2 -0
  6. package/dist/runtime/trace.d.ts +7 -0
  7. package/dist/runtime/trace.js +24 -1
  8. package/dist/server/serve-implementation.d.ts +4 -0
  9. package/dist/server/serve-implementation.js +576 -321
  10. package/dist/types.d.ts +1 -0
  11. package/package.json +1 -1
  12. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +3 -0
  13. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +3 -0
  14. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +2 -0
  15. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +3 -0
  16. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +6 -0
  17. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +1 -0
  18. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +1 -0
  19. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +3 -0
  20. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +7 -0
  21. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +19 -0
  22. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +6 -0
  23. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +3 -0
  24. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +8 -0
  25. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +5 -0
  26. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +4 -0
  27. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +3 -0
  28. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +3 -0
  29. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +10 -0
  30. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +1 -0
  31. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +1 -0
  32. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +2 -0
  33. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +3 -0
  34. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +3 -0
  35. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +11 -0
  36. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +7 -0
  37. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +18 -0
  38. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +13 -0
  39. package/src/cli/migrate-operation-declaration.ts +844 -49
  40. package/src/runtime/auth-flow.ts +3 -0
  41. package/src/runtime/trace.ts +37 -1
  42. package/src/server/serve-implementation.ts +759 -586
  43. package/src/types.ts +1 -0
@@ -55,6 +55,9 @@ const SIDECAR_NAME = "migrate-operation-declaration.locales-todo.json";
55
55
  * copies every untouched source slice byte-for-byte.
56
56
  */
57
57
  export function migrateOperationDeclaration(sourceText, fileName, options = {}) {
58
+ return migrateOperationDeclarationInternal(sourceText, fileName, options);
59
+ }
60
+ function migrateOperationDeclarationInternal(sourceText, fileName, options, context = {}) {
58
61
  const source = parseSource(fileName, sourceText);
59
62
  const parseError = firstSyntaxError(source);
60
63
  if (parseError !== undefined) {
@@ -65,7 +68,7 @@ export function migrateOperationDeclaration(sourceText, fileName, options = {})
65
68
  }
66
69
  const constObjects = collectModuleConstObjects(source);
67
70
  const constArrays = collectModuleConstArrays(source);
68
- const discovery = discoverOperationSites(source, fileName, constObjects, options.operationIds);
71
+ const discovery = discoverOperationSites(source, fileName, constObjects, options.operationIds, context.operationSites, context.excludedBindings);
69
72
  if (discovery.refusals.length > 0) {
70
73
  return { status: "refused", refusals: discovery.refusals };
71
74
  }
@@ -73,7 +76,7 @@ export function migrateOperationDeclaration(sourceText, fileName, options = {})
73
76
  const todos = [];
74
77
  const refusals = [];
75
78
  for (const site of discovery.sites) {
76
- const plan = planOperationMigration(source, fileName, site, constObjects, constArrays, options.localeFiles ?? []);
79
+ const plan = planOperationMigration(source, fileName, site, constObjects, constArrays, options.localeFiles ?? [], context.staticObjectResolver);
77
80
  if ("refusal" in plan) {
78
81
  refusals.push(plan.refusal);
79
82
  continue;
@@ -120,13 +123,13 @@ export function verifyOperationDeclarationRewrite(code, fileName) {
120
123
  return undefined;
121
124
  return refusal(fileName, "<file>", "codemod_syntax", `Transform output did not parse: ${outputError}`);
122
125
  }
123
- function planOperationMigration(source, fileName, site, constObjects, constArrays, localeFiles) {
126
+ function planOperationMigration(source, fileName, site, constObjects, constArrays, localeFiles, staticObjectResolver) {
124
127
  if (site.object.properties.some(ts.isSpreadAssignment)) {
125
128
  return {
126
129
  refusal: refusal(fileName, site.operationKey, "non_literal", "Top-level operation spreads cannot be rewritten without changing their shared declaration."),
127
130
  };
128
131
  }
129
- const expanded = expandMembers(site.object, constObjects, fileName, site.operationKey);
132
+ const expanded = expandMembers(site.object, source, constObjects, fileName, site.operationKey, staticObjectResolver);
130
133
  if ("refusal" in expanded)
131
134
  return expanded;
132
135
  const top = indexMembers(expanded.members, fileName, site.operationKey);
@@ -142,13 +145,13 @@ function planOperationMigration(source, fileName, site, constObjects, constArray
142
145
  const member = top.byName.get(containerName);
143
146
  if (member === undefined)
144
147
  continue;
145
- const object = resolveObjectInitializer(member, constObjects);
146
- if (object === undefined) {
148
+ const resolvedObject = resolveObjectInitializer(member, source, constObjects, staticObjectResolver);
149
+ if (resolvedObject === undefined) {
147
150
  return {
148
151
  refusal: refusal(fileName, site.operationKey, "non_literal", `${containerName} must be an object literal or a module-level const object literal.`),
149
152
  };
150
153
  }
151
- const members = expandMembers(object, constObjects, fileName, site.operationKey);
154
+ const members = expandMembers(resolvedObject.object, resolvedObject.source, constObjects, fileName, site.operationKey, staticObjectResolver);
152
155
  if ("refusal" in members)
153
156
  return members;
154
157
  const indexed = indexMembers(members.members, fileName, site.operationKey);
@@ -196,24 +199,24 @@ function planOperationMigration(source, fileName, site, constObjects, constArray
196
199
  const sourceContainer = risk.source === "annotations" ? "annotations" : "toolRouter";
197
200
  addInsertion(insertions, sourceContainer, `riskClass: ${risk.text}`);
198
201
  }
199
- const timeout = mergeFlatAndNested("timeoutMs", top.byName.get("timeoutMs"), annotations.get("timeoutMs"), fileName, site.operationKey, source, "execution_conflict");
202
+ const timeout = mergeFlatAndNested("timeoutMs", top.byName.get("timeoutMs"), annotations.get("timeoutMs"), fileName, site.operationKey, "execution_conflict");
200
203
  if ("refusal" in timeout)
201
204
  return timeout;
202
205
  if (timeout.insert !== undefined)
203
206
  addInsertion(insertions, "annotations", timeout.insert);
204
- const connection = resolveConnectionMode(top.byName, toolRouter, fileName, site.operationKey, source);
207
+ const connection = resolveConnectionMode(top.byName, toolRouter, fileName, site.operationKey);
205
208
  if ("refusal" in connection)
206
209
  return connection;
207
210
  if (connection.insert !== undefined) {
208
211
  addInsertion(insertions, "toolRouter", connection.insert);
209
212
  }
210
- const externalRef = mergeFlatAndNested("connectionExternalRefParam", top.byName.get("connectionExternalRefParam"), toolRouter.get("connectionExternalRefParam"), fileName, site.operationKey, source, "connection_mode_conflict");
213
+ const externalRef = mergeFlatAndNested("connectionExternalRefParam", top.byName.get("connectionExternalRefParam"), toolRouter.get("connectionExternalRefParam"), fileName, site.operationKey, "connection_mode_conflict");
211
214
  if ("refusal" in externalRef)
212
215
  return externalRef;
213
216
  if (externalRef.insert !== undefined) {
214
217
  addInsertion(insertions, "toolRouter", externalRef.insert);
215
218
  }
216
- const approval = resolveApproval(top.byName.get("approval"), toolRouter.get("approval"), risk.value, fileName, site.operationKey, source);
219
+ const approval = resolveApproval(top.byName.get("approval"), toolRouter.get("approval"), risk.value, fileName, site.operationKey);
217
220
  if ("refusal" in approval)
218
221
  return approval;
219
222
  if (approval.removeTop !== undefined)
@@ -221,7 +224,7 @@ function planOperationMigration(source, fileName, site, constObjects, constArray
221
224
  if (approval.insert !== undefined)
222
225
  addInsertion(insertions, "toolRouter", approval.insert);
223
226
  for (const field of DOCS_FIELDS) {
224
- const merged = mergeFlatAndNested(field, top.byName.get(field), docs.get(field), fileName, site.operationKey, source, "locale_key_conflict");
227
+ const merged = mergeFlatAndNested(field, top.byName.get(field), docs.get(field), fileName, site.operationKey, "locale_key_conflict");
225
228
  if ("refusal" in merged)
226
229
  return merged;
227
230
  if (merged.insert !== undefined)
@@ -380,7 +383,7 @@ function resolveRiskClass(top, annotations, toolRouter, fileName, operationKey)
380
383
  refusal: refusal(fileName, operationKey, "no_safety", "No authored riskClass, annotations.destructive: true, or annotations.readOnly: true declaration exists; refusing to invent write."),
381
384
  };
382
385
  }
383
- function resolveConnectionMode(top, toolRouter, fileName, operationKey, source) {
386
+ function resolveConnectionMode(top, toolRouter, fileName, operationKey) {
384
387
  const flat = top.get("connectionMode");
385
388
  const nested = toolRouter.get("connectionMode");
386
389
  if (flat !== undefined && nested !== undefined) {
@@ -398,7 +401,7 @@ function resolveConnectionMode(top, toolRouter, fileName, operationKey, source)
398
401
  if (flat !== undefined)
399
402
  return {};
400
403
  if (nested !== undefined) {
401
- return { insert: memberText(nested, "connectionMode", source) };
404
+ return { insert: memberText(nested, "connectionMode") };
402
405
  }
403
406
  const required = toolRouter.get("requiresConnection");
404
407
  if (required === undefined)
@@ -408,7 +411,7 @@ function resolveConnectionMode(top, toolRouter, fileName, operationKey, source)
408
411
  return value;
409
412
  return { insert: `connectionMode: ${value.value ? '"required"' : '"none"'}` };
410
413
  }
411
- function resolveApproval(flat, nested, riskClass, fileName, operationKey, source) {
414
+ function resolveApproval(flat, nested, riskClass, fileName, operationKey) {
412
415
  let selected = flat ?? nested;
413
416
  if (flat !== undefined && nested !== undefined) {
414
417
  const same = equivalentLiteral(flat.initializer, nested.initializer);
@@ -434,7 +437,7 @@ function resolveApproval(flat, nested, riskClass, fileName, operationKey, source
434
437
  }
435
438
  if (flat !== undefined)
436
439
  return {};
437
- return { insert: memberText(selected, "approval", source) };
440
+ return { insert: memberText(selected, "approval") };
438
441
  }
439
442
  function defaultApprovalPolicy(riskClass) {
440
443
  if (riskClass === "read")
@@ -443,16 +446,22 @@ function defaultApprovalPolicy(riskClass) {
443
446
  return "risk-based";
444
447
  return "always";
445
448
  }
446
- function mergeFlatAndNested(field, flat, nested, fileName, operationKey, source, reason) {
449
+ function mergeFlatAndNested(field, flat, nested, fileName, operationKey, reason) {
447
450
  if (nested === undefined)
448
451
  return {};
449
452
  if (flat === undefined)
450
- return { insert: memberText(nested, field, source) };
453
+ return { insert: memberText(nested, field) };
451
454
  const same = equivalentLiteral(flat.initializer, nested.initializer);
452
455
  if (same === undefined) {
453
456
  return nonLiteral(fileName, operationKey, `${field} must be literal when both top-level and nested declarations exist.`);
454
457
  }
455
458
  if (!same) {
459
+ // A shared imported docs template is intentionally lower-precedence than
460
+ // an operation's explicit flat locale key. Keep direct-vs-direct
461
+ // conflicts fail-closed, but do not let a spread default replace the
462
+ // operation-specific key during flattening.
463
+ if (reason === "locale_key_conflict" && nested.fromSpread === true)
464
+ return {};
456
465
  return {
457
466
  refusal: refusal(fileName, operationKey, reason, `Top-level ${field} conflicts with nested ${field}.`),
458
467
  };
@@ -550,7 +559,9 @@ function planExamples(inputExamples, existingExamples, fileName, site, source, c
550
559
  }
551
560
  function resolveOperationLocaleNamespace(members, fileName, site) {
552
561
  const authoredNamespaces = new Set();
553
- for (const member of members) {
562
+ const directMembers = members.filter((member) => member !== undefined && member.fromSpread !== true);
563
+ const namespaceMembers = directMembers.length > 0 ? directMembers : members;
564
+ for (const member of namespaceMembers) {
554
565
  const localeKey = literalString(member?.initializer);
555
566
  if (localeKey === undefined)
556
567
  continue;
@@ -567,6 +578,11 @@ function resolveOperationLocaleNamespace(members, fileName, site) {
567
578
  const authored = authoredNamespaces.values().next().value;
568
579
  if (authored !== undefined)
569
580
  return { namespace: authored };
581
+ // planExamples proves the id before using this placeholder to construct a
582
+ // locale key. Avoid validating an unproven binding such as <anonymous> as
583
+ // though it were an authored operation id.
584
+ if (!site.operationIdProven)
585
+ return { namespace: site.operationKey };
570
586
  try {
571
587
  return { namespace: operationIdToLocaleNamespace(site.operationKey) };
572
588
  }
@@ -583,10 +599,13 @@ function replaceExampleLocaleMember(member, newName, localeKey, source) {
583
599
  text: `${newName}: ${JSON.stringify(localeKey)}`,
584
600
  };
585
601
  }
586
- function discoverOperationSites(source, fileName, constObjects, operationIds) {
602
+ function discoverOperationSites(source, fileName, constObjects, operationIds, operationSites, excludedBindings) {
587
603
  const sitesByStart = new Map();
588
604
  const localIds = new Map(operationIds ?? []);
605
+ const localExcludedBindings = new Set(excludedBindings ?? []);
589
606
  const refusals = [];
607
+ const operationFactories = collectSimpleOperationFactories(source);
608
+ const factoryCallIds = new Map();
590
609
  for (const map of collectOperationsMaps(source, constObjects, fileName, refusals)) {
591
610
  for (const property of map.properties) {
592
611
  if (ts.isSpreadAssignment(property))
@@ -615,6 +634,16 @@ function discoverOperationSites(source, fileName, constObjects, operationIds) {
615
634
  });
616
635
  continue;
617
636
  }
637
+ if (ts.isCallExpression(unwrapped) &&
638
+ ts.isIdentifier(unwrapped.expression) &&
639
+ operationFactories.has(unwrapped.expression.text) &&
640
+ !localExcludedBindings.has(unwrapped.expression.text)) {
641
+ const factoryName = unwrapped.expression.text;
642
+ const operationIdsForFactory = factoryCallIds.get(factoryName) ?? new Set();
643
+ operationIdsForFactory.add(key);
644
+ factoryCallIds.set(factoryName, operationIdsForFactory);
645
+ continue;
646
+ }
618
647
  if (ts.isObjectLiteralExpression(unwrapped)) {
619
648
  sitesByStart.set(unwrapped.getStart(source), {
620
649
  object: unwrapped,
@@ -624,10 +653,35 @@ function discoverOperationSites(source, fileName, constObjects, operationIds) {
624
653
  }
625
654
  }
626
655
  }
656
+ for (const [factoryName, operationIdsForFactory] of factoryCallIds) {
657
+ const ids = [...operationIdsForFactory];
658
+ if (ids.length === 1) {
659
+ const operationId = ids[0];
660
+ if (operationId !== undefined)
661
+ localIds.set(factoryName, operationId);
662
+ continue;
663
+ }
664
+ localExcludedBindings.add(factoryName);
665
+ refusals.push(refusal(fileName, factoryName, "factory_operation_id_ambiguous", `Factory ${factoryName} is registered under multiple operation ids: ${ids
666
+ .map((id) => JSON.stringify(id))
667
+ .join(", ")}. A shared operation body cannot own one examples locale namespace.`));
668
+ }
627
669
  const visit = (node) => {
670
+ if (ts.isObjectLiteralExpression(node)) {
671
+ const indexedOperationKey = operationSites?.get(node.getStart(source));
672
+ if (indexedOperationKey !== undefined) {
673
+ sitesByStart.set(node.getStart(source), {
674
+ object: node,
675
+ operationKey: indexedOperationKey,
676
+ operationIdProven: true,
677
+ });
678
+ }
679
+ }
628
680
  if (ts.isCallExpression(node) && isOperationHelperCall(node)) {
629
681
  const argument = operationArgument(node);
630
682
  const bindingName = enclosingBindingName(node);
683
+ if (bindingName !== undefined && localExcludedBindings.has(bindingName))
684
+ return;
631
685
  const operationKey = (bindingName === undefined ? undefined : localIds.get(bindingName)) ??
632
686
  bindingName ??
633
687
  "<anonymous>";
@@ -662,6 +716,31 @@ function discoverOperationSites(source, fileName, constObjects, operationIds) {
662
716
  refusals,
663
717
  };
664
718
  }
719
+ function collectSimpleOperationFactories(source) {
720
+ const factories = new Set();
721
+ for (const statement of source.statements) {
722
+ if (!ts.isFunctionDeclaration(statement) ||
723
+ statement.name === undefined ||
724
+ statement.body === undefined ||
725
+ statement.body.statements.length !== 1) {
726
+ continue;
727
+ }
728
+ const returned = statement.body.statements[0];
729
+ if (!ts.isReturnStatement(returned) || returned.expression === undefined)
730
+ continue;
731
+ const expression = unwrapExpression(returned.expression);
732
+ if (expression !== undefined &&
733
+ ts.isCallExpression(expression) &&
734
+ isOperationHelperCall(expression)) {
735
+ const argument = operationArgument(expression);
736
+ const object = argument === undefined ? undefined : unwrapExpression(argument);
737
+ if (object !== undefined && ts.isObjectLiteralExpression(object)) {
738
+ factories.add(statement.name.text);
739
+ }
740
+ }
741
+ }
742
+ return factories;
743
+ }
665
744
  function collectOperationsMaps(source, constObjects, fileName, refusals) {
666
745
  const maps = new Map();
667
746
  const inspect = (expression, label) => {
@@ -784,6 +863,9 @@ function enclosingBindingName(node) {
784
863
  if (ts.isVariableDeclaration(current) && ts.isIdentifier(current.name)) {
785
864
  return current.name.text;
786
865
  }
866
+ if (ts.isFunctionDeclaration(current) && current.name !== undefined) {
867
+ return current.name.text;
868
+ }
787
869
  if (ts.isPropertyAssignment(current))
788
870
  return staticPropertyName(current.name);
789
871
  if (ts.isExportAssignment(current))
@@ -855,7 +937,7 @@ function expandArrayElements(array, constArrays, fileName, operationKey, seen =
855
937
  }
856
938
  return { elements };
857
939
  }
858
- function expandMembers(object, constObjects, fileName, operationKey, seen = new Set()) {
940
+ function expandMembers(object, source, constObjects, fileName, operationKey, staticObjectResolver, seen = new Set()) {
859
941
  if (seen.has(object)) {
860
942
  return nonLiteral(fileName, operationKey, "A module-level object spread is recursive.");
861
943
  }
@@ -863,17 +945,12 @@ function expandMembers(object, constObjects, fileName, operationKey, seen = new
863
945
  const members = [];
864
946
  for (const property of object.properties) {
865
947
  if (ts.isSpreadAssignment(property)) {
866
- const expression = unwrapExpression(property.expression);
867
- const spreadObject = expression !== undefined && ts.isObjectLiteralExpression(expression)
868
- ? expression
869
- : expression !== undefined && ts.isIdentifier(expression)
870
- ? constObjects.get(expression.text)
871
- : undefined;
948
+ const spreadObject = resolveStaticObjectExpression(property.expression, source, constObjects, staticObjectResolver);
872
949
  if (spreadObject === undefined) {
873
- members.push({ name: "<spread>", property });
950
+ members.push({ name: "<spread>", property, source });
874
951
  continue;
875
952
  }
876
- const expanded = expandMembers(spreadObject, constObjects, fileName, operationKey, new Set(seen));
953
+ const expanded = expandMembers(spreadObject.object, spreadObject.source, constObjects, fileName, operationKey, staticObjectResolver, new Set(seen));
877
954
  if ("refusal" in expanded)
878
955
  return expanded;
879
956
  members.push(...expanded.members.map((member) => ({ ...member, fromSpread: true })));
@@ -884,7 +961,7 @@ function expandMembers(object, constObjects, fileName, operationKey, seen = new
884
961
  return nonLiteral(fileName, operationKey, "A declaration member uses a computed name.");
885
962
  }
886
963
  const initializer = propertyValue(property);
887
- members.push({ name, property, initializer });
964
+ members.push({ name, property, initializer, source });
888
965
  }
889
966
  return { members };
890
967
  }
@@ -915,21 +992,32 @@ function indexMembersFromObject(object, fileName, operationKey) {
915
992
  if (name === undefined || initializer === undefined) {
916
993
  return nonLiteral(fileName, operationKey, "Example members must be literal properties.");
917
994
  }
918
- members.push({ name, property, initializer });
995
+ members.push({ name, property, initializer, source: object.getSourceFile() });
919
996
  }
920
997
  const indexed = indexMembers(members, fileName, operationKey);
921
998
  if ("refusal" in indexed)
922
999
  return indexed;
923
1000
  return { byName: indexed.byName };
924
1001
  }
925
- function resolveObjectInitializer(member, constObjects) {
926
- const initializer = unwrapExpression(member.initializer);
927
- if (initializer === undefined)
1002
+ function resolveObjectInitializer(member, rootSource, constObjects, staticObjectResolver) {
1003
+ if (member.initializer === undefined)
928
1004
  return undefined;
929
- if (ts.isObjectLiteralExpression(initializer))
930
- return initializer;
931
- if (ts.isIdentifier(initializer))
932
- return constObjects.get(initializer.text);
1005
+ return resolveStaticObjectExpression(member.initializer, member.source, constObjects, staticObjectResolver, rootSource);
1006
+ }
1007
+ function resolveStaticObjectExpression(expression, source, constObjects, staticObjectResolver, rootSource = source) {
1008
+ const unwrapped = unwrapExpression(expression);
1009
+ if (unwrapped === undefined)
1010
+ return undefined;
1011
+ if (ts.isObjectLiteralExpression(unwrapped))
1012
+ return { object: unwrapped, source };
1013
+ const externallyResolved = staticObjectResolver?.(unwrapped, source);
1014
+ if (externallyResolved !== undefined)
1015
+ return externallyResolved;
1016
+ if (source === rootSource && ts.isIdentifier(unwrapped)) {
1017
+ const object = constObjects.get(unwrapped.text);
1018
+ if (object !== undefined)
1019
+ return { object, source };
1020
+ }
933
1021
  return undefined;
934
1022
  }
935
1023
  function memberMap(members) {
@@ -1020,10 +1108,10 @@ function literalValue(expression) {
1020
1108
  }
1021
1109
  return NOT_LITERAL;
1022
1110
  }
1023
- function memberText(member, name, source) {
1111
+ function memberText(member, name) {
1024
1112
  if (member.initializer === undefined)
1025
1113
  return name;
1026
- return `${name}: ${member.initializer.getText(source)}`;
1114
+ return `${name}: ${member.initializer.getText(member.source)}`;
1027
1115
  }
1028
1116
  function addInsertion(insertions, sourceContainer, text) {
1029
1117
  const items = insertions.get(sourceContainer) ?? [];
@@ -1180,17 +1268,21 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
1180
1268
  const providerRoot = resolve(providerRootInput);
1181
1269
  const sourceFiles = collectSourceFiles(providerRoot);
1182
1270
  const localeFiles = collectLocaleFiles(providerRoot);
1183
- const operationIds = buildOperationIdIndex(sourceFiles);
1271
+ const repositoryIndex = buildRepositoryOperationIndex(sourceFiles, providerRoot);
1184
1272
  const pendingWrites = new Map();
1185
1273
  const changedFiles = [];
1186
1274
  const todos = [];
1187
- const refusals = [];
1275
+ const refusals = [...repositoryIndex.refusals];
1188
1276
  let operationCount = 0;
1189
1277
  for (const sourcePath of sourceFiles) {
1190
1278
  const relativePath = slash(relative(providerRoot, sourcePath));
1191
- const result = migrateOperationDeclaration(readFileSync(sourcePath, "utf8"), relativePath, {
1192
- operationIds: operationIds.get(sourcePath),
1279
+ const result = migrateOperationDeclarationInternal(readFileSync(sourcePath, "utf8"), relativePath, {
1280
+ operationIds: repositoryIndex.operationIds.get(sourcePath),
1193
1281
  localeFiles,
1282
+ }, {
1283
+ operationSites: repositoryIndex.operationSites.get(sourcePath),
1284
+ excludedBindings: repositoryIndex.excludedBindings.get(sourcePath),
1285
+ staticObjectResolver: repositoryIndex.staticObjectResolverFor(sourcePath),
1194
1286
  });
1195
1287
  if (result.status === "refused") {
1196
1288
  refusals.push(...result.refusals);
@@ -1203,11 +1295,23 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
1203
1295
  todos.push(...result.localeTodos);
1204
1296
  }
1205
1297
  }
1298
+ if (repositoryIndex.declarations.length > 0 && repositoryIndex.discoveredCount === 0) {
1299
+ for (const declaration of repositoryIndex.declarations) {
1300
+ refusals.push(refusal(slash(relative(providerRoot, declaration.path)), "<operations>", "no_operations_discovered", `Provider construct ${declaration.construct} declares operations via unresolved initializer ${JSON.stringify(declaration.initializer.getText(declaration.initializer.getSourceFile()))}, but repository-wide discovery found zero operation sites.`));
1301
+ }
1302
+ }
1206
1303
  const todoConflict = findLocaleTodoConflict(todos);
1207
1304
  if (todoConflict !== undefined)
1208
1305
  refusals.push(todoConflict);
1209
1306
  if (refusals.length > 0) {
1210
- return { status: "refused", providerRoot, refusals };
1307
+ return {
1308
+ status: "refused",
1309
+ providerRoot,
1310
+ refusals,
1311
+ operationCount,
1312
+ changedFiles,
1313
+ localeTodoCount: todos.length,
1314
+ };
1211
1315
  }
1212
1316
  for (const [path, code] of renderLocaleCatalogWrites(providerRoot, todos)) {
1213
1317
  pendingWrites.set(path, code);
@@ -1375,6 +1479,496 @@ function collectLocaleFiles(root) {
1375
1479
  .map((entry) => `locales/${entry.name}`)
1376
1480
  .sort();
1377
1481
  }
1482
+ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
1483
+ const sources = new Map();
1484
+ for (const path of sourceFiles) {
1485
+ const source = parseSource(path, readFileSync(path, "utf8"));
1486
+ if (firstSyntaxError(source) === undefined)
1487
+ sources.set(path, source);
1488
+ }
1489
+ const operationIds = buildOperationIdIndex(sourceFiles);
1490
+ const operationSites = new Map();
1491
+ const excludedBindings = new Map();
1492
+ const refusals = [];
1493
+ const declarations = [];
1494
+ const indexedProviderProperties = new Set();
1495
+ const factoryIds = new Map();
1496
+ const bindingCandidates = new Map();
1497
+ let discoveredCount = 0;
1498
+ const relativePath = (path) => slash(relative(providerRoot, path));
1499
+ const resolutionRefusal = (path, operationKey, detail) => {
1500
+ refusals.push(refusal(relativePath(path), operationKey, "non_literal", detail));
1501
+ };
1502
+ const resolveExport = (path, exportedName, active) => {
1503
+ const key = `${path}\0export\0${exportedName}`;
1504
+ if (active.has(key)) {
1505
+ return {
1506
+ status: "refused",
1507
+ detail: `Static relative export cycle while resolving ${JSON.stringify(exportedName)} from ${relativePath(path)}.`,
1508
+ };
1509
+ }
1510
+ const source = sources.get(path);
1511
+ if (source === undefined)
1512
+ return { status: "missing" };
1513
+ const nextActive = new Set(active);
1514
+ nextActive.add(key);
1515
+ const explicit = [];
1516
+ const exportStars = [];
1517
+ for (const statement of source.statements) {
1518
+ if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
1519
+ for (const declaration of statement.declarationList.declarations) {
1520
+ if (ts.isIdentifier(declaration.name) &&
1521
+ declaration.name.text === exportedName &&
1522
+ declaration.initializer !== undefined) {
1523
+ const initializer = declaration.initializer;
1524
+ const bindingName = declaration.name.text;
1525
+ explicit.push(() => ({
1526
+ status: "resolved",
1527
+ value: {
1528
+ path,
1529
+ source,
1530
+ expression: initializer,
1531
+ bindingName,
1532
+ },
1533
+ }));
1534
+ }
1535
+ }
1536
+ }
1537
+ if (ts.isExportAssignment(statement) &&
1538
+ !statement.isExportEquals &&
1539
+ exportedName === "default") {
1540
+ explicit.push(() => ({
1541
+ status: "resolved",
1542
+ value: { path, source, expression: statement.expression },
1543
+ }));
1544
+ }
1545
+ if (!ts.isExportDeclaration(statement))
1546
+ continue;
1547
+ const specifier = statement.moduleSpecifier !== undefined && ts.isStringLiteral(statement.moduleSpecifier)
1548
+ ? statement.moduleSpecifier.text
1549
+ : undefined;
1550
+ if (statement.exportClause === undefined) {
1551
+ if (specifier !== undefined)
1552
+ exportStars.push(specifier);
1553
+ continue;
1554
+ }
1555
+ if (!ts.isNamedExports(statement.exportClause))
1556
+ continue;
1557
+ for (const element of statement.exportClause.elements) {
1558
+ if (element.name.text !== exportedName)
1559
+ continue;
1560
+ const localName = element.propertyName?.text ?? element.name.text;
1561
+ explicit.push(() => {
1562
+ if (specifier === undefined) {
1563
+ return resolveIdentifier(path, localName, nextActive);
1564
+ }
1565
+ const target = resolveStaticModule(path, specifier, sources);
1566
+ if (target.status !== "resolved")
1567
+ return target;
1568
+ return resolveExport(target.path, localName, nextActive);
1569
+ });
1570
+ }
1571
+ }
1572
+ if (explicit.length > 1) {
1573
+ return {
1574
+ status: "refused",
1575
+ detail: `Export ${JSON.stringify(exportedName)} is ambiguous in ${relativePath(path)}.`,
1576
+ };
1577
+ }
1578
+ if (explicit.length === 1)
1579
+ return explicit[0]?.() ?? { status: "missing" };
1580
+ const resolvedStars = [];
1581
+ let firstStarRefusal;
1582
+ for (const specifier of exportStars) {
1583
+ const target = resolveStaticModule(path, specifier, sources);
1584
+ if (target.status !== "resolved") {
1585
+ if (target.status === "refused")
1586
+ firstStarRefusal ??= target;
1587
+ continue;
1588
+ }
1589
+ const candidate = resolveExport(target.path, exportedName, nextActive);
1590
+ if (candidate.status === "resolved")
1591
+ resolvedStars.push(candidate.value);
1592
+ else if (candidate.status === "refused")
1593
+ firstStarRefusal ??= candidate;
1594
+ }
1595
+ const origins = new Set(resolvedStars.map((item) => `${item.path}\0${item.expression.getStart(item.source)}`));
1596
+ if (origins.size > 1) {
1597
+ return {
1598
+ status: "refused",
1599
+ detail: `Export ${JSON.stringify(exportedName)} is ambiguous across relative re-exports from ${relativePath(path)}.`,
1600
+ };
1601
+ }
1602
+ if (firstStarRefusal !== undefined)
1603
+ return firstStarRefusal;
1604
+ const resolved = resolvedStars[0];
1605
+ return resolved === undefined ? { status: "missing" } : { status: "resolved", value: resolved };
1606
+ };
1607
+ const resolveIdentifier = (path, name, active) => {
1608
+ const source = sources.get(path);
1609
+ if (source === undefined)
1610
+ return { status: "missing" };
1611
+ for (const statement of source.statements) {
1612
+ if (!ts.isVariableStatement(statement))
1613
+ continue;
1614
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0)
1615
+ continue;
1616
+ for (const declaration of statement.declarationList.declarations) {
1617
+ if (ts.isIdentifier(declaration.name) &&
1618
+ declaration.name.text === name &&
1619
+ declaration.initializer !== undefined) {
1620
+ return {
1621
+ status: "resolved",
1622
+ value: {
1623
+ path,
1624
+ source,
1625
+ expression: declaration.initializer,
1626
+ bindingName: name,
1627
+ },
1628
+ };
1629
+ }
1630
+ }
1631
+ }
1632
+ for (const statement of source.statements) {
1633
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
1634
+ continue;
1635
+ }
1636
+ let exportedName;
1637
+ if (statement.importClause?.name?.text === name)
1638
+ exportedName = "default";
1639
+ const bindings = statement.importClause?.namedBindings;
1640
+ if (bindings !== undefined && ts.isNamedImports(bindings)) {
1641
+ for (const element of bindings.elements) {
1642
+ if (element.name.text === name) {
1643
+ exportedName = element.propertyName?.text ?? element.name.text;
1644
+ }
1645
+ }
1646
+ }
1647
+ if (exportedName === undefined)
1648
+ continue;
1649
+ const specifier = statement.moduleSpecifier.text;
1650
+ const target = resolveStaticModule(path, specifier, sources);
1651
+ if (target.status !== "resolved")
1652
+ return target;
1653
+ return resolveExport(target.path, exportedName, active);
1654
+ }
1655
+ return { status: "missing" };
1656
+ };
1657
+ const resolveLocatedExpression = (located, active) => {
1658
+ const expression = unwrapExpression(located.expression);
1659
+ if (expression === undefined)
1660
+ return { status: "missing" };
1661
+ if (!ts.isIdentifier(expression)) {
1662
+ return { status: "resolved", value: { ...located, expression } };
1663
+ }
1664
+ const key = `${located.path}\0identifier\0${expression.text}`;
1665
+ if (active.has(key)) {
1666
+ return {
1667
+ status: "refused",
1668
+ detail: `Static identifier cycle while resolving ${JSON.stringify(expression.text)} in ${relativePath(located.path)}.`,
1669
+ };
1670
+ }
1671
+ const nextActive = new Set(active);
1672
+ nextActive.add(key);
1673
+ const resolved = resolveIdentifier(located.path, expression.text, nextActive);
1674
+ return resolved.status === "resolved"
1675
+ ? resolveLocatedExpression(resolved.value, nextActive)
1676
+ : resolved;
1677
+ };
1678
+ const flattenRegistry = (locatedInput, active) => {
1679
+ const resolved = resolveLocatedExpression(locatedInput, active);
1680
+ if (resolved.status === "refused")
1681
+ return { detail: resolved.detail };
1682
+ if (resolved.status === "missing") {
1683
+ return {
1684
+ detail: `Operations initializer ${JSON.stringify(locatedInput.expression.getText(locatedInput.source))} is not a static local or relative-imported object.`,
1685
+ };
1686
+ }
1687
+ const expression = unwrapExpression(resolved.value.expression);
1688
+ if (expression === undefined || !ts.isObjectLiteralExpression(expression)) {
1689
+ return {
1690
+ detail: `Operations initializer ${JSON.stringify(resolved.value.expression.getText(resolved.value.source))} is not a static object literal.`,
1691
+ };
1692
+ }
1693
+ const cycleKey = `${resolved.value.path}\0object\0${expression.getStart(resolved.value.source)}`;
1694
+ if (active.has(cycleKey)) {
1695
+ return {
1696
+ detail: `Static operations registry spread cycle reaches ${relativePath(resolved.value.path)}.`,
1697
+ };
1698
+ }
1699
+ const nextActive = new Set(active);
1700
+ nextActive.add(cycleKey);
1701
+ const entries = new Map();
1702
+ for (const property of expression.properties) {
1703
+ if (ts.isSpreadAssignment(property)) {
1704
+ const spread = flattenRegistry({
1705
+ path: resolved.value.path,
1706
+ source: resolved.value.source,
1707
+ expression: property.expression,
1708
+ }, nextActive);
1709
+ if ("detail" in spread)
1710
+ return spread;
1711
+ for (const [operationId, entry] of spread.entries)
1712
+ entries.set(operationId, entry);
1713
+ continue;
1714
+ }
1715
+ const operationId = staticPropertyName(property.name);
1716
+ const value = propertyValue(property);
1717
+ if (operationId === undefined) {
1718
+ return {
1719
+ detail: `Operations registry in ${relativePath(resolved.value.path)} uses a computed key.`,
1720
+ };
1721
+ }
1722
+ if (value === undefined) {
1723
+ return { detail: `Operation ${JSON.stringify(operationId)} has a non-static initializer.` };
1724
+ }
1725
+ entries.set(operationId, {
1726
+ operationId,
1727
+ value: {
1728
+ path: resolved.value.path,
1729
+ source: resolved.value.source,
1730
+ expression: value,
1731
+ },
1732
+ });
1733
+ }
1734
+ return { entries };
1735
+ };
1736
+ const addBindingCandidate = (path, name, operationId) => {
1737
+ const key = `${path}\0${name}`;
1738
+ const candidate = bindingCandidates.get(key) ?? { path, name, ids: new Set() };
1739
+ candidate.ids.add(operationId);
1740
+ bindingCandidates.set(key, candidate);
1741
+ };
1742
+ const addObjectSite = (path, source, object, operationId) => {
1743
+ const sites = operationSites.get(path) ?? new Map();
1744
+ const start = object.getStart(source);
1745
+ const previous = sites.get(start);
1746
+ if (previous !== undefined && previous !== operationId) {
1747
+ resolutionRefusal(path, operationId, `One static operation object is registered under both ${JSON.stringify(previous)} and ${JSON.stringify(operationId)}.`);
1748
+ return;
1749
+ }
1750
+ sites.set(start, operationId);
1751
+ operationSites.set(path, sites);
1752
+ discoveredCount += 1;
1753
+ };
1754
+ const classifyEntry = (entry) => {
1755
+ const resolved = resolveLocatedExpression(entry.value, new Set());
1756
+ if (resolved.status === "refused") {
1757
+ resolutionRefusal(entry.value.path, entry.operationId, resolved.detail);
1758
+ return;
1759
+ }
1760
+ if (resolved.status === "missing") {
1761
+ resolutionRefusal(entry.value.path, entry.operationId, `Operation initializer ${JSON.stringify(entry.value.expression.getText(entry.value.source))} is not statically resolvable.`);
1762
+ return;
1763
+ }
1764
+ const expression = unwrapExpression(resolved.value.expression);
1765
+ if (expression === undefined)
1766
+ return;
1767
+ if (ts.isObjectLiteralExpression(expression)) {
1768
+ addObjectSite(resolved.value.path, resolved.value.source, expression, entry.operationId);
1769
+ return;
1770
+ }
1771
+ if (ts.isCallExpression(expression) && isOperationHelperCall(expression)) {
1772
+ const argument = operationArgument(expression);
1773
+ const object = argument === undefined ? undefined : unwrapExpression(argument);
1774
+ if (object === undefined || !ts.isObjectLiteralExpression(object)) {
1775
+ resolutionRefusal(resolved.value.path, entry.operationId, "Operation helper argument must be a static object literal.");
1776
+ return;
1777
+ }
1778
+ if (resolved.value.bindingName !== undefined) {
1779
+ addBindingCandidate(resolved.value.path, resolved.value.bindingName, entry.operationId);
1780
+ }
1781
+ addObjectSite(resolved.value.path, resolved.value.source, object, entry.operationId);
1782
+ return;
1783
+ }
1784
+ if (ts.isCallExpression(expression) &&
1785
+ ts.isIdentifier(expression.expression) &&
1786
+ collectSimpleOperationFactories(resolved.value.source).has(expression.expression.text)) {
1787
+ const name = expression.expression.text;
1788
+ const key = `${resolved.value.path}\0${name}`;
1789
+ const factory = factoryIds.get(key) ?? {
1790
+ path: resolved.value.path,
1791
+ name,
1792
+ ids: new Set(),
1793
+ };
1794
+ factory.ids.add(entry.operationId);
1795
+ factoryIds.set(key, factory);
1796
+ discoveredCount += 1;
1797
+ return;
1798
+ }
1799
+ resolutionRefusal(resolved.value.path, entry.operationId, `Operation initializer ${JSON.stringify(expression.getText(resolved.value.source))} is not a raw object, operation helper, or simple same-file factory call.`);
1800
+ };
1801
+ const inspectProviderProperty = (path, source, node, construct) => {
1802
+ const key = `${path}\0${node.getStart(source)}`;
1803
+ if (indexedProviderProperties.has(key))
1804
+ return;
1805
+ indexedProviderProperties.add(key);
1806
+ declarations.push({ path, construct, initializer: node.initializer });
1807
+ const flattened = flattenRegistry({ path, source, expression: node.initializer }, new Set());
1808
+ if ("detail" in flattened) {
1809
+ resolutionRefusal(path, "<operations>", flattened.detail);
1810
+ }
1811
+ else {
1812
+ for (const entry of flattened.entries.values())
1813
+ classifyEntry(entry);
1814
+ }
1815
+ };
1816
+ for (const [path, source] of sources) {
1817
+ const constObjects = collectModuleConstObjects(source);
1818
+ const visit = (node) => {
1819
+ if (ts.isPropertyAssignment(node) &&
1820
+ staticPropertyName(node.name) === "operations" &&
1821
+ isProviderOperationsProperty(node, constObjects)) {
1822
+ inspectProviderProperty(path, source, node, providerConstructName(node));
1823
+ }
1824
+ if (ts.isCallExpression(node)) {
1825
+ const construct = providerCallConstruct(node);
1826
+ if (construct !== undefined) {
1827
+ for (const argument of node.arguments) {
1828
+ const resolved = resolveLocatedExpression({ path, source, expression: argument }, new Set());
1829
+ if (resolved.status !== "resolved")
1830
+ continue;
1831
+ const object = unwrapExpression(resolved.value.expression);
1832
+ if (object === undefined || !ts.isObjectLiteralExpression(object))
1833
+ continue;
1834
+ for (const property of object.properties) {
1835
+ if (ts.isPropertyAssignment(property) &&
1836
+ staticPropertyName(property.name) === "operations") {
1837
+ inspectProviderProperty(resolved.value.path, resolved.value.source, property, construct);
1838
+ }
1839
+ }
1840
+ }
1841
+ }
1842
+ }
1843
+ ts.forEachChild(node, visit);
1844
+ };
1845
+ visit(source);
1846
+ }
1847
+ for (const candidate of bindingCandidates.values()) {
1848
+ const ids = [...candidate.ids];
1849
+ const indexed = operationIds.get(candidate.path) ?? new Map();
1850
+ if (ids.length === 1) {
1851
+ const operationId = ids[0];
1852
+ if (operationId !== undefined)
1853
+ indexed.set(candidate.name, operationId);
1854
+ }
1855
+ else {
1856
+ indexed.delete(candidate.name);
1857
+ const excluded = excludedBindings.get(candidate.path) ?? new Set();
1858
+ excluded.add(candidate.name);
1859
+ excludedBindings.set(candidate.path, excluded);
1860
+ }
1861
+ operationIds.set(candidate.path, indexed);
1862
+ }
1863
+ for (const factory of factoryIds.values()) {
1864
+ const indexed = operationIds.get(factory.path) ?? new Map();
1865
+ const ids = [...factory.ids];
1866
+ if (ids.length === 1) {
1867
+ const operationId = ids[0];
1868
+ if (operationId !== undefined)
1869
+ indexed.set(factory.name, operationId);
1870
+ }
1871
+ else {
1872
+ indexed.delete(factory.name);
1873
+ const excluded = excludedBindings.get(factory.path) ?? new Set();
1874
+ excluded.add(factory.name);
1875
+ excludedBindings.set(factory.path, excluded);
1876
+ refusals.push(refusal(relativePath(factory.path), factory.name, "factory_operation_id_ambiguous", `Factory ${factory.name} is registered under multiple operation ids: ${ids
1877
+ .map((id) => JSON.stringify(id))
1878
+ .join(", ")}. A shared operation body cannot own one examples locale namespace.`));
1879
+ }
1880
+ operationIds.set(factory.path, indexed);
1881
+ }
1882
+ const resolveStaticObject = (expressionInput, path, active = new Set()) => {
1883
+ const expression = unwrapExpression(expressionInput);
1884
+ if (expression === undefined)
1885
+ return undefined;
1886
+ if (ts.isObjectLiteralExpression(expression)) {
1887
+ const source = sources.get(path) ?? expression.getSourceFile();
1888
+ return { object: expression, source };
1889
+ }
1890
+ if (ts.isIdentifier(expression)) {
1891
+ const key = `${path}\0member\0${expression.text}`;
1892
+ if (active.has(key))
1893
+ return undefined;
1894
+ const nextActive = new Set(active);
1895
+ nextActive.add(key);
1896
+ const resolved = resolveIdentifier(path, expression.text, nextActive);
1897
+ return resolved.status === "resolved"
1898
+ ? resolveStaticObject(resolved.value.expression, resolved.value.path, nextActive)
1899
+ : undefined;
1900
+ }
1901
+ if (ts.isPropertyAccessExpression(expression)) {
1902
+ const owner = resolveStaticObject(expression.expression, path, active);
1903
+ if (owner === undefined)
1904
+ return undefined;
1905
+ for (const property of owner.object.properties) {
1906
+ if (ts.isSpreadAssignment(property))
1907
+ continue;
1908
+ if (staticPropertyName(property.name) !== expression.name.text)
1909
+ continue;
1910
+ const value = propertyValue(property);
1911
+ if (value === undefined)
1912
+ return undefined;
1913
+ return resolveStaticObject(value, owner.source.fileName, active);
1914
+ }
1915
+ }
1916
+ return undefined;
1917
+ };
1918
+ return {
1919
+ operationIds,
1920
+ operationSites,
1921
+ excludedBindings,
1922
+ refusals,
1923
+ declarations,
1924
+ discoveredCount,
1925
+ staticObjectResolverFor: (currentPath) => (expression, source) => resolveStaticObject(expression, sources.has(source.fileName) ? source.fileName : currentPath),
1926
+ };
1927
+ }
1928
+ function hasExportModifier(node) {
1929
+ return (ts.canHaveModifiers(node) &&
1930
+ ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ===
1931
+ true);
1932
+ }
1933
+ function resolveStaticModule(containingPath, specifier, sources) {
1934
+ if (!specifier.startsWith(".")) {
1935
+ return {
1936
+ status: "refused",
1937
+ detail: `Static operation discovery refuses non-relative import ${JSON.stringify(specifier)}.`,
1938
+ };
1939
+ }
1940
+ const path = resolveLocalModule(containingPath, specifier, sources);
1941
+ if (path === undefined) {
1942
+ return {
1943
+ status: "refused",
1944
+ detail: `Static relative import ${JSON.stringify(specifier)} from ${containingPath} could not be resolved.`,
1945
+ };
1946
+ }
1947
+ return { status: "resolved", path };
1948
+ }
1949
+ function providerConstructName(node) {
1950
+ let current = node;
1951
+ while (current.parent !== undefined) {
1952
+ const parent = current.parent;
1953
+ if (ts.isCallExpression(parent)) {
1954
+ return providerCallConstruct(parent) ?? parent.expression.getText();
1955
+ }
1956
+ current = parent;
1957
+ }
1958
+ return "provider declaration";
1959
+ }
1960
+ function providerCallConstruct(call) {
1961
+ if (ts.isIdentifier(call.expression) &&
1962
+ /(?:defineProvider|Provider)\b/.test(call.expression.text)) {
1963
+ return call.expression.text;
1964
+ }
1965
+ if (ts.isCallExpression(call.expression) &&
1966
+ ts.isIdentifier(call.expression.expression) &&
1967
+ /\bdefineProvider\b/.test(call.expression.expression.text)) {
1968
+ return call.expression.expression.text;
1969
+ }
1970
+ return undefined;
1971
+ }
1378
1972
  function buildOperationIdIndex(sourceFiles) {
1379
1973
  const result = new Map();
1380
1974
  const idsByPath = new Map();
@@ -1556,7 +2150,12 @@ function resolveLocalModule(containingPath, specifier, sources) {
1556
2150
  if (!specifier.startsWith("."))
1557
2151
  return undefined;
1558
2152
  const base = resolve(dirname(containingPath), specifier);
1559
- for (const candidate of [base, `${base}.ts`, join(base, "index.ts")]) {
2153
+ const emittedExtensionSource = /\.(?:c|m)?js$/.test(base)
2154
+ ? base.replace(/\.(?:c|m)?js$/, ".ts")
2155
+ : undefined;
2156
+ for (const candidate of [base, emittedExtensionSource, `${base}.ts`, join(base, "index.ts")]) {
2157
+ if (candidate === undefined)
2158
+ continue;
1560
2159
  if (sources.has(candidate))
1561
2160
  return candidate;
1562
2161
  }