@formspec/build 0.1.0-alpha.44 → 0.1.0-alpha.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  2. package/dist/analyzer/program.d.ts +4 -1
  3. package/dist/analyzer/program.d.ts.map +1 -1
  4. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  5. package/dist/browser.cjs +33 -7
  6. package/dist/browser.cjs.map +1 -1
  7. package/dist/browser.js +34 -10
  8. package/dist/browser.js.map +1 -1
  9. package/dist/build-alpha.d.ts +106 -5
  10. package/dist/build-beta.d.ts +106 -5
  11. package/dist/build-internal.d.ts +106 -5
  12. package/dist/build.d.ts +106 -5
  13. package/dist/cli.cjs +324 -71
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +320 -70
  16. package/dist/cli.js.map +1 -1
  17. package/dist/extensions/index.d.ts +1 -1
  18. package/dist/extensions/index.d.ts.map +1 -1
  19. package/dist/extensions/registry.d.ts +64 -5
  20. package/dist/extensions/registry.d.ts.map +1 -1
  21. package/dist/extensions/symbol-registry.d.ts +33 -0
  22. package/dist/extensions/symbol-registry.d.ts.map +1 -0
  23. package/dist/generators/class-schema.d.ts +16 -0
  24. package/dist/generators/class-schema.d.ts.map +1 -1
  25. package/dist/generators/discovered-schema.d.ts.map +1 -1
  26. package/dist/index.cjs +310 -64
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +311 -67
  31. package/dist/index.js.map +1 -1
  32. package/dist/internals.cjs +106 -23
  33. package/dist/internals.cjs.map +1 -1
  34. package/dist/internals.d.ts +1 -1
  35. package/dist/internals.d.ts.map +1 -1
  36. package/dist/internals.js +107 -26
  37. package/dist/internals.js.map +1 -1
  38. package/dist/json-schema/generator.d.ts.map +1 -1
  39. package/dist/metadata/collision-guards.d.ts.map +1 -1
  40. package/dist/metadata/policy.d.ts.map +1 -1
  41. package/dist/metadata/resolve.d.ts.map +1 -1
  42. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  43. package/dist/validate/constraint-validator.d.ts.map +1 -1
  44. package/package.json +5 -5
