@thymian/plugin-sampler 0.3.0-canary.20261007-e6cb8dc → 0.3.0-canary.20261007-cdbe1d3

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 (264) hide show
  1. package/dist/cli/check-result.d.ts +88 -0
  2. package/dist/cli/check-result.d.ts.map +1 -0
  3. package/dist/cli/check-result.js +160 -0
  4. package/dist/cli/check-result.js.map +1 -0
  5. package/dist/cli/commands/sampler/check.d.ts +32 -4
  6. package/dist/cli/commands/sampler/check.d.ts.map +1 -1
  7. package/dist/cli/commands/sampler/check.js +126 -88
  8. package/dist/cli/commands/sampler/check.js.map +1 -1
  9. package/dist/cli/commands/sampler/init.d.ts +4 -6
  10. package/dist/cli/commands/sampler/init.d.ts.map +1 -1
  11. package/dist/cli/commands/sampler/init.js +33 -49
  12. package/dist/cli/commands/sampler/init.js.map +1 -1
  13. package/dist/cli/commands/sampler/show.d.ts +11 -0
  14. package/dist/cli/commands/sampler/show.d.ts.map +1 -0
  15. package/dist/cli/commands/sampler/show.js +36 -0
  16. package/dist/cli/commands/sampler/show.js.map +1 -0
  17. package/dist/cli/commands/sampler/sync.d.ts +11 -0
  18. package/dist/cli/commands/sampler/sync.d.ts.map +1 -0
  19. package/dist/cli/commands/sampler/sync.js +65 -0
  20. package/dist/cli/commands/sampler/sync.js.map +1 -0
  21. package/dist/cli/commands/sampler/validate.d.ts +3 -10
  22. package/dist/cli/commands/sampler/validate.d.ts.map +1 -1
  23. package/dist/cli/commands/sampler/validate.js +57 -126
  24. package/dist/cli/commands/sampler/validate.js.map +1 -1
  25. package/dist/generation/closed-schema.d.ts +22 -0
  26. package/dist/generation/closed-schema.d.ts.map +1 -0
  27. package/dist/generation/closed-schema.js +45 -0
  28. package/dist/generation/closed-schema.js.map +1 -0
  29. package/dist/generation/content-type-strategies/json.content-type-strategy.d.ts.map +1 -1
  30. package/dist/generation/content-type-strategies/json.content-type-strategy.js +3 -1
  31. package/dist/generation/content-type-strategies/json.content-type-strategy.js.map +1 -1
  32. package/dist/generation/content-type-strategies/xml.content-type-strategy.d.ts.map +1 -1
  33. package/dist/generation/content-type-strategies/xml.content-type-strategy.js +3 -1
  34. package/dist/generation/content-type-strategies/xml.content-type-strategy.js.map +1 -1
  35. package/dist/generation/generate-request-sample.d.ts +12 -0
  36. package/dist/generation/generate-request-sample.d.ts.map +1 -0
  37. package/dist/generation/{generate-samples.js → generate-request-sample.js} +11 -4
  38. package/dist/generation/generate-request-sample.js.map +1 -0
  39. package/dist/generation/request-generators/default-request-generator.d.ts +14 -0
  40. package/dist/generation/request-generators/default-request-generator.d.ts.map +1 -1
  41. package/dist/generation/request-generators/default-request-generator.js +18 -1
  42. package/dist/generation/request-generators/default-request-generator.js.map +1 -1
  43. package/dist/generation/types/declaration-set.d.ts +124 -0
  44. package/dist/generation/types/declaration-set.d.ts.map +1 -0
  45. package/dist/generation/types/declaration-set.js +389 -0
  46. package/dist/generation/types/declaration-set.js.map +1 -0
  47. package/dist/generation/types/example-reflection.d.ts +21 -0
  48. package/dist/generation/types/example-reflection.d.ts.map +1 -0
  49. package/dist/generation/types/example-reflection.js +135 -0
  50. package/dist/generation/types/example-reflection.js.map +1 -0
  51. package/dist/generation/types/generate-type-surface.d.ts +23 -0
  52. package/dist/generation/types/generate-type-surface.d.ts.map +1 -0
  53. package/dist/generation/types/generate-type-surface.js +671 -0
  54. package/dist/generation/types/generate-type-surface.js.map +1 -0
  55. package/dist/generation/types/schema-type.d.ts +20 -0
  56. package/dist/generation/types/schema-type.d.ts.map +1 -0
  57. package/dist/generation/types/schema-type.js +203 -0
  58. package/dist/generation/types/schema-type.js.map +1 -0
  59. package/dist/generation/types/self-check-surface.d.ts +32 -0
  60. package/dist/generation/types/self-check-surface.d.ts.map +1 -0
  61. package/dist/generation/types/self-check-surface.js +92 -0
  62. package/dist/generation/types/self-check-surface.js.map +1 -0
  63. package/dist/generation/types/type-names.d.ts +135 -0
  64. package/dist/generation/types/type-names.d.ts.map +1 -0
  65. package/dist/generation/types/type-names.js +213 -0
  66. package/dist/generation/types/type-names.js.map +1 -0
  67. package/dist/generation/types/write-type-surface.d.ts +58 -0
  68. package/dist/generation/types/write-type-surface.d.ts.map +1 -0
  69. package/dist/generation/types/write-type-surface.js +119 -0
  70. package/dist/generation/types/write-type-surface.js.map +1 -0
  71. package/dist/hooks/create-hook-utils.d.ts +22 -6
  72. package/dist/hooks/create-hook-utils.d.ts.map +1 -1
  73. package/dist/hooks/create-hook-utils.js +74 -96
  74. package/dist/hooks/create-hook-utils.js.map +1 -1
  75. package/dist/hooks/hook-api.d.ts +49 -0
  76. package/dist/hooks/hook-api.d.ts.map +1 -0
  77. package/dist/hooks/hook-api.js +105 -0
  78. package/dist/hooks/hook-api.js.map +1 -0
  79. package/dist/hooks/hook-diagnostics.d.ts +45 -0
  80. package/dist/hooks/hook-diagnostics.d.ts.map +1 -0
  81. package/dist/hooks/hook-diagnostics.js +80 -0
  82. package/dist/hooks/hook-diagnostics.js.map +1 -0
  83. package/dist/hooks/hook-errors.d.ts +33 -0
  84. package/dist/hooks/hook-errors.d.ts.map +1 -1
  85. package/dist/hooks/hook-errors.js +46 -0
  86. package/dist/hooks/hook-errors.js.map +1 -1
  87. package/dist/hooks/hook-registration.d.ts +108 -0
  88. package/dist/hooks/hook-registration.d.ts.map +1 -0
  89. package/dist/hooks/hook-registration.js +69 -0
  90. package/dist/hooks/hook-registration.js.map +1 -0
  91. package/dist/hooks/hook-runner.d.ts +111 -11
  92. package/dist/hooks/hook-runner.d.ts.map +1 -1
  93. package/dist/hooks/hook-runner.js +309 -142
  94. package/dist/hooks/hook-runner.js.map +1 -1
  95. package/dist/hooks/hook-utils-factory.d.ts +19 -0
  96. package/dist/hooks/hook-utils-factory.d.ts.map +1 -0
  97. package/dist/hooks/hook-utils-factory.js +2 -0
  98. package/dist/hooks/hook-utils-factory.js.map +1 -0
  99. package/dist/hooks/hook-utils.d.ts +80 -12
  100. package/dist/hooks/hook-utils.d.ts.map +1 -1
  101. package/dist/hooks/invoke-hook.d.ts +71 -0
  102. package/dist/hooks/invoke-hook.d.ts.map +1 -0
  103. package/dist/hooks/invoke-hook.js +161 -0
  104. package/dist/hooks/invoke-hook.js.map +1 -0
  105. package/dist/hooks/load-user-hooks.d.ts +96 -0
  106. package/dist/hooks/load-user-hooks.d.ts.map +1 -0
  107. package/dist/hooks/load-user-hooks.js +432 -0
  108. package/dist/hooks/load-user-hooks.js.map +1 -0
  109. package/dist/hooks/nested-request.d.ts +21 -0
  110. package/dist/hooks/nested-request.d.ts.map +1 -0
  111. package/dist/hooks/nested-request.js +24 -0
  112. package/dist/hooks/nested-request.js.map +1 -0
  113. package/dist/hooks/overlay-args.d.ts +40 -0
  114. package/dist/hooks/overlay-args.d.ts.map +1 -0
  115. package/dist/hooks/overlay-args.js +84 -0
  116. package/dist/hooks/overlay-args.js.map +1 -0
  117. package/dist/hooks/resolve-targeting.d.ts +16 -0
  118. package/dist/hooks/resolve-targeting.d.ts.map +1 -0
  119. package/dist/hooks/resolve-targeting.js +168 -0
  120. package/dist/hooks/resolve-targeting.js.map +1 -0
  121. package/dist/hooks/run-scoped-hooks.d.ts +83 -0
  122. package/dist/hooks/run-scoped-hooks.d.ts.map +1 -0
  123. package/dist/hooks/run-scoped-hooks.js +153 -0
  124. package/dist/hooks/run-scoped-hooks.js.map +1 -0
  125. package/dist/index.d.ts +42 -19
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +152 -165
  128. package/dist/index.js.map +1 -1
  129. package/dist/request-sampler.d.ts +40 -11
  130. package/dist/request-sampler.d.ts.map +1 -1
  131. package/dist/request-sampler.js +51 -76
  132. package/dist/request-sampler.js.map +1 -1
  133. package/dist/sampler-paths.d.ts +19 -0
  134. package/dist/sampler-paths.d.ts.map +1 -0
  135. package/dist/sampler-paths.js +22 -0
  136. package/dist/sampler-paths.js.map +1 -0
  137. package/dist/selectors/nearest-paths.d.ts +14 -0
  138. package/dist/selectors/nearest-paths.d.ts.map +1 -0
  139. package/dist/selectors/nearest-paths.js +32 -0
  140. package/dist/selectors/nearest-paths.js.map +1 -0
  141. package/dist/selectors/path-glob.d.ts +94 -0
  142. package/dist/selectors/path-glob.d.ts.map +1 -0
  143. package/dist/selectors/path-glob.js +136 -0
  144. package/dist/selectors/path-glob.js.map +1 -0
  145. package/dist/selectors/selector-errors.d.ts +17 -0
  146. package/dist/selectors/selector-errors.d.ts.map +1 -0
  147. package/dist/selectors/selector-errors.js +139 -0
  148. package/dist/selectors/selector-errors.js.map +1 -0
  149. package/dist/selectors/selector.d.ts +55 -0
  150. package/dist/selectors/selector.d.ts.map +1 -0
  151. package/dist/selectors/selector.js +305 -0
  152. package/dist/selectors/selector.js.map +1 -0
  153. package/dist/selectors/transaction-catalog.d.ts +96 -0
  154. package/dist/selectors/transaction-catalog.d.ts.map +1 -0
  155. package/dist/selectors/transaction-catalog.js +193 -0
  156. package/dist/selectors/transaction-catalog.js.map +1 -0
  157. package/dist/selectors/transaction-filter.d.ts +119 -0
  158. package/dist/selectors/transaction-filter.d.ts.map +1 -0
  159. package/dist/selectors/transaction-filter.js +191 -0
  160. package/dist/selectors/transaction-filter.js.map +1 -0
  161. package/dist/ts-path.d.ts +36 -0
  162. package/dist/ts-path.d.ts.map +1 -0
  163. package/dist/ts-path.js +46 -0
  164. package/dist/ts-path.js.map +1 -0
  165. package/dist/utils.d.ts +1 -2
  166. package/dist/utils.d.ts.map +1 -1
  167. package/dist/utils.js +2 -20
  168. package/dist/utils.js.map +1 -1
  169. package/dist/validation/canonicalize.d.ts +17 -0
  170. package/dist/validation/canonicalize.d.ts.map +1 -0
  171. package/dist/validation/canonicalize.js +29 -0
  172. package/dist/validation/canonicalize.js.map +1 -0
  173. package/dist/validation/shared-compiler-host.d.ts +17 -0
  174. package/dist/validation/shared-compiler-host.d.ts.map +1 -0
  175. package/dist/validation/shared-compiler-host.js +58 -0
  176. package/dist/validation/shared-compiler-host.js.map +1 -0
  177. package/dist/validation/typecheck-hooks.d.ts +37 -0
  178. package/dist/validation/typecheck-hooks.d.ts.map +1 -0
  179. package/dist/validation/typecheck-hooks.js +169 -0
  180. package/dist/validation/typecheck-hooks.js.map +1 -0
  181. package/dist/validation/validate-sampler.d.ts +79 -0
  182. package/dist/validation/validate-sampler.d.ts.map +1 -0
  183. package/dist/validation/validate-sampler.js +89 -0
  184. package/dist/validation/validate-sampler.js.map +1 -0
  185. package/package.json +7 -11
  186. package/dist/cli/commands/sampler/generate/hook.d.ts +0 -10
  187. package/dist/cli/commands/sampler/generate/hook.d.ts.map +0 -1
  188. package/dist/cli/commands/sampler/generate/hook.js +0 -19
  189. package/dist/cli/commands/sampler/generate/hook.js.map +0 -1
  190. package/dist/cli/generate-hook.d.ts +0 -4
  191. package/dist/cli/generate-hook.d.ts.map +0 -1
  192. package/dist/cli/generate-hook.js +0 -96
  193. package/dist/cli/generate-hook.js.map +0 -1
  194. package/dist/cli/templates.d.ts +0 -4
  195. package/dist/cli/templates.d.ts.map +0 -1
  196. package/dist/cli/templates.js +0 -28
  197. package/dist/cli/templates.js.map +0 -1
  198. package/dist/constants.d.ts +0 -12
  199. package/dist/constants.d.ts.map +0 -1
  200. package/dist/constants.js +0 -16
  201. package/dist/constants.js.map +0 -1
  202. package/dist/generation/generate-samples-for-thymian-format.d.ts +0 -4
  203. package/dist/generation/generate-samples-for-thymian-format.d.ts.map +0 -1
  204. package/dist/generation/generate-samples-for-thymian-format.js +0 -18
  205. package/dist/generation/generate-samples-for-thymian-format.js.map +0 -1
  206. package/dist/generation/generate-samples.d.ts +0 -4
  207. package/dist/generation/generate-samples.d.ts.map +0 -1
  208. package/dist/generation/generate-samples.js.map +0 -1
  209. package/dist/hooks/generate-request-types.d.ts +0 -34
  210. package/dist/hooks/generate-request-types.d.ts.map +0 -1
  211. package/dist/hooks/generate-request-types.js +0 -458
  212. package/dist/hooks/generate-request-types.js.map +0 -1
  213. package/dist/hooks/hook-types.d.ts +0 -7
  214. package/dist/hooks/hook-types.d.ts.map +0 -1
  215. package/dist/hooks/hook-types.js +0 -2
  216. package/dist/hooks/hook-types.js.map +0 -1
  217. package/dist/hooks/load-hooks-from-samples.d.ts +0 -3
  218. package/dist/hooks/load-hooks-from-samples.d.ts.map +0 -1
  219. package/dist/hooks/load-hooks-from-samples.js +0 -22
  220. package/dist/hooks/load-hooks-from-samples.js.map +0 -1
  221. package/dist/hooks/ts-config.d.ts +0 -10
  222. package/dist/hooks/ts-config.d.ts.map +0 -1
  223. package/dist/hooks/ts-config.js +0 -10
  224. package/dist/hooks/ts-config.js.map +0 -1
  225. package/dist/samples-structure/file-request-sample.d.ts +0 -20
  226. package/dist/samples-structure/file-request-sample.d.ts.map +0 -1
  227. package/dist/samples-structure/file-request-sample.js +0 -5
  228. package/dist/samples-structure/file-request-sample.js.map +0 -1
  229. package/dist/samples-structure/get-path-transaction-id.d.ts +0 -3
  230. package/dist/samples-structure/get-path-transaction-id.d.ts.map +0 -1
  231. package/dist/samples-structure/get-path-transaction-id.js +0 -22
  232. package/dist/samples-structure/get-path-transaction-id.js.map +0 -1
  233. package/dist/samples-structure/merge-tree.d.ts +0 -5
  234. package/dist/samples-structure/merge-tree.d.ts.map +0 -1
  235. package/dist/samples-structure/merge-tree.js +0 -118
  236. package/dist/samples-structure/merge-tree.js.map +0 -1
  237. package/dist/samples-structure/read-samples-from-dir.d.ts +0 -13
  238. package/dist/samples-structure/read-samples-from-dir.d.ts.map +0 -1
  239. package/dist/samples-structure/read-samples-from-dir.js +0 -205
  240. package/dist/samples-structure/read-samples-from-dir.js.map +0 -1
  241. package/dist/samples-structure/samples-from-transactions.d.ts +0 -5
  242. package/dist/samples-structure/samples-from-transactions.d.ts.map +0 -1
  243. package/dist/samples-structure/samples-from-transactions.js +0 -120
  244. package/dist/samples-structure/samples-from-transactions.js.map +0 -1
  245. package/dist/samples-structure/samples-tree-structure.d.ts +0 -106
  246. package/dist/samples-structure/samples-tree-structure.d.ts.map +0 -1
  247. package/dist/samples-structure/samples-tree-structure.js +0 -26
  248. package/dist/samples-structure/samples-tree-structure.js.map +0 -1
  249. package/dist/samples-structure/structure-meta-on-disc.d.ts +0 -11
  250. package/dist/samples-structure/structure-meta-on-disc.d.ts.map +0 -1
  251. package/dist/samples-structure/structure-meta-on-disc.js +0 -2
  252. package/dist/samples-structure/structure-meta-on-disc.js.map +0 -1
  253. package/dist/samples-structure/traverse.d.ts +0 -4
  254. package/dist/samples-structure/traverse.d.ts.map +0 -1
  255. package/dist/samples-structure/traverse.js +0 -13
  256. package/dist/samples-structure/traverse.js.map +0 -1
  257. package/dist/samples-structure/write-samples-to-dir.d.ts +0 -37
  258. package/dist/samples-structure/write-samples-to-dir.d.ts.map +0 -1
  259. package/dist/samples-structure/write-samples-to-dir.js +0 -183
  260. package/dist/samples-structure/write-samples-to-dir.js.map +0 -1
  261. package/dist/validation/validate-sampler-output.d.ts +0 -38
  262. package/dist/validation/validate-sampler-output.d.ts.map +0 -1
  263. package/dist/validation/validate-sampler-output.js +0 -288
  264. package/dist/validation/validate-sampler-output.js.map +0 -1
