vouchington-tooling 0.33.0 → 0.35.0

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 (182) hide show
  1. package/README.md +181 -1
  2. package/dist/api-contract-discovery/index.d.mts +4 -1
  3. package/dist/api-contract-discovery/index.mjs +1 -0
  4. package/dist/api-contract-discovery/protocol-analysis-cache.d.mts +21 -0
  5. package/dist/api-contract-discovery/protocol-analysis-cache.mjs +7 -0
  6. package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +2 -1
  7. package/dist/api-contract-discovery/protocol-callback-invocation.mjs +12 -4
  8. package/dist/api-contract-discovery/protocol-callback-values.d.mts +2 -1
  9. package/dist/api-contract-discovery/protocol-callback-values.mjs +3 -3
  10. package/dist/api-contract-discovery/protocol-execution-path.d.mts +2 -1
  11. package/dist/api-contract-discovery/protocol-execution-path.mjs +2 -2
  12. package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +2 -1
  13. package/dist/api-contract-discovery/protocol-http-context-callers.mjs +13 -7
  14. package/dist/api-contract-discovery/query-contract-extraction.mjs +20 -3
  15. package/dist/api-contract-discovery/query-contract-literals.d.mts +5 -0
  16. package/dist/api-contract-discovery/query-contract-literals.mjs +29 -0
  17. package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +2 -1
  18. package/dist/api-contract-discovery/registered-route-binding-writes.mjs +20 -11
  19. package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +7 -1
  20. package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +27 -26
  21. package/dist/api-contract-discovery/registered-route-handler-helpers.d.mts +3 -0
  22. package/dist/api-contract-discovery/registered-route-handler-helpers.mjs +16 -0
  23. package/dist/api-contract-discovery/request-validation-callbacks.d.mts +9 -0
  24. package/dist/api-contract-discovery/request-validation-callbacks.mjs +27 -0
  25. package/dist/api-contract-discovery/request-validation-checker.d.mts +7 -0
  26. package/dist/api-contract-discovery/request-validation-checker.mjs +25 -0
  27. package/dist/api-contract-discovery/request-validation-conditional.d.mts +6 -0
  28. package/dist/api-contract-discovery/request-validation-conditional.mjs +31 -0
  29. package/dist/api-contract-discovery/request-validation-factory-calls.d.mts +11 -0
  30. package/dist/api-contract-discovery/request-validation-factory-calls.mjs +23 -0
  31. package/dist/api-contract-discovery/request-validation-facts.d.mts +16 -0
  32. package/dist/api-contract-discovery/request-validation-facts.mjs +64 -0
  33. package/dist/api-contract-discovery/request-validation-follow.d.mts +24 -0
  34. package/dist/api-contract-discovery/request-validation-follow.mjs +38 -0
  35. package/dist/api-contract-discovery/request-validation-handler-state.d.mts +8 -0
  36. package/dist/api-contract-discovery/request-validation-handler-state.mjs +35 -0
  37. package/dist/api-contract-discovery/request-validation-input.d.mts +12 -0
  38. package/dist/api-contract-discovery/request-validation-input.mjs +43 -0
  39. package/dist/api-contract-discovery/request-validation-keys.d.mts +25 -0
  40. package/dist/api-contract-discovery/request-validation-keys.mjs +119 -0
  41. package/dist/api-contract-discovery/request-validation-match.d.mts +11 -0
  42. package/dist/api-contract-discovery/request-validation-match.mjs +47 -0
  43. package/dist/api-contract-discovery/request-validation-mutation.d.mts +7 -0
  44. package/dist/api-contract-discovery/request-validation-mutation.mjs +80 -0
  45. package/dist/api-contract-discovery/request-validation-origin.d.mts +20 -0
  46. package/dist/api-contract-discovery/request-validation-origin.mjs +71 -0
  47. package/dist/api-contract-discovery/request-validation-reads.d.mts +13 -0
  48. package/dist/api-contract-discovery/request-validation-reads.mjs +88 -0
  49. package/dist/api-contract-discovery/request-validation-registration.d.mts +9 -0
  50. package/dist/api-contract-discovery/request-validation-registration.mjs +32 -0
  51. package/dist/api-contract-discovery/request-validation-report.d.mts +3 -0
  52. package/dist/api-contract-discovery/request-validation-report.mjs +21 -0
  53. package/dist/api-contract-discovery/request-validation-request-origin.d.mts +5 -0
  54. package/dist/api-contract-discovery/request-validation-request-origin.mjs +13 -0
  55. package/dist/api-contract-discovery/request-validation-select.d.mts +9 -0
  56. package/dist/api-contract-discovery/request-validation-select.mjs +36 -0
  57. package/dist/api-contract-discovery/request-validation-sites.d.mts +9 -0
  58. package/dist/api-contract-discovery/request-validation-sites.mjs +77 -0
  59. package/dist/api-contract-discovery/request-validation-state-key.d.mts +13 -0
  60. package/dist/api-contract-discovery/request-validation-state-key.mjs +30 -0
  61. package/dist/api-contract-discovery/request-validation-trace-helpers.d.mts +14 -0
  62. package/dist/api-contract-discovery/request-validation-trace-helpers.mjs +80 -0
  63. package/dist/api-contract-discovery/request-validation-trace-select.d.mts +23 -0
  64. package/dist/api-contract-discovery/request-validation-trace-select.mjs +45 -0
  65. package/dist/api-contract-discovery/request-validation-trace.d.mts +8 -0
  66. package/dist/api-contract-discovery/request-validation-trace.mjs +130 -0
  67. package/dist/api-contract-discovery/request-validation-types.d.mts +84 -0
  68. package/dist/api-contract-discovery/request-validation-types.mjs +1 -0
  69. package/dist/api-contract-discovery/request-validation-walker.d.mts +26 -0
  70. package/dist/api-contract-discovery/request-validation-walker.mjs +143 -0
  71. package/dist/api-contract-discovery/request-validation.test-helpers.d.mts +39 -0
  72. package/dist/api-contract-discovery/request-validation.test-helpers.mjs +108 -0
  73. package/dist/api-contract-discovery/response-contract-attribution-facts.d.mts +2 -0
  74. package/dist/api-contract-discovery/response-contract-attribution-facts.mjs +18 -0
  75. package/dist/api-contract-discovery/response-contract-attribution.d.mts +3 -0
  76. package/dist/api-contract-discovery/response-contract-attribution.mjs +23 -0
  77. package/dist/api-contract-discovery/response-contract-call-classification.d.mts +3 -0
  78. package/dist/api-contract-discovery/response-contract-call-classification.mjs +37 -0
  79. package/dist/api-contract-discovery/response-contract-error-branch.d.mts +1 -1
  80. package/dist/api-contract-discovery/response-contract-error-branch.mjs +109 -6
  81. package/dist/api-contract-discovery/response-contract-handler-bindings.d.mts +3 -0
  82. package/dist/api-contract-discovery/response-contract-handler-bindings.mjs +126 -0
  83. package/dist/api-contract-discovery/response-contract-handler-calls.d.mts +3 -0
  84. package/dist/api-contract-discovery/response-contract-handler-calls.mjs +81 -0
  85. package/dist/api-contract-discovery/response-contract-implicit-variants.d.mts +2 -0
  86. package/dist/api-contract-discovery/response-contract-implicit-variants.mjs +6 -0
  87. package/dist/api-contract-discovery/response-contract-implicit.d.mts +2 -1
  88. package/dist/api-contract-discovery/response-contract-implicit.mjs +9 -17
  89. package/dist/api-contract-discovery/response-contract-lenient.d.mts +18 -0
  90. package/dist/api-contract-discovery/response-contract-object-symbols.d.mts +3 -0
  91. package/dist/api-contract-discovery/response-contract-object-symbols.mjs +80 -0
  92. package/dist/api-contract-discovery/response-contract-property-mutations.d.mts +5 -0
  93. package/dist/api-contract-discovery/response-contract-property-mutations.mjs +84 -0
  94. package/dist/api-contract-discovery/response-contract-registration.d.mts +1 -1
  95. package/dist/api-contract-discovery/response-contract-registration.mjs +7 -3
  96. package/dist/api-contract-discovery/response-contract-registry.mjs +43 -8
  97. package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +3 -4
  98. package/dist/api-contract-discovery/response-contract-route-analysis.mjs +4 -101
  99. package/dist/api-contract-discovery/response-contract-route-syntax.d.mts +7 -0
  100. package/dist/api-contract-discovery/response-contract-route-syntax.mjs +41 -0
  101. package/dist/api-contract-discovery/response-contract-symbols.d.mts +5 -0
  102. package/dist/api-contract-discovery/response-contract-symbols.mjs +82 -0
  103. package/dist/compiler-build/index.d.mts +2 -0
  104. package/dist/compiler-build/index.mjs +1 -0
  105. package/dist/compiler-build/program-configuration.d.mts +9 -0
  106. package/dist/compiler-build/program-configuration.mjs +28 -0
  107. package/dist/compiler-build/program-types.d.mts +29 -0
  108. package/dist/compiler-build/program-types.mjs +1 -0
  109. package/dist/compiler-build/program.d.mts +7 -0
  110. package/dist/compiler-build/program.mjs +78 -0
  111. package/dist/contract-schema/index.d.mts +4 -0
  112. package/dist/contract-schema/index.mjs +2 -0
  113. package/dist/contract-schema/type-query-api.d.mts +44 -0
  114. package/dist/contract-schema/type-query-api.mjs +10 -0
  115. package/dist/contract-schema/type-query-call.d.mts +16 -0
  116. package/dist/contract-schema/type-query-call.mjs +52 -0
  117. package/dist/contract-schema/type-query-export.d.mts +10 -0
  118. package/dist/contract-schema/type-query-export.mjs +91 -0
  119. package/dist/contract-schema/type-query.d.mts +22 -0
  120. package/dist/contract-schema/type-query.mjs +39 -0
  121. package/dist/finite-enum-ripple/ast.d.mts +9 -0
  122. package/dist/finite-enum-ripple/ast.mjs +94 -0
  123. package/dist/finite-enum-ripple/check.d.mts +3 -0
  124. package/dist/finite-enum-ripple/check.mjs +62 -0
  125. package/dist/finite-enum-ripple/compare.d.mts +20 -0
  126. package/dist/finite-enum-ripple/compare.mjs +88 -0
  127. package/dist/finite-enum-ripple/component-paths.d.mts +8 -0
  128. package/dist/finite-enum-ripple/component-paths.mjs +30 -0
  129. package/dist/finite-enum-ripple/configured-object-mutations.d.mts +1 -0
  130. package/dist/finite-enum-ripple/configured-object-mutations.mjs +78 -0
  131. package/dist/finite-enum-ripple/create-page-jsx.d.mts +3 -0
  132. package/dist/finite-enum-ripple/create-page-jsx.mjs +28 -0
  133. package/dist/finite-enum-ripple/create-page-literals.d.mts +2 -0
  134. package/dist/finite-enum-ripple/create-page-literals.mjs +83 -0
  135. package/dist/finite-enum-ripple/create-page-object-visitor.d.mts +2 -0
  136. package/dist/finite-enum-ripple/create-page-object-visitor.mjs +57 -0
  137. package/dist/finite-enum-ripple/create-page-properties.d.mts +4 -0
  138. package/dist/finite-enum-ripple/create-page-properties.mjs +35 -0
  139. package/dist/finite-enum-ripple/index.d.mts +2 -0
  140. package/dist/finite-enum-ripple/index.mjs +1 -0
  141. package/dist/finite-enum-ripple/model.d.mts +78 -0
  142. package/dist/finite-enum-ripple/model.mjs +1 -0
  143. package/dist/finite-enum-ripple/parser-support.d.mts +11 -0
  144. package/dist/finite-enum-ripple/parser-support.mjs +99 -0
  145. package/dist/finite-enum-ripple/parsers.d.mts +32 -0
  146. package/dist/finite-enum-ripple/parsers.mjs +115 -0
  147. package/dist/finite-enum-ripple/pattern-captures.d.mts +2 -0
  148. package/dist/finite-enum-ripple/pattern-captures.mjs +45 -0
  149. package/dist/finite-enum-ripple/structured-route-check.d.mts +3 -0
  150. package/dist/finite-enum-ripple/structured-route-check.mjs +94 -0
  151. package/dist/finite-enum-ripple/types.d.mts +2 -0
  152. package/dist/finite-enum-ripple/types.mjs +60 -0
  153. package/dist/finite-enum-ripple/union-factory-check.d.mts +2 -0
  154. package/dist/finite-enum-ripple/union-factory-check.mjs +34 -0
  155. package/dist/finite-enum-ripple/union-types.d.mts +2 -0
  156. package/dist/finite-enum-ripple/union-types.mjs +129 -0
  157. package/dist/openapi-document/build-openapi-query.mjs +1 -1
  158. package/dist/openapi-document/openapi-types.d.mts +1 -1
  159. package/dist/openapi-document/query-types.d.mts +8 -2
  160. package/dist/post-publication-inventory/index.d.mts +5 -0
  161. package/dist/post-publication-inventory/index.mjs +2 -0
  162. package/dist/post-publication-inventory/readers/sql-executors.d.mts +3 -0
  163. package/dist/post-publication-inventory/readers/sql-executors.mjs +32 -0
  164. package/dist/post-publication-inventory/readers/sql-source.d.mts +7 -0
  165. package/dist/post-publication-inventory/readers/sql-source.mjs +87 -0
  166. package/dist/post-publication-inventory/readers/sql-template.d.mts +12 -0
  167. package/dist/post-publication-inventory/readers/sql-template.mjs +119 -0
  168. package/dist/post-publication-inventory/writers/source.d.mts +9 -0
  169. package/dist/post-publication-inventory/writers/source.mjs +128 -0
  170. package/dist/post-publication-inventory/writers/types.d.mts +19 -0
  171. package/dist/post-publication-inventory/writers/types.mjs +1 -0
  172. package/dist/workflow-policy/index.d.mts +1 -3
  173. package/dist/workflow-policy/index.mjs +0 -2
  174. package/dist/workflow-policy/types.d.mts +2 -28
  175. package/docs/workflow-and-compiler.md +7 -16
  176. package/package.json +13 -2
  177. package/dist/workflow-policy/concurrency-scope.d.mts +0 -4
  178. package/dist/workflow-policy/concurrency-scope.mjs +0 -66
  179. package/dist/workflow-policy/concurrency.d.mts +0 -10
  180. package/dist/workflow-policy/concurrency.mjs +0 -77
  181. package/dist/workflow-policy/graph.d.mts +0 -2
  182. package/dist/workflow-policy/graph.mjs +0 -138