package/dist/cli.js CHANGED
@@ -243,7 +243,9 @@ function resolveTypeNodeMetadata(type, options) {
243
243
  case "object":
244
244
  return {
245
245
  ...type,
246
- properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
246
+ properties: type.properties.map(
247
+ (property) => resolveObjectPropertyMetadata(property, options)
248
+ )
247
249
  };
248
250
  case "record":
249
251
  return {
@@ -1721,10 +1723,7 @@ function generateJsonSchema(form, options) {
1721
1723
  const metadata = options?.metadata;
1722
1724
  const vendorPrefix = options?.vendorPrefix;
1723
1725
  const enumSerialization = options?.enumSerialization;
1724
- const ir = canonicalizeChainDSL(
1725
- form,
1726
- metadata !== void 0 ? { metadata } : void 0
1727
- );
1726
+ const ir = canonicalizeChainDSL(form, metadata !== void 0 ? { metadata } : void 0);
1728
1727
  const internalOptions = vendorPrefix === void 0 && enumSerialization === void 0 ? void 0 : {
1729
1728
  ...vendorPrefix !== void 0 && { vendorPrefix },
1730
1729
  ...enumSerialization !== void 0 && { enumSerialization }
@@ -1953,7 +1952,10 @@ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
1953
1952
  break;
1954
1953
  }
1955
1954
  case "conditional": {
1956
- const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
1955
+ const newRule = createShowRule(
1956
+ fieldNameMap.get(element.fieldName) ?? element.fieldName,
1957
+ element.value
1958
+ );
1957
1959
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
1958
1960
  const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
1959
1961
  result.push(...childElements);
@@ -2044,8 +2046,10 @@ function buildConstraintTagSources(extensions) {
2044
2046
  }
2045
2047
  function createExtensionRegistry(extensions) {
2046
2048
  const reservedTagSources = buildConstraintTagSources(extensions);
2049
+ let symbolMap = /* @__PURE__ */ new Map();
2047
2050
  const typeMap = /* @__PURE__ */ new Map();
2048
2051
  const typeNameMap = /* @__PURE__ */ new Map();
2052
+ const brandMap = /* @__PURE__ */ new Map();
2049
2053
  const constraintMap = /* @__PURE__ */ new Map();
2050
2054
  const constraintTagMap = /* @__PURE__ */ new Map();
2051
2055
  const builtinBroadeningMap = /* @__PURE__ */ new Map();
@@ -2069,6 +2073,20 @@ function createExtensionRegistry(extensions) {
2069
2073
  registration: type
2070
2074
  });
2071
2075
  }
2076
+ if (type.brand !== void 0) {
2077
+ if (type.brand === "__integerBrand") {
2078
+ throw new Error(
2079
+ `Brand "__integerBrand" is reserved for the builtin Integer type and cannot be registered by extensions`
2080
+ );
2081
+ }
2082
+ if (brandMap.has(type.brand)) {
2083
+ throw new Error(`Duplicate custom type brand: "${type.brand}"`);
2084
+ }
2085
+ brandMap.set(type.brand, {
2086
+ extensionId: ext.extensionId,
2087
+ registration: type
2088
+ });
2089
+ }
2072
2090
  if (type.builtinConstraintBroadenings !== void 0) {
2073
2091
  for (const broadening of type.builtinConstraintBroadenings) {
2074
2092
  const key = `${qualifiedId}:${broadening.tagName}`;
@@ -2138,7 +2156,10 @@ function createExtensionRegistry(extensions) {
2138
2156
  `Metadata tag "@${canonicalTagName}" conflicts with existing FormSpec tag "@${canonicalTagName}".`
2139
2157
  );
2140
2158
  }
2141
- if (Object.hasOwn(BUILTIN_CONSTRAINT_DEFINITIONS, normalizeConstraintTagName(canonicalTagName))) {
2159
+ if (Object.hasOwn(
2160
+ BUILTIN_CONSTRAINT_DEFINITIONS,
2161
+ normalizeConstraintTagName(canonicalTagName)
2162
+ )) {
2142
2163
  throw new Error(
2143
2164
  `Metadata tag "@${canonicalTagName}" conflicts with existing FormSpec tag "@${normalizeConstraintTagName(canonicalTagName)}".`
2144
2165
  );
@@ -2159,6 +2180,11 @@ function createExtensionRegistry(extensions) {
2159
2180
  extensions,
2160
2181
  findType: (typeId) => typeMap.get(typeId),
2161
2182
  findTypeByName: (typeName) => typeNameMap.get(typeName),
2183
+ findTypeByBrand: (brand) => brandMap.get(brand),
2184
+ findTypeBySymbol: (symbol) => symbolMap.get(symbol),
2185
+ setSymbolMap: (map) => {
2186
+ symbolMap = map;
2187
+ },
2162
2188
  findConstraint: (constraintId) => constraintMap.get(constraintId),
2163
2189
  findConstraintTag: (tagName) => constraintTagMap.get(normalizeFormSpecTagName(tagName)),
2164
2190
  findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
@@ -3063,30 +3089,70 @@ function isObjectType(type) {
3063
3089
  function isIntersectionType(type) {
3064
3090
  return !!(type.flags & ts3.TypeFlags.Intersection);
3065
3091
  }
3092
+ function collectBrandIdentifiers(type) {
3093
+ if (!type.isIntersection()) {
3094
+ return [];
3095
+ }
3096
+ const brands = [];
3097
+ for (const prop of type.getProperties()) {
3098
+ const decl = prop.valueDeclaration ?? prop.declarations?.[0];
3099
+ if (decl === void 0) continue;
3100
+ if (!ts3.isPropertySignature(decl) && !ts3.isPropertyDeclaration(decl)) continue;
3101
+ if (!ts3.isComputedPropertyName(decl.name)) continue;
3102
+ if (!ts3.isIdentifier(decl.name.expression)) continue;
3103
+ brands.push(decl.name.expression.text);
3104
+ }
3105
+ return brands;
3106
+ }
3107
+ function resolveCanonicalSymbol(type, checker) {
3108
+ const raw = type.aliasSymbol ?? type.getSymbol();
3109
+ if (raw === void 0) return void 0;
3110
+ return raw.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(raw) : raw;
3111
+ }
3112
+ function resolveSymbolBasedCustomType(type, extensionRegistry, checker) {
3113
+ if (extensionRegistry === void 0) {
3114
+ return null;
3115
+ }
3116
+ const canonical = resolveCanonicalSymbol(type, checker);
3117
+ if (canonical === void 0) {
3118
+ return null;
3119
+ }
3120
+ const registration = extensionRegistry.findTypeBySymbol(canonical);
3121
+ if (registration === void 0) {
3122
+ return null;
3123
+ }
3124
+ return {
3125
+ kind: "custom",
3126
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
3127
+ payload: null
3128
+ };
3129
+ }
3066
3130
  function isIntegerBrandedType(type) {
3067
3131
  if (!type.isIntersection()) {
3068
3132
  return false;
3069
3133
  }
3070
- const hasNumberBase = type.types.some(
3071
- (member) => !!(member.flags & ts3.TypeFlags.Number)
3072
- );
3134
+ const hasNumberBase = type.types.some((member) => !!(member.flags & ts3.TypeFlags.Number));
3073
3135
  if (!hasNumberBase) {
3074
3136
  return false;
3075
3137
  }
3076
- return type.getProperties().some((prop) => {
3077
- const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
3078
- if (declaration === void 0) {
3079
- return false;
3080
- }
3081
- if (!ts3.isPropertySignature(declaration) && !ts3.isPropertyDeclaration(declaration)) {
3082
- return false;
3083
- }
3084
- const name = declaration.name;
3085
- if (!ts3.isComputedPropertyName(name)) {
3086
- return false;
3138
+ return collectBrandIdentifiers(type).includes("__integerBrand");
3139
+ }
3140
+ function resolveBrandedCustomType(type, extensionRegistry) {
3141
+ if (extensionRegistry === void 0) {
3142
+ return null;
3143
+ }
3144
+ for (const brand of collectBrandIdentifiers(type)) {
3145
+ const registration = extensionRegistry.findTypeByBrand(brand);
3146
+ if (registration === void 0) {
3147
+ continue;
3087
3148
  }
3088
- return ts3.isIdentifier(name.expression) && name.expression.text === "__integerBrand";
3089
- });
3149
+ return {
3150
+ kind: "custom",
3151
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
3152
+ payload: null
3153
+ };
3154
+ }
3155
+ return null;
3090
3156
  }
3091
3157
  function isResolvableObjectLikeAliasTypeNode(typeNode) {
3092
3158
  if (ts3.isParenthesizedTypeNode(typeNode)) {
@@ -4194,6 +4260,14 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
4194
4260
  if (customType) {
4195
4261
  return customType;
4196
4262
  }
4263
+ const symbolCustomType = resolveSymbolBasedCustomType(type, extensionRegistry, checker);
4264
+ if (symbolCustomType) {
4265
+ return symbolCustomType;
4266
+ }
4267
+ const brandedCustomType = resolveBrandedCustomType(type, extensionRegistry);
4268
+ if (brandedCustomType) {
4269
+ return brandedCustomType;
4270
+ }
4197
4271
  const primitiveAlias = tryResolveNamedPrimitiveAlias(
4198
4272
  type,
4199
4273
  checker,
@@ -5154,7 +5228,7 @@ function createProgramContextFromProgram(program, filePath) {
5154
5228
  sourceFile
5155
5229
  };
5156
5230
  }
5157
- function createProgramContext(filePath) {
5231
+ function createProgramContext(filePath, additionalFiles) {
5158
5232
  const absolutePath = path.resolve(filePath);
5159
5233
  const fileDir = path.dirname(absolutePath);
5160
5234
  const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
@@ -5177,7 +5251,8 @@ function createProgramContext(filePath) {
5177
5251
  throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
5178
5252
  }
5179
5253
  compilerOptions = parsed.options;
5180
- fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
5254
+ const normalizedAdditional = (additionalFiles ?? []).map((f) => path.resolve(f));
5255
+ fileNames = [.../* @__PURE__ */ new Set([...parsed.fileNames, absolutePath, ...normalizedAdditional])];
5181
5256
  } else {
5182
5257
  compilerOptions = {
5183
5258
  target: ts4.ScriptTarget.ES2022,
@@ -5187,7 +5262,8 @@ function createProgramContext(filePath) {
5187
5262
  skipLibCheck: true,
5188
5263
  declaration: true
5189
5264
  };
5190
- fileNames = [absolutePath];
5265
+ const normalizedAdditional = (additionalFiles ?? []).map((f) => path.resolve(f));
5266
+ fileNames = [.../* @__PURE__ */ new Set([absolutePath, ...normalizedAdditional])];
5191
5267
  }
5192
5268
  const program = ts4.createProgram(fileNames, compilerOptions);
5193
5269
  const sourceFile = program.getSourceFile(absolutePath);
@@ -5497,10 +5573,139 @@ var init_program = __esm({
5497
5573
  }
5498
5574
  });
5499
5575
 
5576
+ // src/extensions/symbol-registry.ts
5577
+ import * as ts5 from "typescript";
5578
+ import * as path2 from "path";
5579
+ function buildSymbolMapFromConfig(configPath, program, checker, extensionRegistry) {
5580
+ const symbolMap = /* @__PURE__ */ new Map();
5581
+ const normalizedPath = path2.resolve(configPath);
5582
+ const configFile = program.getSourceFile(normalizedPath);
5583
+ if (configFile === void 0) {
5584
+ return symbolMap;
5585
+ }
5586
+ function visit(node) {
5587
+ if (ts5.isCallExpression(node) && isDefineCustomTypeCall(node, checker)) {
5588
+ processDefineCustomTypeCall(node);
5589
+ }
5590
+ ts5.forEachChild(node, visit);
5591
+ }
5592
+ function processDefineCustomTypeCall(call) {
5593
+ const typeArgNode = call.typeArguments?.[0];
5594
+ if (typeArgNode === void 0) {
5595
+ return;
5596
+ }
5597
+ const resolvedType = checker.getTypeFromTypeNode(typeArgNode);
5598
+ const canonical = resolveCanonicalSymbol2(resolvedType, checker);
5599
+ if (canonical === void 0) {
5600
+ return;
5601
+ }
5602
+ const typeName = extractTypeNameFromCallArg(call);
5603
+ if (typeName === null) {
5604
+ return;
5605
+ }
5606
+ let entry;
5607
+ const extensionId = extractEnclosingExtensionId(call, checker);
5608
+ if (extensionId !== null) {
5609
+ const reg = extensionRegistry.findType(`${extensionId}/${typeName}`);
5610
+ if (reg !== void 0) {
5611
+ entry = { extensionId, registration: reg };
5612
+ }
5613
+ }
5614
+ entry ??= findRegistrationByTypeName(extensionRegistry, typeName);
5615
+ if (entry === void 0) {
5616
+ return;
5617
+ }
5618
+ symbolMap.set(canonical, entry);
5619
+ }
5620
+ visit(configFile);
5621
+ return symbolMap;
5622
+ }
5623
+ function isDefineCustomTypeCall(node, checker) {
5624
+ if (node.typeArguments === void 0 || node.typeArguments.length === 0) return false;
5625
+ const callSymbol = checker.getSymbolAtLocation(node.expression);
5626
+ if (callSymbol !== void 0) {
5627
+ const resolved = callSymbol.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(callSymbol) : callSymbol;
5628
+ const decl = resolved.declarations?.[0];
5629
+ if (decl !== void 0) {
5630
+ const sourceFile = decl.getSourceFile().fileName.replace(/\\/g, "/");
5631
+ return resolved.name === "defineCustomType" && // Match whether in node_modules/@formspec/core or monorepo packages/core.
5632
+ (sourceFile.includes("@formspec/core") || sourceFile.includes("/packages/core/"));
5633
+ }
5634
+ }
5635
+ return ts5.isIdentifier(node.expression) && node.expression.text === "defineCustomType";
5636
+ }
5637
+ function resolveCanonicalSymbol2(type, checker) {
5638
+ const raw = type.aliasSymbol ?? type.getSymbol();
5639
+ if (raw === void 0) return void 0;
5640
+ return raw.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(raw) : raw;
5641
+ }
5642
+ function extractTypeNameFromCallArg(call) {
5643
+ const arg = call.arguments[0];
5644
+ if (arg === void 0 || !ts5.isObjectLiteralExpression(arg)) {
5645
+ return null;
5646
+ }
5647
+ const typeNameProp = arg.properties.find(
5648
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === "typeName"
5649
+ );
5650
+ if (typeNameProp === void 0 || !ts5.isStringLiteral(typeNameProp.initializer)) {
5651
+ return null;
5652
+ }
5653
+ return typeNameProp.initializer.text;
5654
+ }
5655
+ function extractEnclosingExtensionId(call, checker) {
5656
+ for (let node = call.parent; !ts5.isSourceFile(node); node = node.parent) {
5657
+ if (ts5.isCallExpression(node) && isDefineExtensionCall(node, checker)) {
5658
+ return extractExtensionIdFromCallArg(node);
5659
+ }
5660
+ }
5661
+ return null;
5662
+ }
5663
+ function isDefineExtensionCall(node, checker) {
5664
+ const callSymbol = checker.getSymbolAtLocation(node.expression);
5665
+ if (callSymbol !== void 0) {
5666
+ const resolved = callSymbol.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(callSymbol) : callSymbol;
5667
+ const decl = resolved.declarations?.[0];
5668
+ if (decl !== void 0) {
5669
+ const sourceFile = decl.getSourceFile().fileName.replace(/\\/g, "/");
5670
+ return resolved.name === "defineExtension" && (sourceFile.includes("@formspec/core") || sourceFile.includes("/packages/core/"));
5671
+ }
5672
+ }
5673
+ return ts5.isIdentifier(node.expression) && node.expression.text === "defineExtension";
5674
+ }
5675
+ function extractExtensionIdFromCallArg(call) {
5676
+ const arg = call.arguments[0];
5677
+ if (arg === void 0 || !ts5.isObjectLiteralExpression(arg)) {
5678
+ return null;
5679
+ }
5680
+ const prop = arg.properties.find(
5681
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === "extensionId"
5682
+ );
5683
+ if (prop === void 0 || !ts5.isStringLiteral(prop.initializer)) {
5684
+ return null;
5685
+ }
5686
+ return prop.initializer.text;
5687
+ }
5688
+ function findRegistrationByTypeName(registry, typeName) {
5689
+ for (const ext of registry.extensions) {
5690
+ if (ext.types === void 0) {
5691
+ continue;
5692
+ }
5693
+ for (const type of ext.types) {
5694
+ if (type.typeName === typeName) {
5695
+ return { extensionId: ext.extensionId, registration: type };
5696
+ }
5697
+ }
5698
+ }
5699
+ return void 0;
5700
+ }
5701
+ var init_symbol_registry = __esm({
5702
+ "src/extensions/symbol-registry.ts"() {
5703
+ "use strict";
5704
+ }
5705
+ });
5706
+
5500
5707
  // src/validate/constraint-validator.ts
5501
- import {
5502
- analyzeConstraintTargets
5503
- } from "@formspec/analysis/internal";
5708
+ import { analyzeConstraintTargets } from "@formspec/analysis/internal";
5504
5709
  function validateFieldNode(ctx, field) {
5505
5710
  const analysis = analyzeConstraintTargets(
5506
5711
  field.name,
@@ -5586,7 +5791,7 @@ var init_validate = __esm({
5586
5791
  });
5587
5792
 
5588
5793
  // src/generators/class-schema.ts
5589
- import * as ts5 from "typescript";
5794
+ import * as ts6 from "typescript";
5590
5795
  function generateClassSchemas(analysis, source, options) {
5591
5796
  const result = generateClassSchemasDetailed(analysis, source, options);
5592
5797
  if (!result.ok || result.jsonSchema === void 0 || result.uiSchema === void 0) {
@@ -5645,7 +5850,8 @@ function formatLocation(location) {
5645
5850
  return `${location.file}:${String(location.line)}:${String(location.column)}`;
5646
5851
  }
5647
5852
  function generateSchemasFromClass(options) {
5648
- const ctx = createProgramContext(options.filePath);
5853
+ const additionalFiles = options.configPath !== void 0 ? [options.configPath] : void 0;
5854
+ const ctx = createProgramContext(options.filePath, additionalFiles);
5649
5855
  const classDecl = findClassByName(ctx.sourceFile, options.className);
5650
5856
  if (!classDecl) {
5651
5857
  throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
@@ -5710,7 +5916,8 @@ function generateSchemasDetailed(options) {
5710
5916
  function generateSchemasDetailedInternal(options) {
5711
5917
  let ctx;
5712
5918
  try {
5713
- ctx = createProgramContext(options.filePath);
5919
+ const additionalFiles = options.configPath !== void 0 ? [options.configPath] : void 0;
5920
+ ctx = createProgramContext(options.filePath, additionalFiles);
5714
5921
  } catch (error) {
5715
5922
  return {
5716
5923
  ok: false,
@@ -5749,13 +5956,16 @@ function generateSchemasFromProgramDetailedInternal(options) {
5749
5956
  }
5750
5957
  function generateSchemasBatch(options) {
5751
5958
  const contextCache = /* @__PURE__ */ new Map();
5959
+ const resolved = resolveOptions(options);
5960
+ let symbolMapProgram;
5752
5961
  return options.targets.map((target) => {
5753
5962
  let ctx;
5754
5963
  try {
5755
- const cacheKey = ts5.sys.useCaseSensitiveFileNames ? target.filePath : target.filePath.toLowerCase();
5964
+ const cacheKey = ts6.sys.useCaseSensitiveFileNames ? target.filePath : target.filePath.toLowerCase();
5756
5965
  const cachedContext = contextCache.get(cacheKey);
5757
5966
  if (cachedContext === void 0) {
5758
- ctx = createProgramContext(target.filePath);
5967
+ const additionalFiles = options.configPath !== void 0 ? [options.configPath] : void 0;
5968
+ ctx = createProgramContext(target.filePath, additionalFiles);
5759
5969
  contextCache.set(cacheKey, ctx);
5760
5970
  } else {
5761
5971
  ctx = cachedContext;
@@ -5766,9 +5976,25 @@ function generateSchemasBatch(options) {
5766
5976
  diagnostics: [createProgramContextFailureDiagnostic(target.filePath, error)]
5767
5977
  });
5768
5978
  }
5979
+ if (options.configPath !== void 0 && resolved.extensionRegistry !== void 0 && isMutableRegistry(resolved.extensionRegistry) && ctx.program !== symbolMapProgram) {
5980
+ const symbolMap = buildSymbolMapFromConfig(
5981
+ options.configPath,
5982
+ ctx.program,
5983
+ ctx.checker,
5984
+ resolved.extensionRegistry
5985
+ );
5986
+ resolved.extensionRegistry.setSymbolMap(symbolMap);
5987
+ symbolMapProgram = ctx.program;
5988
+ }
5769
5989
  return withTarget(
5770
5990
  target,
5771
- generateSchemasFromDetailedProgramContext(ctx, target.filePath, target.typeName, options)
5991
+ generateSchemasFromResolvedOptions(
5992
+ ctx,
5993
+ target.filePath,
5994
+ target.typeName,
5995
+ resolved,
5996
+ options.discriminator
5997
+ )
5772
5998
  );
5773
5999
  });
5774
6000
  }
@@ -5789,11 +6015,19 @@ function generateSchemasBatchFromProgram(options) {
5789
6015
  );
5790
6016
  });
5791
6017
  }
6018
+ function isMutableRegistry(reg) {
6019
+ return "setSymbolMap" in reg && typeof reg.setSymbolMap === "function";
6020
+ }
5792
6021
  function resolveOptions(options) {
5793
6022
  const configRegistry = options.config?.extensions !== void 0 ? createExtensionRegistry(options.config.extensions) : void 0;
6023
+ const legacyRegistry = options.extensionRegistry;
5794
6024
  return {
5795
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5796
- extensionRegistry: options.extensionRegistry ?? configRegistry,
6025
+ // When the caller provides the deprecated extensionRegistry field directly,
6026
+ // it is typed as the read-only ExtensionRegistry interface. We cast here
6027
+ // because the legacy path was introduced before MutableExtensionRegistry was
6028
+ // split out; callers using createExtensionRegistry() always get a mutable
6029
+ // registry, and this cast is safe for all registries produced by this module.
6030
+ extensionRegistry: legacyRegistry ?? configRegistry,
5797
6031
  // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5798
6032
  vendorPrefix: options.vendorPrefix ?? options.config?.vendorPrefix,
5799
6033
  // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
@@ -5804,13 +6038,31 @@ function resolveOptions(options) {
5804
6038
  }
5805
6039
  function generateSchemasFromDetailedProgramContext(ctx, filePath, typeName, options) {
5806
6040
  const resolved = resolveOptions(options);
6041
+ if (options.configPath !== void 0 && resolved.extensionRegistry !== void 0 && isMutableRegistry(resolved.extensionRegistry)) {
6042
+ const symbolMap = buildSymbolMapFromConfig(
6043
+ options.configPath,
6044
+ ctx.program,
6045
+ ctx.checker,
6046
+ resolved.extensionRegistry
6047
+ );
6048
+ resolved.extensionRegistry.setSymbolMap(symbolMap);
6049
+ }
6050
+ return generateSchemasFromResolvedOptions(
6051
+ ctx,
6052
+ filePath,
6053
+ typeName,
6054
+ resolved,
6055
+ options.discriminator
6056
+ );
6057
+ }
6058
+ function generateSchemasFromResolvedOptions(ctx, filePath, typeName, resolved, discriminator) {
5807
6059
  const analysisResult = analyzeNamedTypeToIRFromProgramContextDetailed(
5808
6060
  ctx,
5809
6061
  filePath,
5810
6062
  typeName,
5811
6063
  resolved.extensionRegistry,
5812
6064
  resolved.metadata,
5813
- options.discriminator
6065
+ discriminator
5814
6066
  );
5815
6067
  if (!analysisResult.ok) {
5816
6068
  return {
@@ -5858,13 +6110,14 @@ var init_class_schema = __esm({
5858
6110
  init_canonicalize();
5859
6111
  init_ir_generator();
5860
6112
  init_extensions();
6113
+ init_symbol_registry();
5861
6114
  init_ir_generator2();
5862
6115
  init_validate();
5863
6116
  }
5864
6117
  });
5865
6118
 
5866
6119
  // src/static-build.ts
5867
- import * as ts6 from "typescript";
6120
+ import * as ts7 from "typescript";
5868
6121
  function toStaticBuildContext(context) {
5869
6122
  return context;
5870
6123
  }
@@ -5879,7 +6132,7 @@ function getModuleSymbol(context) {
5879
6132
  return context.checker.getSymbolAtLocation(context.sourceFile) ?? sourceFileWithSymbol.symbol;
5880
6133
  }
5881
6134
  function isSchemaSourceDeclaration(declaration) {
5882
- return ts6.isClassDeclaration(declaration) || ts6.isInterfaceDeclaration(declaration) || ts6.isTypeAliasDeclaration(declaration);
6135
+ return ts7.isClassDeclaration(declaration) || ts7.isInterfaceDeclaration(declaration) || ts7.isTypeAliasDeclaration(declaration);
5883
6136
  }
5884
6137
  function resolveModuleExport(context, exportName = "default") {
5885
6138
  const moduleSymbol = getModuleSymbol(context);
@@ -5890,7 +6143,7 @@ function resolveModuleExport(context, exportName = "default") {
5890
6143
  if (exportSymbol === null) {
5891
6144
  return null;
5892
6145
  }
5893
- return exportSymbol.flags & ts6.SymbolFlags.Alias ? context.checker.getAliasedSymbol(exportSymbol) : exportSymbol;
6146
+ return exportSymbol.flags & ts7.SymbolFlags.Alias ? context.checker.getAliasedSymbol(exportSymbol) : exportSymbol;
5894
6147
  }
5895
6148
  function resolveModuleExportDeclaration(context, exportName = "default") {
5896
6149
  return resolveModuleExport(context, exportName)?.declarations?.find(isSchemaSourceDeclaration) ?? null;
@@ -5903,7 +6156,7 @@ var init_static_build = __esm({
5903
6156
  });
5904
6157
 
5905
6158
  // src/generators/discovered-schema.ts
5906
- import * as ts7 from "typescript";
6159
+ import * as ts8 from "typescript";
5907
6160
  import { analyzeMetadataForNodeWithChecker as analyzeMetadataForNodeWithChecker2 } from "@formspec/analysis/internal";
5908
6161
  import { IR_VERSION as IR_VERSION3 } from "@formspec/core/internals";
5909
6162
  function toDiscoveredTypeSchemas(result, resolvedMetadata) {
@@ -5913,17 +6166,17 @@ function toDiscoveredTypeSchemas(result, resolvedMetadata) {
5913
6166
  };
5914
6167
  }
5915
6168
  function isNamedTypeDeclaration(declaration) {
5916
- return ts7.isClassDeclaration(declaration) || ts7.isInterfaceDeclaration(declaration) || ts7.isTypeAliasDeclaration(declaration);
6169
+ return ts8.isClassDeclaration(declaration) || ts8.isInterfaceDeclaration(declaration) || ts8.isTypeAliasDeclaration(declaration);
5917
6170
  }
5918
6171
  function hasConcreteTypeArguments(type, checker) {
5919
6172
  if ("aliasTypeArguments" in type && Array.isArray(type.aliasTypeArguments) && type.aliasTypeArguments.length > 0) {
5920
6173
  return true;
5921
6174
  }
5922
- if ((type.flags & ts7.TypeFlags.Object) === 0) {
6175
+ if ((type.flags & ts8.TypeFlags.Object) === 0) {
5923
6176
  return false;
5924
6177
  }
5925
6178
  const objectType = type;
5926
- if ((objectType.objectFlags & ts7.ObjectFlags.Reference) === 0) {
6179
+ if ((objectType.objectFlags & ts8.ObjectFlags.Reference) === 0) {
5927
6180
  return false;
5928
6181
  }
5929
6182
  return checker.getTypeArguments(objectType).length > 0;
@@ -5936,13 +6189,13 @@ function getNamedTypeDeclaration2(type) {
5936
6189
  return declaration;
5937
6190
  }
5938
6191
  }
5939
- const aliasDeclaration = type.aliasSymbol?.declarations?.find(ts7.isTypeAliasDeclaration);
6192
+ const aliasDeclaration = type.aliasSymbol?.declarations?.find(ts8.isTypeAliasDeclaration);
5940
6193
  return aliasDeclaration;
5941
6194
  }
5942
6195
  function getFallbackName(sourceNode, fallback = "AnonymousType") {
5943
6196
  if (sourceNode !== void 0 && "name" in sourceNode) {
5944
6197
  const namedNode = sourceNode;
5945
- if (namedNode.name !== void 0 && ts7.isIdentifier(namedNode.name)) {
6198
+ if (namedNode.name !== void 0 && ts8.isIdentifier(namedNode.name)) {
5946
6199
  return namedNode.name.text;
5947
6200
  }
5948
6201
  }
@@ -6061,17 +6314,14 @@ function toStandaloneJsonSchema(root, typeRegistry, options) {
6061
6314
  rootLogicalName: root.name
6062
6315
  }
6063
6316
  );
6064
- const schema = generateJsonSchemaFromIR(
6065
- ir,
6066
- {
6067
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6068
- extensionRegistry: options?.extensionRegistry,
6069
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6070
- enumSerialization: options?.enumSerialization,
6071
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6072
- vendorPrefix: options?.vendorPrefix
6073
- }
6074
- );
6317
+ const schema = generateJsonSchemaFromIR(ir, {
6318
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6319
+ extensionRegistry: options?.extensionRegistry,
6320
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6321
+ enumSerialization: options?.enumSerialization,
6322
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6323
+ vendorPrefix: options?.vendorPrefix
6324
+ });
6075
6325
  const result = schema.properties?.["__result"];
6076
6326
  if (result === void 0) {
6077
6327
  throw new Error("FormSpec failed to extract the standalone schema root from the synthetic IR.");
@@ -6170,7 +6420,7 @@ function generateSchemasFromResolvedType(options, skipNamedDeclaration = false,
6170
6420
  }
6171
6421
  function generateSchemasFromDeclaration(options) {
6172
6422
  const filePath = options.declaration.getSourceFile().fileName;
6173
- if (ts7.isClassDeclaration(options.declaration)) {
6423
+ if (ts8.isClassDeclaration(options.declaration)) {
6174
6424
  return generateSchemasFromAnalysis(
6175
6425
  analyzeClassToIR(
6176
6426
  options.declaration,
@@ -6186,7 +6436,7 @@ function generateSchemasFromDeclaration(options) {
6186
6436
  options
6187
6437
  );
6188
6438
  }
6189
- if (ts7.isInterfaceDeclaration(options.declaration)) {
6439
+ if (ts8.isInterfaceDeclaration(options.declaration)) {
6190
6440
  return generateSchemasFromAnalysis(
6191
6441
  analyzeInterfaceToIR(
6192
6442
  options.declaration,
@@ -6202,7 +6452,7 @@ function generateSchemasFromDeclaration(options) {
6202
6452
  options
6203
6453
  );
6204
6454
  }
6205
- if (ts7.isTypeAliasDeclaration(options.declaration)) {
6455
+ if (ts8.isTypeAliasDeclaration(options.declaration)) {
6206
6456
  const analyzedAlias = analyzeTypeAliasToIR(
6207
6457
  options.declaration,
6208
6458
  options.context.checker,
@@ -6265,7 +6515,7 @@ function generateSchemasFromReturnType(options) {
6265
6515
  const returnType = signature !== void 0 ? options.context.checker.getReturnTypeOfSignature(signature) : options.context.checker.getTypeAtLocation(options.declaration);
6266
6516
  const type = unwrapPromiseType(options.context.checker, returnType);
6267
6517
  const sourceNode = type !== returnType ? unwrapPromiseTypeNode(options.declaration.type) ?? options.declaration.type ?? options.declaration : options.declaration.type ?? options.declaration;
6268
- const fallbackName = options.declaration.name !== void 0 && ts7.isIdentifier(options.declaration.name) ? `${options.declaration.name.text}ReturnType` : "ReturnType";
6518
+ const fallbackName = options.declaration.name !== void 0 && ts8.isIdentifier(options.declaration.name) ? `${options.declaration.name.text}ReturnType` : "ReturnType";
6269
6519
  return generateSchemasFromResolvedType({
6270
6520
  ...options,
6271
6521
  type,
@@ -6300,14 +6550,14 @@ function unwrapPromiseTypeNode(typeNode) {
6300
6550
  if (typeNode === void 0) {
6301
6551
  return void 0;
6302
6552
  }
6303
- if (ts7.isParenthesizedTypeNode(typeNode)) {
6553
+ if (ts8.isParenthesizedTypeNode(typeNode)) {
6304
6554
  const unwrapped = unwrapPromiseTypeNode(typeNode.type);
6305
6555
  return unwrapped ?? typeNode;
6306
6556
  }
6307
6557
  return isPromiseTypeReferenceNode(typeNode) ? typeNode.typeArguments[0] : typeNode;
6308
6558
  }
6309
6559
  function isPromiseTypeReferenceNode(typeNode) {
6310
- return ts7.isTypeReferenceNode(typeNode) && ts7.isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise" && typeNode.typeArguments !== void 0 && typeNode.typeArguments.length > 0;
6560
+ return ts8.isTypeReferenceNode(typeNode) && ts8.isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise" && typeNode.typeArguments !== void 0 && typeNode.typeArguments.length > 0;
6311
6561
  }
6312
6562
  var init_discovered_schema = __esm({
6313
6563
  "src/generators/discovered-schema.ts"() {
@@ -6552,7 +6802,7 @@ __export(index_exports, {
6552
6802
  writeSchemas: () => writeSchemas
6553
6803
  });
6554
6804
  import * as fs from "fs";
6555
- import * as path2 from "path";
6805
+ import * as path3 from "path";
6556
6806
  function buildFormSchemas(form, options) {
6557
6807
  return {
6558
6808
  jsonSchema: generateJsonSchema(form, options),
@@ -6577,8 +6827,8 @@ function writeSchemas(form, options) {
6577
6827
  if (!fs.existsSync(outDir)) {
6578
6828
  fs.mkdirSync(outDir, { recursive: true });
6579
6829
  }
6580
- const jsonSchemaPath = path2.join(outDir, `${name}-schema.json`);
6581
- const uiSchemaPath = path2.join(outDir, `${name}-uischema.json`);
6830
+ const jsonSchemaPath = path3.join(outDir, `${name}-schema.json`);
6831
+ const uiSchemaPath = path3.join(outDir, `${name}-uischema.json`);
6582
6832
  fs.writeFileSync(jsonSchemaPath, JSON.stringify(jsonSchema, null, indent));
6583
6833
  fs.writeFileSync(uiSchemaPath, JSON.stringify(uiSchema2, null, indent));
6584
6834
  return { jsonSchemaPath, uiSchemaPath };
@@ -6605,7 +6855,7 @@ var init_index = __esm({
6605
6855
  });
6606
6856
 
6607
6857
  // src/cli.ts
6608
- import * as path3 from "path";
6858
+ import * as path4 from "path";
6609
6859
  import { pathToFileURL } from "url";
6610
6860
  function printHelp() {
6611
6861
  console.log(`
@@ -6694,7 +6944,7 @@ function parseArgs(args) {
6694
6944
  return null;
6695
6945
  }
6696
6946
  if (!name) {
6697
- name = path3.basename(inputFile, path3.extname(inputFile));
6947
+ name = path4.basename(inputFile, path4.extname(inputFile));
6698
6948
  }
6699
6949
  return { inputFile, outDir, name, enumSerialization };
6700
6950
  }
@@ -6705,7 +6955,7 @@ async function main() {
6705
6955
  process.exit(1);
6706
6956
  }
6707
6957
  const { inputFile, outDir, name, enumSerialization } = options;
6708
- const absoluteInput = path3.resolve(process.cwd(), inputFile);
6958
+ const absoluteInput = path4.resolve(process.cwd(), inputFile);
6709
6959
  try {
6710
6960
  const fileUrl = pathToFileURL(absoluteInput).href;
6711
6961
  const module = await import(fileUrl);