@@ -0,0 +1,119 @@
1
+ import type { ThymianHttpTransaction } from '@thymian/core';
2
+ /**
3
+ * A path glob, by shape.
4
+ *
5
+ * A string with no `*` is not a `PathGlob`, so it has to be an exact path —
6
+ * which is what keeps a typo'd exact path a compile error while a glob stays a
7
+ * plain string. The shape type never touches the `Path` union: validating a
8
+ * glob *against* the union was measured and rejected on language-server cost,
9
+ * and vacuousness is checked at run and validate time instead.
10
+ *
11
+ * {@link PATH_GLOB_SOURCE} is the same type as text, so the generated surface
12
+ * and this module cannot drift on it.
13
+ */
14
+ export type PathGlob = `${string}*${string}`;
15
+ /** {@link PathGlob} as source text, for the generated declaration file. */
16
+ export declare const PATH_GLOB_SOURCE = "`${string}*${string}`";
17
+ /**
18
+ * The fields a filter can constrain, as the runtime sees them.
19
+ *
20
+ * Every one is a closed, spec-derived union in the *generated* surface, which is
21
+ * what makes a stale value a compile error. Here they are the widest thing a
22
+ * `.js` hook file could pass, because that file is not type-checked — so the
23
+ * runtime validates shapes itself rather than trusting them.
24
+ */
25
+ export type FilterFields = {
26
+ readonly method?: string | readonly string[];
27
+ readonly status?: number | readonly number[];
28
+ readonly statusClass?: string | readonly string[];
29
+ readonly path?: string | PathGlob | readonly (string | PathGlob)[];
30
+ readonly requestMediaType?: string | readonly string[];
31
+ readonly responseMediaType?: string | readonly string[];
32
+ };
33
+ /**
34
+ * Targets a set of Transactions.
35
+ *
36
+ * Fields **AND**-combine; an array within a field **OR**-combines. `not` takes
37
+ * filter fields and excludes anything matching them — one level deep by
38
+ * construction, because `FilterFields` has no `not` of its own.
39
+ */
40
+ export type TransactionFilter = FilterFields & {
41
+ readonly not?: FilterFields | readonly FilterFields[];
42
+ };
43
+ declare const FIELDS: {
44
+ method: {
45
+ valid: (value: unknown) => value is string;
46
+ expected: string;
47
+ accepts: (value: unknown, { thymianReq }: ThymianHttpTransaction) => boolean;
48
+ };
49
+ status: {
50
+ valid: (value: unknown) => boolean;
51
+ expected: string;
52
+ accepts: (value: unknown, { thymianRes }: ThymianHttpTransaction) => boolean;
53
+ };
54
+ statusClass: {
55
+ valid: (value: unknown) => boolean;
56
+ expected: string;
57
+ accepts: (value: unknown, { thymianRes }: ThymianHttpTransaction) => boolean;
58
+ };
59
+ path: {
60
+ valid: (value: unknown) => value is string;
61
+ expected: string;
62
+ accepts: (value: unknown, transaction: ThymianHttpTransaction) => boolean;
63
+ };
64
+ requestMediaType: {
65
+ valid: (value: unknown) => value is string;
66
+ expected: string;
67
+ accepts: (value: unknown, { thymianReq }: ThymianHttpTransaction) => boolean;
68
+ };
69
+ responseMediaType: {
70
+ valid: (value: unknown) => value is string;
71
+ expected: string;
72
+ accepts: (value: unknown, { thymianRes }: ThymianHttpTransaction) => boolean;
73
+ };
74
+ };
75
+ export type FilterFieldName = keyof typeof FIELDS;
76
+ /** The path a filter compares against: the description's own spelling. */
77
+ export declare function pathOf(transaction: ThymianHttpTransaction): string;
78
+ /**
79
+ * Whether `value` is shaped like a filter.
80
+ *
81
+ * One known field is enough. Requiring *every* key to be known meant a single
82
+ * typo — `{ path: '/v1/**', tag: 'x' }` — was reported as "not a filter at
83
+ * all", which sends the reader looking for a selector they never wrote;
84
+ * {@link filterProblems} names the unknown key instead.
85
+ */
86
+ export declare function isTransactionFilter(value: unknown): value is TransactionFilter;
87
+ /** One thing wrong with a filter, in the words a diagnostic uses. */
88
+ export type FilterProblem = string;
89
+ /**
90
+ * Everything wrong with a filter's own text, before any Transaction is
91
+ * consulted.
92
+ *
93
+ * Separate from matching because a filter that cannot mean anything is a
94
+ * different fault from one that means something nothing satisfies. Reporting a
95
+ * mistyped `'4xx'` as "all valid but intersects nothing" is the wrong sentence,
96
+ * and letting `{ method: 42 }` through produced a bare `TypeError` naming no
97
+ * file at all.
98
+ */
99
+ export declare function filterProblems(filter: TransactionFilter): FilterProblem[];
100
+ /**
101
+ * Field names `fields` gives an explicit, empty value list — a disjunction of
102
+ * zero alternatives, which {@link matchesFields} treats as always false.
103
+ *
104
+ * Read back from the same rule {@link matchesFields} applies, rather than a
105
+ * second definition of "empty": a field that is merely absent is not named
106
+ * here, only one that was given a value and that value is an empty array.
107
+ */
108
+ export declare function emptyValueFields(fields: FilterFields): FilterFieldName[];
109
+ /**
110
+ * `matches(t) = positive(t) && !not.some(n => n(t))`.
111
+ *
112
+ * An empty positive side matches everything, which is what makes
113
+ * `{ not: { … } }` mean "all but these" without a second spelling.
114
+ */
115
+ export declare function matchesTransactionFilter(filter: TransactionFilter, transaction: ThymianHttpTransaction): boolean;
116
+ /** Every path value a filter mentions, including the ones inside `not`. */
117
+ export declare function pathValuesOf(filter: TransactionFilter): readonly string[];
118
+ export {};
119
+ //# sourceMappingURL=transaction-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction-filter.d.ts","sourceRoot":"","sources":["../../src/selectors/transaction-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAK5D;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,QAAQ,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAE7C,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAClD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;IACnE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACvD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;CACzD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,YAAY,GAAG,SAAS,YAAY,EAAE,CAAC;CACvD,CAAC;AAsBF,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCyB,CAAC;AAEtC,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,MAAM,CAAC;AAIlD,0EAA0E;AAC1E,wBAAgB,MAAM,CAAC,WAAW,EAAE,sBAAsB,GAAG,MAAM,CAElE;AAUD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,iBAAiB,CAQ5B;AAED,qEAAqE;AACrE,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAEnC;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,iBAAiB,GAAG,aAAa,EAAE,CAuDzE;AAuDD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,eAAe,EAAE,CAQxE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAgBT;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CAWzE"}
@@ -0,0 +1,191 @@
1
+ import { matchesPathGlob } from './path-glob.js';
2
+ import { canonicalPath } from './selector.js';
3
+ /** {@link PathGlob} as source text, for the generated declaration file. */
4
+ export const PATH_GLOB_SOURCE = '`${string}*${string}`';
5
+ /** A status class: the first digit of a status, spelled `1XX` … `5XX`. */
6
+ const STATUS_CLASS = /^[1-5]XX$/;
7
+ const FIELDS = {
8
+ method: {
9
+ valid: (value) => typeof value === 'string',
10
+ expected: 'an HTTP method',
11
+ // Methods are compared case-insensitively because a selector uppercases
12
+ // them, so `get` and `GET` are the same method by construction.
13
+ accepts: (value, { thymianReq }) => String(value).toUpperCase() === thymianReq.method.toUpperCase(),
14
+ },
15
+ status: {
16
+ valid: (value) => typeof value === 'number' && Number.isInteger(value),
17
+ expected: 'a status code, as a number',
18
+ accepts: (value, { thymianRes }) => value === thymianRes.statusCode,
19
+ },
20
+ statusClass: {
21
+ valid: (value) => typeof value === 'string' && STATUS_CLASS.test(value),
22
+ expected: 'a status class: "1XX", "2XX", "3XX", "4XX" or "5XX"',
23
+ // The first digit, not core's list of registered codes: a status class is a
24
+ // range, and a description is free to declare a 418 or a vendor's 499.
25
+ accepts: (value, { thymianRes }) => String(thymianRes.statusCode).startsWith(String(value)[0]),
26
+ },
27
+ path: {
28
+ valid: (value) => typeof value === 'string',
29
+ expected: 'a path or a path glob',
30
+ accepts: (value, transaction) => matchesPathGlob(String(value), pathOf(transaction)),
31
+ },
32
+ requestMediaType: {
33
+ valid: (value) => typeof value === 'string',
34
+ expected: 'a media type',
35
+ accepts: (value, { thymianReq }) => value === thymianReq.mediaType,
36
+ },
37
+ responseMediaType: {
38
+ valid: (value) => typeof value === 'string',
39
+ expected: 'a media type',
40
+ accepts: (value, { thymianRes }) => value === thymianRes.mediaType,
41
+ },
42
+ };
43
+ const FIELD_NAMES = Object.keys(FIELDS);
44
+ /** The path a filter compares against: the description's own spelling. */
45
+ export function pathOf(transaction) {
46
+ return canonicalPath(transaction.thymianReq.path);
47
+ }
48
+ function asList(value) {
49
+ if (value === undefined) {
50
+ return [];
51
+ }
52
+ return Array.isArray(value) ? value : [value];
53
+ }
54
+ /**
55
+ * Whether `value` is shaped like a filter.
56
+ *
57
+ * One known field is enough. Requiring *every* key to be known meant a single
58
+ * typo — `{ path: '/v1/**', tag: 'x' }` — was reported as "not a filter at
59
+ * all", which sends the reader looking for a selector they never wrote;
60
+ * {@link filterProblems} names the unknown key instead.
61
+ */
62
+ export function isTransactionFilter(value) {
63
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
64
+ return false;
65
+ }
66
+ return Object.keys(value).some((key) => key === 'not' || FIELD_NAMES.includes(key));
67
+ }
68
+ /**
69
+ * Everything wrong with a filter's own text, before any Transaction is
70
+ * consulted.
71
+ *
72
+ * Separate from matching because a filter that cannot mean anything is a
73
+ * different fault from one that means something nothing satisfies. Reporting a
74
+ * mistyped `'4xx'` as "all valid but intersects nothing" is the wrong sentence,
75
+ * and letting `{ method: 42 }` through produced a bare `TypeError` naming no
76
+ * file at all.
77
+ */
78
+ export function filterProblems(filter) {
79
+ const problems = [];
80
+ const record = filter;
81
+ for (const key of Object.keys(record)) {
82
+ if (key === 'not') {
83
+ for (const exclusion of asList(record[key])) {
84
+ if (typeof exclusion !== 'object' ||
85
+ exclusion === null ||
86
+ Array.isArray(exclusion)) {
87
+ problems.push('"not" takes a filter, or a list of filters, and nothing else');
88
+ continue;
89
+ }
90
+ if (constrainsNothing(exclusion)) {
91
+ problems.push('"not" was given an exclusion that constrains nothing, which would exclude every transaction');
92
+ }
93
+ problems.push(...filterProblems(exclusion).map((problem) => `inside "not": ${problem}`));
94
+ }
95
+ continue;
96
+ }
97
+ if (!FIELD_NAMES.includes(key)) {
98
+ problems.push(`"${key}" is not a filter field; the fields are ${FIELD_NAMES.map((name) => `"${name}"`).join(', ')} and "not"`);
99
+ continue;
100
+ }
101
+ const spec = FIELDS[key];
102
+ for (const value of asList(record[key])) {
103
+ if (!spec.valid(value)) {
104
+ problems.push(`"${key}" was given ${JSON.stringify(value) ?? String(value)}, but it takes ${spec.expected}`);
105
+ }
106
+ }
107
+ }
108
+ return problems;
109
+ }
110
+ /**
111
+ * Whether an exclusion constrains nothing at all.
112
+ *
113
+ * `matchesFields` is an `every` over the fields that are present, so a group
114
+ * with no present field is vacuously true — which as an *exclusion* means "not
115
+ * anything", excluding every transaction. `{ not: {} }` type-checks because
116
+ * every `FilterFields` member is optional, `{ not: { path: undefined } }` is
117
+ * what a computed list that came back `undefined` produces, and a `.js` hook
118
+ * can reach the same state with `{ not: { not: X } }`, whose only key is not a
119
+ * field at all. All three are the author asking to exclude nothing and getting
120
+ * everything.
121
+ */
122
+ function constrainsNothing(fields) {
123
+ const record = fields;
124
+ return FIELD_NAMES.every((name) => record[name] === undefined);
125
+ }
126
+ /**
127
+ * Whether every field present in `fields` accepts `transaction`.
128
+ *
129
+ * A field that is simply absent (`undefined`) does not constrain — vacuously
130
+ * true, which is what lets `{ method: 'GET' }` leave every other field open.
131
+ * A field that is *present* with a value list that turns out empty is a
132
+ * different thing: `{ method: [] }` is a disjunction of zero alternatives,
133
+ * which is false by construction, never true. Collapsing the two would let a
134
+ * computed filter array that happened to come back empty silently widen into
135
+ * "this field doesn't matter" instead of "nothing satisfies this field" — see
136
+ * {@link emptyValueFields}, which reads this same rule back as data for the
137
+ * diagnostic.
138
+ */
139
+ function matchesFields(fields, transaction) {
140
+ const record = fields;
141
+ return FIELD_NAMES.every((name) => {
142
+ const raw = record[name];
143
+ if (raw === undefined) {
144
+ return true;
145
+ }
146
+ const values = asList(raw);
147
+ return (values.length > 0 &&
148
+ values.some((value) => FIELDS[name].accepts(value, transaction)));
149
+ });
150
+ }
151
+ /**
152
+ * Field names `fields` gives an explicit, empty value list — a disjunction of
153
+ * zero alternatives, which {@link matchesFields} treats as always false.
154
+ *
155
+ * Read back from the same rule {@link matchesFields} applies, rather than a
156
+ * second definition of "empty": a field that is merely absent is not named
157
+ * here, only one that was given a value and that value is an empty array.
158
+ */
159
+ export function emptyValueFields(fields) {
160
+ const record = fields;
161
+ return FIELD_NAMES.filter((name) => {
162
+ const raw = record[name];
163
+ return raw !== undefined && asList(raw).length === 0;
164
+ });
165
+ }
166
+ /**
167
+ * `matches(t) = positive(t) && !not.some(n => n(t))`.
168
+ *
169
+ * An empty positive side matches everything, which is what makes
170
+ * `{ not: { … } }` mean "all but these" without a second spelling.
171
+ */
172
+ export function matchesTransactionFilter(filter, transaction) {
173
+ if (!matchesFields(filter, transaction)) {
174
+ return false;
175
+ }
176
+ // An exclusion that constrains nothing excludes nothing. Vacuous truth is
177
+ // the right answer for a *conjunction* of present fields and the wrong one
178
+ // for an exclusion, which would otherwise swallow the whole catalog.
179
+ // `filterProblems` reports it; this keeps the run's behaviour honest in the
180
+ // meantime, and keeps the diagnostic pointed at the real fault instead of
181
+ // the "values are all valid but intersect nothing" hint.
182
+ return !asList(filter.not).some((exclusion) => !constrainsNothing(exclusion) &&
183
+ matchesFields(exclusion, transaction));
184
+ }
185
+ /** Every path value a filter mentions, including the ones inside `not`. */
186
+ export function pathValuesOf(filter) {
187
+ const own = asList(filter.path).filter((value) => typeof value === 'string');
188
+ const excluded = asList(filter.not).flatMap((exclusion) => asList(exclusion['path']).filter((value) => typeof value === 'string'));
189
+ return [...own, ...excluded];
190
+ }
191
+ //# sourceMappingURL=transaction-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction-filter.js","sourceRoot":"","sources":["../../src/selectors/transaction-filter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Normalizes a filesystem path to the forward-slash form TypeScript itself
3
+ * uses for `ts.SourceFile.fileName` and `ts.Diagnostic.file.fileName` —
4
+ * `ts.createProgram` produces that form regardless of platform, while
5
+ * `node:path` (`join`, `relative`, `mkdtemp`) produces the platform's own
6
+ * separator, backslashes on Windows.
7
+ *
8
+ * Printing a path for a user (a tsconfig `exclude` hint, a tree listing) goes
9
+ * through this directly. Comparing a path against a TypeScript file name
10
+ * should go through {@link isSameTsPath} or {@link tsPathRelativeTo} instead —
11
+ * they normalize both sides so a caller cannot compare a raw one against a
12
+ * normalized one by mistake.
13
+ */
14
+ export declare function toTypeScriptPath(path: string): string;
15
+ /**
16
+ * Whether two paths — a TypeScript file name and/or a `node:path` value —
17
+ * name the same file, ignoring separator style.
18
+ *
19
+ * A raw `===` holds on POSIX, where the two forms are already the same
20
+ * string, and silently never matches on Windows.
21
+ */
22
+ export declare function isSameTsPath(a: string, b: string): boolean;
23
+ /**
24
+ * `fileName`'s path relative to `root`, both normalized first — or
25
+ * `undefined` when `fileName` does not fall under `root`.
26
+ *
27
+ * This is the exclusion test and the display path in one: `validate`'s
28
+ * scratch-surface exclusion asks only whether the result is `undefined`, and
29
+ * the compile probe's diagnostic location uses the string itself. A raw
30
+ * `startsWith`/`slice` pair against un-normalized paths is what let every
31
+ * scratch-surface diagnostic through on Windows — `scratch` is a `node:path`
32
+ * value and `fileName` is TypeScript's own forward-slashed form, so the
33
+ * comparison never matched.
34
+ */
35
+ export declare function tsPathRelativeTo(fileName: string, root: string): string | undefined;
36
+ //# sourceMappingURL=ts-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ts-path.d.ts","sourceRoot":"","sources":["../src/ts-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAOpB"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Normalizes a filesystem path to the forward-slash form TypeScript itself
3
+ * uses for `ts.SourceFile.fileName` and `ts.Diagnostic.file.fileName` —
4
+ * `ts.createProgram` produces that form regardless of platform, while
5
+ * `node:path` (`join`, `relative`, `mkdtemp`) produces the platform's own
6
+ * separator, backslashes on Windows.
7
+ *
8
+ * Printing a path for a user (a tsconfig `exclude` hint, a tree listing) goes
9
+ * through this directly. Comparing a path against a TypeScript file name
10
+ * should go through {@link isSameTsPath} or {@link tsPathRelativeTo} instead —
11
+ * they normalize both sides so a caller cannot compare a raw one against a
12
+ * normalized one by mistake.
13
+ */
14
+ export function toTypeScriptPath(path) {
15
+ return path.split(/[\\/]/).join('/');
16
+ }
17
+ /**
18
+ * Whether two paths — a TypeScript file name and/or a `node:path` value —
19
+ * name the same file, ignoring separator style.
20
+ *
21
+ * A raw `===` holds on POSIX, where the two forms are already the same
22
+ * string, and silently never matches on Windows.
23
+ */
24
+ export function isSameTsPath(a, b) {
25
+ return toTypeScriptPath(a) === toTypeScriptPath(b);
26
+ }
27
+ /**
28
+ * `fileName`'s path relative to `root`, both normalized first — or
29
+ * `undefined` when `fileName` does not fall under `root`.
30
+ *
31
+ * This is the exclusion test and the display path in one: `validate`'s
32
+ * scratch-surface exclusion asks only whether the result is `undefined`, and
33
+ * the compile probe's diagnostic location uses the string itself. A raw
34
+ * `startsWith`/`slice` pair against un-normalized paths is what let every
35
+ * scratch-surface diagnostic through on Windows — `scratch` is a `node:path`
36
+ * value and `fileName` is TypeScript's own forward-slashed form, so the
37
+ * comparison never matched.
38
+ */
39
+ export function tsPathRelativeTo(fileName, root) {
40
+ const path = toTypeScriptPath(fileName);
41
+ const rootPath = toTypeScriptPath(root);
42
+ return path === rootPath || path.startsWith(`${rootPath}/`)
43
+ ? path.slice(rootPath.length + 1)
44
+ : undefined;
45
+ }
46
+ //# sourceMappingURL=ts-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ts-path.js","sourceRoot":"","sources":["../src/ts-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS,EAAE,CAAS;IAC/C,OAAO,gBAAgB,CAAC,CAAC,CAAC,KAAK,gBAAgB,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,IAAY;IAEZ,MAAM,IAAI,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAExC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,CAAC;QACzD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QACjC,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC"}
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export declare function sanitize(name: string): string;
2
- export declare function checkForSafePath(path: string, baseDir: string): void;
1
+ /** Whether a filesystem entry exists at `path`. */
3
2
  export declare function entryExists(path: string): Promise<boolean>;