@@ -0,0 +1,94 @@
1
+ import { hasConfiguredObjectDeclaration } from './ast.mjs';
2
+ import { parseStructuredRouteConfigEntries } from './parsers.mjs';
3
+ import { checkCollectionPagePathLiterals, compareSets, finiteEnumError, hasAllFiles, routePageSlugs, uniqueSorted, } from './compare.mjs';
4
+ import { checkStructuredCollectionComponentPathLiterals } from './component-paths.mjs';
5
+ export function checkStructuredRouteConfigs(errors, files, readTracked, config, backendEntries, backendSlugByValue) {
6
+ const routeConfigsPath = config.routeConfigsPath;
7
+ const backendStructuredsPath = config.backendPath;
8
+ if (hasAllFiles(files, [routeConfigsPath])) {
9
+ const routeConfigContent = readTracked(routeConfigsPath);
10
+ if (hasConfiguredObjectDeclaration(routeConfigContent, config.routeConfigObject, routeConfigsPath)) {
11
+ const routeConfigs = parseStructuredRouteConfigEntries(routeConfigContent, routeConfigsPath, config.routeConfigObject, config.typeArrayProperty, config.routeExemptionProperty, config.pluralPathProperty, config.singularPathProperty);
12
+ const slugPluralByValue = new Map(backendEntries.map((entry) => [entry.value, entry.slugPlural]));
13
+ const valueBySlug = new Map(backendEntries.map((entry) => [entry.slug, entry.value]));
14
+ const typedRouteConfigs = routeConfigs.map((routeConfig) => ({
15
+ ...routeConfig,
16
+ inferredStructuredType: valueBySlug.get(routeConfig.singularPath),
17
+ }));
18
+ for (const routeConfig of typedRouteConfigs) {
19
+ if (routeConfig.structuredTypes.length > 0 ||
20
+ routeConfig.routeExempt ||
21
+ routeConfig.inferredStructuredType === undefined ||
22
+ config.routeConfigExceptions.includes(routeConfig.key)) {
23
+ continue;
24
+ }
25
+ errors.push(finiteEnumError(routeConfigsPath, `${config.routeConfigObject}.${routeConfig.key} has ${config.singularPathProperty} "${routeConfig.singularPath}" but does not declare ${config.typeArrayProperty}`));
26
+ }
27
+ const enumRouteConfigs = typedRouteConfigs.filter((routeConfig) => routeConfig.structuredTypes.length > 0);
28
+ for (const routeConfig of routeConfigs) {
29
+ if (routeConfig.key === routeConfig.pluralPath)
30
+ continue;
31
+ errors.push(finiteEnumError(routeConfigsPath, `${config.routeConfigObject}.${routeConfig.key} has ${config.pluralPathProperty} "${routeConfig.pluralPath}" but the route config key is "${routeConfig.key}"`));
32
+ }
33
+ for (const routeConfig of enumRouteConfigs) {
34
+ const structuredType = routeConfig.structuredTypes[0];
35
+ const expectedSlug = backendSlugByValue.get(structuredType);
36
+ const expectedSlugPlural = slugPluralByValue.get(structuredType);
37
+ if (routeConfig.structuredTypes.length === 1 &&
38
+ routeConfig.singularPath === expectedSlug &&
39
+ routeConfig.pluralPath === expectedSlugPlural) {
40
+ continue;
41
+ }
42
+ errors.push(finiteEnumError(routeConfigsPath, `${config.routeConfigObject}.${routeConfig.key} maps ${config.singularPathProperty} "${routeConfig.singularPath}" and ${config.pluralPathProperty} "${routeConfig.pluralPath}" to [${routeConfig.structuredTypes.join(', ')}] but ${config.typeObject} expects "${expectedSlug ?? 'missing'}" and "${expectedSlugPlural ?? 'missing'}"`));
43
+ }
44
+ compareSets(errors, {
45
+ label: `${config.collectionLabel} route config values`,
46
+ actualLabel: `${routeConfigsPath} ${config.routeConfigObject}`,
47
+ actualFile: routeConfigsPath,
48
+ actualValues: typedRouteConfigs.flatMap((routeConfig) => routeConfig.structuredTypes.length > 0
49
+ ? routeConfig.structuredTypes
50
+ : routeConfig.inferredStructuredType !== undefined &&
51
+ (routeConfig.routeExempt || config.routeConfigExceptions.includes(routeConfig.key))
52
+ ? [routeConfig.inferredStructuredType]
53
+ : []),
54
+ expectedLabel: backendStructuredsPath,
55
+ expectedFile: backendStructuredsPath,
56
+ expectedValues: backendEntries
57
+ .filter((entry) => !config.collectionRouteExclusions.includes(entry.value))
58
+ .map((entry) => entry.value),
59
+ });
60
+ compareSets(errors, {
61
+ label: `${config.collectionLabel} collection route config paths`,
62
+ actualLabel: `${routeConfigsPath} ${config.routeConfigObject}`,
63
+ actualFile: routeConfigsPath,
64
+ actualValues: enumRouteConfigs.map((routeConfig) => routeConfig.pluralPath),
65
+ expectedLabel: `${backendStructuredsPath} ${config.typeObject} ${config.slugPluralProperty}`,
66
+ expectedFile: backendStructuredsPath,
67
+ expectedValues: enumRouteConfigs.flatMap((routeConfig) => {
68
+ const slugPlural = slugPluralByValue.get(routeConfig.structuredTypes[0]);
69
+ return slugPlural ? [slugPlural] : [];
70
+ }),
71
+ });
72
+ compareSets(errors, {
73
+ label: `${config.collectionLabel} top-level collection route directories`,
74
+ actualLabel: config.routeLabels.collectionTop,
75
+ actualFile: files.structuredCollectionPages[0]?.file ?? routeConfigsPath,
76
+ actualValues: routePageSlugs(files.structuredCollectionPages, true),
77
+ expectedLabel: `${routeConfigsPath} ${config.routeConfigObject} ${config.pluralPathProperty}`,
78
+ expectedFile: routeConfigsPath,
79
+ expectedValues: routeConfigs.map((routeConfig) => routeConfig.pluralPath),
80
+ });
81
+ compareSets(errors, {
82
+ label: `${config.collectionLabel} collection routed pages`,
83
+ actualLabel: config.routeLabels.collection,
84
+ actualFile: files.structuredCollectionPages[0]?.file ?? routeConfigsPath,
85
+ actualValues: uniqueSorted(files.structuredCollectionPages.map((page) => page.slug)),
86
+ expectedLabel: `${routeConfigsPath} ${config.routeConfigObject} ${config.pluralPathProperty}`,
87
+ expectedFile: routeConfigsPath,
88
+ expectedValues: routeConfigs.map((routeConfig) => routeConfig.pluralPath),
89
+ });
90
+ checkCollectionPagePathLiterals(files.structuredCollectionPages, errors, config.collectionPathLabel ?? config.collectionLabel, new Set(routeConfigs.map((routeConfig) => routeConfig.pluralPath)), readTracked, config.collectionPathLiteralPattern);
91
+ checkStructuredCollectionComponentPathLiterals(errors, routeConfigs, files.structuredComponentFiles, new Set(config.ignoredNavigationPaths), readTracked, config.collectionLabel, config.navigationPathLiteralPattern);
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,2 @@
1
+ import type { FiniteEnumFiles, FiniteEnumRippleConfig, ReadTrackedFile } from './model.mts';
2
+ export declare function checkStructuredTypes(errors: string[], files: FiniteEnumFiles, readTracked: ReadTrackedFile, config: NonNullable<FiniteEnumRippleConfig['structured']>): void;
@@ -0,0 +1,60 @@
1
+ import { parseStructuredRouteFactoryArgs, parseStructuredTypeEntries } from './parsers.mjs';
2
+ import { compareSets, finiteEnumError, hasAllFiles, routePageSlugs } from './compare.mjs';
3
+ import { checkStructuredRouteConfigs } from './structured-route-check.mjs';
4
+ export function checkStructuredTypes(errors, files, readTracked, config) {
5
+ const backendStructuredsPath = config.backendPath;
6
+ const webStructuredsPath = config.webPath;
7
+ if (!hasAllFiles(files, [backendStructuredsPath, webStructuredsPath]))
8
+ return;
9
+ const backendEntries = parseStructuredTypeEntries(readTracked(backendStructuredsPath), backendStructuredsPath, config.typeObject, config.slugProperty, config.slugPluralProperty);
10
+ const webEntries = parseStructuredTypeEntries(readTracked(webStructuredsPath), webStructuredsPath, config.typeObject, config.slugProperty, config.slugPluralProperty);
11
+ const backendSlugByValue = new Map(backendEntries.map((entry) => [entry.value, entry.slug]));
12
+ const backendSlugPluralByValue = new Map(backendEntries.map((entry) => [entry.value, entry.slugPlural]));
13
+ compareSets(errors, {
14
+ label: `${config.collectionLabel} values`,
15
+ actualLabel: `${webStructuredsPath} ${config.typeObject}`,
16
+ actualFile: webStructuredsPath,
17
+ actualValues: webEntries.map((entry) => entry.value),
18
+ expectedLabel: `${backendStructuredsPath} ${config.typeObject}`,
19
+ expectedFile: backendStructuredsPath,
20
+ expectedValues: backendEntries.map((entry) => entry.value),
21
+ });
22
+ for (const webEntry of webEntries) {
23
+ const backendSlug = backendSlugByValue.get(webEntry.value);
24
+ if (backendSlug && backendSlug !== webEntry.slug) {
25
+ errors.push(finiteEnumError(webStructuredsPath, `${config.typeObject}.${webEntry.value}.${config.slugProperty} is "${webEntry.slug}" but ${backendStructuredsPath} uses "${backendSlug}"`));
26
+ }
27
+ const backendSlugPlural = backendSlugPluralByValue.get(webEntry.value);
28
+ if (backendSlugPlural && backendSlugPlural !== webEntry.slugPlural) {
29
+ errors.push(finiteEnumError(webStructuredsPath, `${config.typeObject}.${webEntry.value}.${config.slugPluralProperty} is "${webEntry.slugPlural}" but ${backendStructuredsPath} uses "${backendSlugPlural}"`));
30
+ }
31
+ }
32
+ const topRouteSlugs = routePageSlugs(files.structuredDetailPages, true);
33
+ const routeSlugs = routePageSlugs(files.structuredDetailPages);
34
+ compareSets(errors, {
35
+ label: `${config.collectionLabel} route directories`,
36
+ actualLabel: config.routeLabels.detailTop,
37
+ actualFile: files.structuredDetailPages[0]?.file ?? backendStructuredsPath,
38
+ actualValues: topRouteSlugs,
39
+ expectedLabel: `${backendStructuredsPath} ${config.typeObject} slugs`,
40
+ expectedFile: backendStructuredsPath,
41
+ expectedValues: backendEntries.map((entry) => entry.slug),
42
+ });
43
+ compareSets(errors, {
44
+ label: `${config.collectionLabel} routed pages`,
45
+ actualLabel: config.routeLabels.detail,
46
+ actualFile: files.structuredDetailPages[0]?.file ?? backendStructuredsPath,
47
+ actualValues: routeSlugs,
48
+ expectedLabel: `${backendStructuredsPath} ${config.typeObject} slugs`,
49
+ expectedFile: backendStructuredsPath,
50
+ expectedValues: backendEntries.map((entry) => entry.slug),
51
+ });
52
+ checkStructuredRouteConfigs(errors, files, readTracked, config, backendEntries, backendSlugByValue);
53
+ for (const page of files.structuredDetailPages) {
54
+ for (const args of parseStructuredRouteFactoryArgs(readTracked(page.file), page.file, config.factoryCallPattern)) {
55
+ if (page.slug === args.slug)
56
+ continue;
57
+ errors.push(finiteEnumError(page.file, `${config.collectionLabel} route factory slug mismatch; route directory is "${page.slug}" but factory uses "${args.slug}"`));
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,2 @@
1
+ import type { FiniteEnumRippleConfig, ReadTrackedFile, RoutedPage } from './model.mts';
2
+ export declare function checkUnionFactories(errors: string[], pages: readonly RoutedPage[], readTracked: ReadTrackedFile, config: NonNullable<FiniteEnumRippleConfig['union']>, slugToType: ReadonlyMap<string, string>, routeSlugs: readonly string[]): void;
@@ -0,0 +1,34 @@
1
+ import { compareSets, finiteEnumError, uniqueSorted } from './compare.mjs';
2
+ import { parseUnionDetailRouteFactoryArgs } from './parsers.mjs';
3
+ export function checkUnionFactories(errors, pages, readTracked, config, slugToType, routeSlugs) {
4
+ const routeConfigsPath = config.routeConfigsPath;
5
+ const detailFactoryArgs = pages.flatMap((page) => parseUnionDetailRouteFactoryArgs(readTracked(page.file), page.file, config.factoryCallPattern).map((args) => ({
6
+ ...args,
7
+ file: page.file,
8
+ routeSlug: page.slug,
9
+ })));
10
+ compareSets(errors, {
11
+ label: `${config.collectionLabel} route factory slugs`,
12
+ actualLabel: config.routeLabels.factory,
13
+ actualFile: pages[0]?.file ?? routeConfigsPath,
14
+ actualValues: uniqueSorted(detailFactoryArgs.map((args) => args.slug)),
15
+ expectedLabel: config.routeLabels.detail,
16
+ expectedFile: pages[0]?.file ?? routeConfigsPath,
17
+ expectedValues: routeSlugs,
18
+ });
19
+ compareSets(errors, {
20
+ label: `${config.collectionLabel} route factory values`,
21
+ actualLabel: config.routeLabels.factory,
22
+ actualFile: pages[0]?.file ?? routeConfigsPath,
23
+ actualValues: uniqueSorted(detailFactoryArgs.map((args) => args.unionType)),
24
+ expectedLabel: `${routeConfigsPath} ${config.slugMapObject} values`,
25
+ expectedFile: routeConfigsPath,
26
+ expectedValues: [...slugToType.values()],
27
+ });
28
+ for (const args of detailFactoryArgs) {
29
+ const expectedType = slugToType.get(args.routeSlug);
30
+ if (args.slug === args.routeSlug && expectedType === args.unionType)
31
+ continue;
32
+ errors.push(finiteEnumError(args.file, `${config.collectionLabel} route factory args mismatch; route directory is "${args.routeSlug}" and expected type is "${expectedType ?? 'missing'}" but factory uses "${args.unionType}", "${args.slug}"`));
33
+ }
34
+ }
@@ -0,0 +1,2 @@
1
+ import type { FiniteEnumFiles, FiniteEnumRippleConfig, ReadTrackedFile } from './model.mts';
2
+ export declare function checkUnionTypes(errors: string[], files: FiniteEnumFiles, readTracked: ReadTrackedFile, config: NonNullable<FiniteEnumRippleConfig['union']>): void;
@@ -0,0 +1,129 @@
1
+ import { hasConfiguredObjectDeclaration } from './ast.mjs';
2
+ import { checkUnionFactories } from './union-factory-check.mjs';
3
+ import { checkCollectionPagePathLiterals, checkUnionCreatePageTypes, compareSets, finiteEnumError, hasAllFiles, routePageSlugs, uniqueSorted, } from './compare.mjs';
4
+ import { parseUnionRouteConfigEntries, parseUnionSlugToType, parseUnionTypeUnion, } from './parsers.mjs';
5
+ export function checkUnionTypes(errors, files, readTracked, config) {
6
+ const unionTypesPath = config.typesPath;
7
+ const routeConfigsPath = config.routeConfigsPath;
8
+ if (!hasAllFiles(files, [unionTypesPath, routeConfigsPath]))
9
+ return;
10
+ const unionTypes = parseUnionTypeUnion(readTracked(unionTypesPath), unionTypesPath, config.typeAlias);
11
+ const publicUnionTypes = unionTypes.filter((value) => !config.internalTypes.includes(value));
12
+ const routeConfigContent = readTracked(routeConfigsPath);
13
+ const slugToType = parseUnionSlugToType(routeConfigContent, routeConfigsPath, config.slugMapObject);
14
+ const topRouteSlugs = routePageSlugs(files.unionDetailPages, true);
15
+ const routeSlugs = routePageSlugs(files.unionDetailPages);
16
+ compareSets(errors, {
17
+ label: `${config.collectionLabel} route config values`,
18
+ actualLabel: `${routeConfigsPath} ${config.slugMapObject}`,
19
+ actualFile: routeConfigsPath,
20
+ actualValues: [...slugToType.values()],
21
+ expectedLabel: `${unionTypesPath} public ${config.typeAlias} values`,
22
+ expectedFile: unionTypesPath,
23
+ expectedValues: publicUnionTypes,
24
+ });
25
+ compareSets(errors, {
26
+ label: `${config.collectionLabel} route directories`,
27
+ actualLabel: config.routeLabels.detailTop,
28
+ actualFile: files.unionDetailPages[0]?.file ?? routeConfigsPath,
29
+ actualValues: topRouteSlugs,
30
+ expectedLabel: `${routeConfigsPath} ${config.slugMapObject} slugs`,
31
+ expectedFile: routeConfigsPath,
32
+ expectedValues: [...slugToType.keys()],
33
+ });
34
+ compareSets(errors, {
35
+ label: `${config.collectionLabel} routed pages`,
36
+ actualLabel: config.routeLabels.detail,
37
+ actualFile: files.unionDetailPages[0]?.file ?? routeConfigsPath,
38
+ actualValues: routeSlugs,
39
+ expectedLabel: `${routeConfigsPath} ${config.slugMapObject} slugs`,
40
+ expectedFile: routeConfigsPath,
41
+ expectedValues: [...slugToType.keys()],
42
+ });
43
+ if (hasConfiguredObjectDeclaration(routeConfigContent, config.routeConfigObject, routeConfigsPath)) {
44
+ const routeConfigs = parseUnionRouteConfigEntries(routeConfigContent, routeConfigsPath, config.routeConfigObject, config.typeArrayProperty, config.pluralPathProperty, config.singularPathProperty);
45
+ const typedRouteConfigs = routeConfigs.filter((routeConfig) => routeConfig.unionTypes.length > 0);
46
+ const effectiveSingleTypeRouteConfigs = routeConfigs.flatMap((routeConfig) => {
47
+ if (routeConfig.unionTypes.length === 1)
48
+ return [routeConfig];
49
+ if (!config.routeConfigExceptions.includes(routeConfig.key))
50
+ return [];
51
+ const inferredType = slugToType.get(routeConfig.singularPath);
52
+ return inferredType === undefined ? [] : [{ ...routeConfig, unionTypes: [inferredType] }];
53
+ });
54
+ for (const routeConfig of routeConfigs) {
55
+ if (routeConfig.unionTypes.length > 0 ||
56
+ config.routeConfigExceptions.includes(routeConfig.key))
57
+ continue;
58
+ errors.push(finiteEnumError(routeConfigsPath, `${config.routeConfigObject}.${routeConfig.key} has ${config.singularPathProperty} "${routeConfig.singularPath}" but does not declare ${config.typeArrayProperty}`));
59
+ }
60
+ for (const routeConfig of routeConfigs) {
61
+ if (routeConfig.key === routeConfig.pluralPath)
62
+ continue;
63
+ errors.push(finiteEnumError(routeConfigsPath, `${config.routeConfigObject}.${routeConfig.key} has ${config.pluralPathProperty} "${routeConfig.pluralPath}" but the route config key is "${routeConfig.key}"`));
64
+ }
65
+ compareSets(errors, {
66
+ label: `${config.collectionLabel} collection route config values`,
67
+ actualLabel: `${routeConfigsPath} ${config.routeConfigObject}`,
68
+ actualFile: routeConfigsPath,
69
+ actualValues: routeConfigs.flatMap((routeConfig) => {
70
+ if (routeConfig.unionTypes.length > 0)
71
+ return routeConfig.unionTypes;
72
+ if (!config.routeConfigExceptions.includes(routeConfig.key))
73
+ return [];
74
+ const inferredType = slugToType.get(routeConfig.singularPath);
75
+ return inferredType === undefined ? [] : [inferredType];
76
+ }),
77
+ expectedLabel: `${unionTypesPath} public ${config.typeAlias} values`,
78
+ expectedFile: unionTypesPath,
79
+ expectedValues: publicUnionTypes,
80
+ });
81
+ compareSets(errors, {
82
+ label: `${config.collectionLabel} collection route config singular paths`,
83
+ actualLabel: `${routeConfigsPath} ${config.routeConfigObject}`,
84
+ actualFile: routeConfigsPath,
85
+ actualValues: routeConfigs.flatMap((routeConfig) => {
86
+ if (routeConfig.unionTypes.length > 0)
87
+ return [routeConfig.singularPath];
88
+ if (config.routeConfigExceptions.includes(routeConfig.key) &&
89
+ slugToType.has(routeConfig.singularPath)) {
90
+ return [routeConfig.singularPath];
91
+ }
92
+ return [];
93
+ }),
94
+ expectedLabel: `${routeConfigsPath} ${config.slugMapObject} slugs`,
95
+ expectedFile: routeConfigsPath,
96
+ expectedValues: [...slugToType.keys()],
97
+ });
98
+ for (const routeConfig of typedRouteConfigs) {
99
+ const expectedType = slugToType.get(routeConfig.singularPath);
100
+ if (expectedType !== undefined &&
101
+ routeConfig.unionTypes.length === 1 &&
102
+ routeConfig.unionTypes[0] === expectedType) {
103
+ continue;
104
+ }
105
+ errors.push(finiteEnumError(routeConfigsPath, `${config.routeConfigObject}.${routeConfig.key} maps ${config.singularPathProperty} "${routeConfig.singularPath}" to [${routeConfig.unionTypes.join(', ')}] but ${config.slugMapObject} expects "${expectedType ?? 'missing'}"`));
106
+ }
107
+ compareSets(errors, {
108
+ label: `${config.collectionLabel} top-level collection route directories`,
109
+ actualLabel: config.routeLabels.collectionTop,
110
+ actualFile: files.unionCollectionPages[0]?.file ?? routeConfigsPath,
111
+ actualValues: routePageSlugs(files.unionCollectionPages, true),
112
+ expectedLabel: `${routeConfigsPath} ${config.routeConfigObject} ${config.pluralPathProperty}`,
113
+ expectedFile: routeConfigsPath,
114
+ expectedValues: routeConfigs.map((routeConfig) => routeConfig.pluralPath),
115
+ });
116
+ compareSets(errors, {
117
+ label: `${config.collectionLabel} collection routed pages`,
118
+ actualLabel: config.routeLabels.collection,
119
+ actualFile: files.unionCollectionPages[0]?.file ?? routeConfigsPath,
120
+ actualValues: uniqueSorted(files.unionCollectionPages.map((page) => page.slug)),
121
+ expectedLabel: `${routeConfigsPath} ${config.routeConfigObject} ${config.pluralPathProperty}`,
122
+ expectedFile: routeConfigsPath,
123
+ expectedValues: routeConfigs.map((routeConfig) => routeConfig.pluralPath),
124
+ });
125
+ checkUnionCreatePageTypes(errors, effectiveSingleTypeRouteConfigs, files.unionCreatePages, readTracked, config.createPageTypeProperties, config.collectionLabel);
126
+ checkCollectionPagePathLiterals(files.unionCollectionPages, errors, config.collectionPathLabel ?? config.collectionLabel, new Set(effectiveSingleTypeRouteConfigs.map((routeConfig) => routeConfig.pluralPath)), readTracked, config.collectionPathLiteralPattern);
127
+ }
128
+ checkUnionFactories(errors, files.unionDetailPages, readTracked, config, slugToType, routeSlugs);
129
+ }
@@ -9,7 +9,7 @@ function queryParameter(name, descriptor) {
9
9
  return {
10
10
  name,
11
11
  in: 'query',
12
- required: false,
12
+ required: descriptor.required === true,
13
13
  ...(descriptor.description ? { description: descriptor.description } : {}),
14
14
  ...(descriptor.kind === 'csv-array'
15
15
  ? { style: descriptor.style, explode: descriptor.explode }
@@ -29,7 +29,7 @@ export type OpenApiParameter = {
29
29
  } | {
30
30
  name: string;
31
31
  in: 'query';
32
- required: false;
32
+ required: boolean;
33
33
  description?: string;
34
34
  style?: 'form';
35
35
  explode?: false;
@@ -1,4 +1,4 @@
1
- export type OpenApiQueryParameter = {
1
+ export type OpenApiQueryParameterShape = {
2
2
  kind: 'string';
3
3
  format?: 'uuid' | 'uri';
4
4
  description?: string;
@@ -27,9 +27,15 @@ export type OpenApiQueryParameter = {
27
27
  description?: string;
28
28
  } | {
29
29
  kind: 'csv-array';
30
- items: OpenApiQueryParameter;
30
+ items: OpenApiQueryParameterShape & {
31
+ required?: never;
32
+ };
31
33
  style: 'form';
32
34
  explode: false;
33
35
  description?: string;
34
36
  };
37
+ /** Top-level parameters may be `required: true`; array `items` never are. */
38
+ export type OpenApiQueryParameter = OpenApiQueryParameterShape & {
39
+ required?: true;
40
+ };
35
41
  export type OpenApiQueryContract = Readonly<Record<string, OpenApiQueryParameter>>;
@@ -0,0 +1,5 @@
1
+ export { analyzePostPublicationWriterSource } from './writers/source.mts';
2
+ export type { PostPublicationWriterSourceAnalysis } from './writers/source.mts';
3
+ export type { PostPublicationWriterSourceOptions } from './writers/types.mts';
4
+ export { extractStaticSqlTemplateQuasis } from './readers/sql-source.mts';
5
+ export type { ReaderSqlTemplateOptions } from './readers/sql-template.mts';
@@ -0,0 +1,2 @@
1
+ export { analyzePostPublicationWriterSource } from './writers/source.mjs';
2
+ export { extractStaticSqlTemplateQuasis } from './readers/sql-source.mjs';
@@ -0,0 +1,3 @@
1
+ import ts from '../../contract-schema/typescript-api.mts';
2
+ import { type ReaderSqlTemplateOptions } from './sql-template.mts';
3
+ export declare function terminalSqlExecutorBindings(ast: ts.SourceFile, options: ReaderSqlTemplateOptions): Set<string>;
@@ -0,0 +1,32 @@
1
+ import ts from '../../contract-schema/typescript-api.mjs';
2
+ import { identifierName, unparenthesized, walk, } from './sql-template.mjs';
3
+ export function terminalSqlExecutorBindings(ast, options) {
4
+ const bindings = new Set();
5
+ walk(ast, (node) => {
6
+ if (!ts.isImportDeclaration(node) || !ts.isStringLiteral(node.moduleSpecifier))
7
+ return;
8
+ const names = options.executorImports.get(node.moduleSpecifier.text);
9
+ const importClause = node.importClause;
10
+ const specifiers = importClause?.namedBindings;
11
+ if (importClause?.isTypeOnly)
12
+ return;
13
+ if (!names || !specifiers || !ts.isNamedImports(specifiers))
14
+ return;
15
+ for (const specifier of specifiers.elements) {
16
+ if (!specifier.isTypeOnly && names.has((specifier.propertyName ?? specifier.name).text))
17
+ bindings.add(specifier.name.text);
18
+ }
19
+ });
20
+ walk(ast, (node) => {
21
+ if (!ts.isVariableDeclaration(node) || !ts.isIdentifier(node.name))
22
+ return;
23
+ const initializer = node.initializer && unparenthesized(node.initializer);
24
+ if (!initializer || !ts.isConditionalExpression(initializer))
25
+ return;
26
+ const consequent = identifierName(initializer.whenTrue);
27
+ const alternate = identifierName(initializer.whenFalse);
28
+ if ((consequent && bindings.has(consequent)) || (alternate && bindings.has(alternate)))
29
+ bindings.add(node.name.text);
30
+ });
31
+ return bindings;
32
+ }
@@ -0,0 +1,7 @@
1
+ import { type ReaderSqlTemplateOptions } from './sql-template.mts';
2
+ /**
3
+ * Collect static SQL fragments returned, exported, or passed to configured executors.
4
+ * Interpolations become numbered placeholders; local append chains are reassembled.
5
+ * This preserves name-based collection heuristics, not binding or control-flow proof.
6
+ */
7
+ export declare function extractStaticSqlTemplateQuasis(content: string, options: ReaderSqlTemplateOptions): string[];
@@ -0,0 +1,87 @@
1
+ import ts from '../../contract-schema/typescript-api.mjs';
2
+ import { terminalSqlExecutorBindings } from './sql-executors.mjs';
3
+ import { identifierName, staticComputedPropertyName, staticSqlTemplateText, unparenthesized, walk, } from './sql-template.mjs';
4
+ /**
5
+ * Collect static SQL fragments returned, exported, or passed to configured executors.
6
+ * Interpolations become numbered placeholders; local append chains are reassembled.
7
+ * This preserves name-based collection heuristics, not binding or control-flow proof.
8
+ */
9
+ export function extractStaticSqlTemplateQuasis(content, options) {
10
+ // TypeScript's source-file parser recovers from syntax errors. Reject those first,
11
+ // preserving the previous strict parser's failure contract instead of trusting recovery.
12
+ const result = ts.transpileModule(content, {
13
+ fileName: 'reader.mts',
14
+ reportDiagnostics: true,
15
+ compilerOptions: { target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext },
16
+ });
17
+ if (result.diagnostics?.length)
18
+ throw new SyntaxError(ts.flattenDiagnosticMessageText(result.diagnostics[0].messageText, '\n'));
19
+ const ast = ts.createSourceFile('reader.mts', content, ts.ScriptTarget.Latest, true);
20
+ const executors = terminalSqlExecutorBindings(ast, options);
21
+ const statements = new Map();
22
+ const consumed = new Set();
23
+ const direct = [];
24
+ walk(ast, (node) => {
25
+ if (!ts.isVariableDeclaration(node))
26
+ return;
27
+ const binding = identifierName(node.name);
28
+ const text = staticSqlTemplateText(node.initializer, options);
29
+ if (binding && text)
30
+ statements.set(binding, { text, offset: node.getStart(ast) });
31
+ });
32
+ walk(ast, (node) => {
33
+ if (ts.isVariableStatement(node) &&
34
+ node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
35
+ for (const declaration of node.declarationList.declarations) {
36
+ const binding = identifierName(declaration.name);
37
+ if (binding)
38
+ consumed.add(binding);
39
+ }
40
+ }
41
+ if (ts.isReturnStatement(node) && node.expression) {
42
+ const binding = identifierName(node.expression);
43
+ if (binding)
44
+ consumed.add(binding);
45
+ const text = staticSqlTemplateText(node.expression, options);
46
+ if (text)
47
+ direct.push(text);
48
+ return;
49
+ }
50
+ if (!ts.isCallExpression(node))
51
+ return;
52
+ const callee = unparenthesized(node.expression);
53
+ if (!ts.isIdentifier(callee) || !executors.has(callee.text))
54
+ return;
55
+ for (const argument of node.arguments) {
56
+ const binding = identifierName(argument);
57
+ if (binding)
58
+ consumed.add(binding);
59
+ const text = staticSqlTemplateText(argument, options);
60
+ if (text)
61
+ direct.push(text);
62
+ }
63
+ });
64
+ walk(ast, (node) => {
65
+ if (!ts.isCallExpression(node))
66
+ return;
67
+ const callee = unparenthesized(node.expression);
68
+ if (!ts.isPropertyAccessExpression(callee) && !ts.isElementAccessExpression(callee))
69
+ return;
70
+ const method = ts.isPropertyAccessExpression(callee)
71
+ ? callee.name.text
72
+ : callee.argumentExpression
73
+ ? staticComputedPropertyName(callee.argumentExpression)
74
+ : undefined;
75
+ if (method !== options.appendMethod)
76
+ return;
77
+ const receiver = identifierName(callee.expression);
78
+ const statement = receiver ? statements.get(receiver) : undefined;
79
+ const appended = staticSqlTemplateText(node.arguments[0], options);
80
+ if (statement && appended && node.getStart(ast) > statement.offset)
81
+ statement.text += appended;
82
+ });
83
+ return [
84
+ ...direct,
85
+ ...[...statements].flatMap(([binding, statement]) => consumed.has(binding) ? [statement.text] : []),
86
+ ];
87
+ }
@@ -0,0 +1,12 @@
1
+ import ts from '../../contract-schema/typescript-api.mts';
2
+ export interface ReaderSqlTemplateOptions {
3
+ readonly templateTag: string;
4
+ readonly appendMethod: string;
5
+ readonly executorImports: ReadonlyMap<string, ReadonlySet<string>>;
6
+ readonly placeholderPrefix: string;
7
+ }
8
+ export declare function identifierName(node: ts.Node): string | undefined;
9
+ export declare function staticComputedPropertyName(node: ts.Node): string | undefined;
10
+ export declare function unparenthesized(node: ts.Node): ts.Node;
11
+ export declare function staticSqlTemplateText(node: ts.Node | undefined, options: ReaderSqlTemplateOptions): string | undefined;
12
+ export declare function walk(root: ts.Node, visit: (node: ts.Node) => void): void;