4
3
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAKA,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAmBpE;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOhE"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,mDAAmD;AACnD,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOhE"}
package/dist/utils.js CHANGED
@@ -1,29 +1,11 @@
1
1
  import { access } from 'node:fs/promises';
2
- import { isAbsolute, relative, resolve, sep } from 'node:path';
3
- import { ThymianBaseError } from '@thymian/core';
4
- export function sanitize(name) {
5
- return decodeURIComponent(name).replace(/[^a-z0-9.-]/gi, '_');
6
- }
7
- export function checkForSafePath(path, baseDir) {
8
- const resolvedBaseDir = resolve(baseDir);
9
- const resolvedPath = resolve(resolvedBaseDir, path);
10
- const relativePath = relative(resolvedBaseDir, resolvedPath);
11
- const isSafe = relativePath !== '..' &&
12
- !relativePath.startsWith(`..${sep}`) &&
13
- !isAbsolute(relativePath);
14
- if (!isSafe) {
15
- throw new ThymianBaseError(`Access denied. Path "${resolvedPath}" is outside of the base directory "${resolvedBaseDir}".`, {
16
- name: 'PathTraversalError',
17
- ref: 'https://thymian.dev/references/errors/path-traversal-error/',
18
- });
19
- }
20
- }
2
+ /** Whether a filesystem entry exists at `path`. */
21
3
  export async function entryExists(path) {
22
4
  try {
23
5
  await access(path);
24
6
  return true;
25
7
  }
26
- catch (e) {
8
+ catch {
27
9
  return false;
28
10
  }
29
11
  }
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAE/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,OAAe;IAC5D,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,YAAY,GAAG,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,QAAQ,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;IAE7D,MAAM,MAAM,GACV,YAAY,KAAK,IAAI;QACrB,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC;QACpC,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IAE5B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,gBAAgB,CACxB,wBAAwB,YAAY,uCAAuC,eAAe,IAAI,EAC9F;YACE,IAAI,EAAE,oBAAoB;YAC1B,GAAG,EAAE,6DAA6D;SACnE,CACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,mDAAmD;AACnD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A comparison form of a declaration file: every token, one space apart, with
3
+ * comments and JSDoc gone.
4
+ *
5
+ * The drift signal is about the *type surface*, not about its prose. A
6
+ * description-only edit in the API description changes the JSDoc the emitter
7
+ * writes and nothing else, and reporting that as drift would make the gate red
8
+ * for a diff that cannot break a hook. Reformatting is the same class of
9
+ * non-event.
10
+ *
11
+ * Tokenized rather than regex-stripped: `//` inside a string literal is not a
12
+ * comment, and a scanner is the only thing that knows the difference.
13
+ */
14
+ export declare function canonicalize(source: string): string;
15
+ /** Whether two declaration files say the same thing about types. */
16
+ export declare function sameSurface(a: string, b: string): boolean;
17
+ //# sourceMappingURL=canonicalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalize.d.ts","sourceRoot":"","sources":["../../src/validation/canonicalize.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAmBnD;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAEzD"}
@@ -0,0 +1,29 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * A comparison form of a declaration file: every token, one space apart, with
4
+ * comments and JSDoc gone.
5
+ *
6
+ * The drift signal is about the *type surface*, not about its prose. A
7
+ * description-only edit in the API description changes the JSDoc the emitter
8
+ * writes and nothing else, and reporting that as drift would make the gate red
9
+ * for a diff that cannot break a hook. Reformatting is the same class of
10
+ * non-event.
11
+ *
12
+ * Tokenized rather than regex-stripped: `//` inside a string literal is not a
13
+ * comment, and a scanner is the only thing that knows the difference.
14
+ */
15
+ export function canonicalize(source) {
16
+ const scanner = ts.createScanner(ts.ScriptTarget.Latest,
17
+ // Skip trivia: whitespace and comments never reach us.
18
+ true, ts.LanguageVariant.Standard, source);
19
+ const tokens = [];
20
+ for (let token = scanner.scan(); token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
21
+ tokens.push(scanner.getTokenText());
22
+ }
23
+ return tokens.join(' ');
24
+ }
25
+ /** Whether two declaration files say the same thing about types. */
26
+ export function sameSurface(a, b) {
27
+ return canonicalize(a) === canonicalize(b);
28
+ }
29
+ //# sourceMappingURL=canonicalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalize.js","sourceRoot":"","sources":["../../src/validation/canonicalize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,CAC9B,EAAE,CAAC,YAAY,CAAC,MAAM;IACtB,uDAAuD;IACvD,IAAI,EACJ,EAAE,CAAC,eAAe,CAAC,QAAQ,EAC3B,MAAM,CACP,CAAC;IACF,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KACE,IAAI,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,EAC1B,KAAK,KAAK,EAAE,CAAC,UAAU,CAAC,cAAc,EACtC,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,EACtB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,OAAO,YAAY,CAAC,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,17 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * A `ts.CompilerHost` that reuses {@link sourceFiles} across every call to
4
+ * `ts.createProgram` a caller makes with it.
5
+ *
6
+ * Each compile-seam program here pins the same `target`/`lib` (ES2023), so
7
+ * `ts.createProgram`'s dominant cost — parsing the lib closure — is identical
8
+ * work performed again on every "cold" program. That cost was paid once per
9
+ * `it()` block across dozens of compile-seam tests, and once per `validate`
10
+ * call in production. A plain `ts.createCompilerHost` has no cross-call cache
11
+ * of its own; this wraps `getSourceFile` with one that does, which is what the
12
+ * review's "share compiler state instead of a cold program per test" asked
13
+ * for — `validate` benefits the same way the tests do, since it goes through
14
+ * the same host.
15
+ */
16
+ export declare function sharedCompilerHost(options: ts.CompilerOptions): ts.CompilerHost;
17
+ //# sourceMappingURL=shared-compiler-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-compiler-host.d.ts","sourceRoot":"","sources":["../../src/validation/shared-compiler-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAsB5B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,EAAE,CAAC,eAAe,GAC1B,EAAE,CAAC,YAAY,CA2CjB"}
@@ -0,0 +1,58 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * A parsed-`SourceFile` cache, shared by every `ts.CompilerHost` this module
4
+ * hands out.
5
+ *
6
+ * **Only TypeScript's own `lib.*.d.ts` closure.** That closure is the whole
7
+ * win — it is the dominant cost of a cold `ts.createProgram`, every program
8
+ * here pins the same `target`/`lib`, and it lives at a fixed path under
9
+ * `node_modules/typescript` that cannot change within a process.
10
+ *
11
+ * Nothing else may be cached, because a file name is not a version. The
12
+ * probes write under a fresh `mkdtemp` root per call, so caching them only
13
+ * grows the map forever; the user's hooks are worse — `typecheck-hooks.ts`
14
+ * compiles them at their *stable* paths, and `sampler.validate` is an ordinary
15
+ * action that `thymian serve` dispatches over the WebSocket proxy. In one
16
+ * long-lived `serve` process the second `validate` after an edit would have
17
+ * type-checked the first call's parse: a fixed hook still reporting its old
18
+ * error, a newly broken one passing. One-shot CLI runs never showed it.
19
+ */
20
+ const sourceFiles = new Map();
21
+ /**
22
+ * A `ts.CompilerHost` that reuses {@link sourceFiles} across every call to
23
+ * `ts.createProgram` a caller makes with it.
24
+ *
25
+ * Each compile-seam program here pins the same `target`/`lib` (ES2023), so
26
+ * `ts.createProgram`'s dominant cost — parsing the lib closure — is identical
27
+ * work performed again on every "cold" program. That cost was paid once per
28
+ * `it()` block across dozens of compile-seam tests, and once per `validate`
29
+ * call in production. A plain `ts.createCompilerHost` has no cross-call cache
30
+ * of its own; this wraps `getSourceFile` with one that does, which is what the
31
+ * review's "share compiler state instead of a cold program per test" asked
32
+ * for — `validate` benefits the same way the tests do, since it goes through
33
+ * the same host.
34
+ */
35
+ export function sharedCompilerHost(options) {
36
+ const host = ts.createCompilerHost(options, true);
37
+ const getSourceFile = host.getSourceFile.bind(host);
38
+ const libDirectory = host.getDefaultLibLocation?.();
39
+ /** Immutable for the life of the process, so safe to hold parsed. */
40
+ const isLibFile = (fileName) => libDirectory !== undefined &&
41
+ fileName.startsWith(libDirectory.endsWith('/') ? libDirectory : `${libDirectory}/`);
42
+ host.getSourceFile = (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
43
+ const cacheable = isLibFile(fileName);
44
+ if (cacheable && !shouldCreateNewSourceFile) {
45
+ const cached = sourceFiles.get(fileName);
46
+ if (cached) {
47
+ return cached;
48
+ }
49
+ }
50
+ const sourceFile = getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
51
+ if (sourceFile && cacheable) {
52
+ sourceFiles.set(fileName, sourceFile);
53
+ }
54
+ return sourceFile;
55
+ };
56
+ return host;
57
+ }
58
+ //# sourceMappingURL=shared-compiler-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-compiler-host.js","sourceRoot":"","sources":["../../src/validation/shared-compiler-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,GAAG,IAAI,GAAG,EAAyB,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAA2B;IAE3B,MAAM,IAAI,GAAG,EAAE,CAAC,kBAAkB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;IAEpD,qEAAqE;IACrE,MAAM,SAAS,GAAG,CAAC,QAAgB,EAAW,EAAE,CAC9C,YAAY,KAAK,SAAS;QAC1B,QAAQ,CAAC,UAAU,CACjB,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,CAC/D,CAAC;IAEJ,IAAI,CAAC,aAAa,GAAG,CACnB,QAAQ,EACR,wBAAwB,EACxB,OAAO,EACP,yBAAyB,EACzB,EAAE;QACF,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;QAEtC,IAAI,SAAS,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAEzC,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,aAAa,CAC9B,QAAQ,EACR,wBAAwB,EACxB,OAAO,EACP,yBAAyB,CAC1B,CAAC;QAEF,IAAI,UAAU,IAAI,SAAS,EAAE,CAAC;YAC5B,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACxC,CAAC;QAED,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC;IAEF,OAAO,IAAI,CAAC;AACd,CAAC"}