@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.
- package/dist/cli/check-result.d.ts +88 -0
- package/dist/cli/check-result.d.ts.map +1 -0
- package/dist/cli/check-result.js +160 -0
- package/dist/cli/check-result.js.map +1 -0
- package/dist/cli/commands/sampler/check.d.ts +32 -4
- package/dist/cli/commands/sampler/check.d.ts.map +1 -1
- package/dist/cli/commands/sampler/check.js +126 -88
- package/dist/cli/commands/sampler/check.js.map +1 -1
- package/dist/cli/commands/sampler/init.d.ts +4 -6
- package/dist/cli/commands/sampler/init.d.ts.map +1 -1
- package/dist/cli/commands/sampler/init.js +33 -49
- package/dist/cli/commands/sampler/init.js.map +1 -1
- package/dist/cli/commands/sampler/show.d.ts +11 -0
- package/dist/cli/commands/sampler/show.d.ts.map +1 -0
- package/dist/cli/commands/sampler/show.js +36 -0
- package/dist/cli/commands/sampler/show.js.map +1 -0
- package/dist/cli/commands/sampler/sync.d.ts +11 -0
- package/dist/cli/commands/sampler/sync.d.ts.map +1 -0
- package/dist/cli/commands/sampler/sync.js +65 -0
- package/dist/cli/commands/sampler/sync.js.map +1 -0
- package/dist/cli/commands/sampler/validate.d.ts +3 -10
- package/dist/cli/commands/sampler/validate.d.ts.map +1 -1
- package/dist/cli/commands/sampler/validate.js +57 -126
- package/dist/cli/commands/sampler/validate.js.map +1 -1
- package/dist/generation/closed-schema.d.ts +22 -0
- package/dist/generation/closed-schema.d.ts.map +1 -0
- package/dist/generation/closed-schema.js +45 -0
- package/dist/generation/closed-schema.js.map +1 -0
- package/dist/generation/content-type-strategies/json.content-type-strategy.d.ts.map +1 -1
- package/dist/generation/content-type-strategies/json.content-type-strategy.js +3 -1
- package/dist/generation/content-type-strategies/json.content-type-strategy.js.map +1 -1
- package/dist/generation/content-type-strategies/xml.content-type-strategy.d.ts.map +1 -1
- package/dist/generation/content-type-strategies/xml.content-type-strategy.js +3 -1
- package/dist/generation/content-type-strategies/xml.content-type-strategy.js.map +1 -1
- package/dist/generation/generate-request-sample.d.ts +12 -0
- package/dist/generation/generate-request-sample.d.ts.map +1 -0
- package/dist/generation/{generate-samples.js → generate-request-sample.js} +11 -4
- package/dist/generation/generate-request-sample.js.map +1 -0
- package/dist/generation/request-generators/default-request-generator.d.ts +14 -0
- package/dist/generation/request-generators/default-request-generator.d.ts.map +1 -1
- package/dist/generation/request-generators/default-request-generator.js +18 -1
- package/dist/generation/request-generators/default-request-generator.js.map +1 -1
- package/dist/generation/types/declaration-set.d.ts +124 -0
- package/dist/generation/types/declaration-set.d.ts.map +1 -0
- package/dist/generation/types/declaration-set.js +389 -0
- package/dist/generation/types/declaration-set.js.map +1 -0
- package/dist/generation/types/example-reflection.d.ts +21 -0
- package/dist/generation/types/example-reflection.d.ts.map +1 -0
- package/dist/generation/types/example-reflection.js +135 -0
- package/dist/generation/types/example-reflection.js.map +1 -0
- package/dist/generation/types/generate-type-surface.d.ts +23 -0
- package/dist/generation/types/generate-type-surface.d.ts.map +1 -0
- package/dist/generation/types/generate-type-surface.js +671 -0
- package/dist/generation/types/generate-type-surface.js.map +1 -0
- package/dist/generation/types/schema-type.d.ts +20 -0
- package/dist/generation/types/schema-type.d.ts.map +1 -0
- package/dist/generation/types/schema-type.js +203 -0
- package/dist/generation/types/schema-type.js.map +1 -0
- package/dist/generation/types/self-check-surface.d.ts +32 -0
- package/dist/generation/types/self-check-surface.d.ts.map +1 -0
- package/dist/generation/types/self-check-surface.js +92 -0
- package/dist/generation/types/self-check-surface.js.map +1 -0
- package/dist/generation/types/type-names.d.ts +135 -0
- package/dist/generation/types/type-names.d.ts.map +1 -0
- package/dist/generation/types/type-names.js +213 -0
- package/dist/generation/types/type-names.js.map +1 -0
- package/dist/generation/types/write-type-surface.d.ts +58 -0
- package/dist/generation/types/write-type-surface.d.ts.map +1 -0
- package/dist/generation/types/write-type-surface.js +119 -0
- package/dist/generation/types/write-type-surface.js.map +1 -0
- package/dist/hooks/create-hook-utils.d.ts +22 -6
- package/dist/hooks/create-hook-utils.d.ts.map +1 -1
- package/dist/hooks/create-hook-utils.js +74 -96
- package/dist/hooks/create-hook-utils.js.map +1 -1
- package/dist/hooks/hook-api.d.ts +49 -0
- package/dist/hooks/hook-api.d.ts.map +1 -0
- package/dist/hooks/hook-api.js +105 -0
- package/dist/hooks/hook-api.js.map +1 -0
- package/dist/hooks/hook-diagnostics.d.ts +45 -0
- package/dist/hooks/hook-diagnostics.d.ts.map +1 -0
- package/dist/hooks/hook-diagnostics.js +80 -0
- package/dist/hooks/hook-diagnostics.js.map +1 -0
- package/dist/hooks/hook-errors.d.ts +33 -0
- package/dist/hooks/hook-errors.d.ts.map +1 -1
- package/dist/hooks/hook-errors.js +46 -0
- package/dist/hooks/hook-errors.js.map +1 -1
- package/dist/hooks/hook-registration.d.ts +108 -0
- package/dist/hooks/hook-registration.d.ts.map +1 -0
- package/dist/hooks/hook-registration.js +69 -0
- package/dist/hooks/hook-registration.js.map +1 -0
- package/dist/hooks/hook-runner.d.ts +111 -11
- package/dist/hooks/hook-runner.d.ts.map +1 -1
- package/dist/hooks/hook-runner.js +309 -142
- package/dist/hooks/hook-runner.js.map +1 -1
- package/dist/hooks/hook-utils-factory.d.ts +19 -0
- package/dist/hooks/hook-utils-factory.d.ts.map +1 -0
- package/dist/hooks/hook-utils-factory.js +2 -0
- package/dist/hooks/hook-utils-factory.js.map +1 -0
- package/dist/hooks/hook-utils.d.ts +80 -12
- package/dist/hooks/hook-utils.d.ts.map +1 -1
- package/dist/hooks/invoke-hook.d.ts +71 -0
- package/dist/hooks/invoke-hook.d.ts.map +1 -0
- package/dist/hooks/invoke-hook.js +161 -0
- package/dist/hooks/invoke-hook.js.map +1 -0
- package/dist/hooks/load-user-hooks.d.ts +96 -0
- package/dist/hooks/load-user-hooks.d.ts.map +1 -0
- package/dist/hooks/load-user-hooks.js +432 -0
- package/dist/hooks/load-user-hooks.js.map +1 -0
- package/dist/hooks/nested-request.d.ts +21 -0
- package/dist/hooks/nested-request.d.ts.map +1 -0
- package/dist/hooks/nested-request.js +24 -0
- package/dist/hooks/nested-request.js.map +1 -0
- package/dist/hooks/overlay-args.d.ts +40 -0
- package/dist/hooks/overlay-args.d.ts.map +1 -0
- package/dist/hooks/overlay-args.js +84 -0
- package/dist/hooks/overlay-args.js.map +1 -0
- package/dist/hooks/resolve-targeting.d.ts +16 -0
- package/dist/hooks/resolve-targeting.d.ts.map +1 -0
- package/dist/hooks/resolve-targeting.js +168 -0
- package/dist/hooks/resolve-targeting.js.map +1 -0
- package/dist/hooks/run-scoped-hooks.d.ts +83 -0
- package/dist/hooks/run-scoped-hooks.d.ts.map +1 -0
- package/dist/hooks/run-scoped-hooks.js +153 -0
- package/dist/hooks/run-scoped-hooks.js.map +1 -0
- package/dist/index.d.ts +42 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +152 -165
- package/dist/index.js.map +1 -1
- package/dist/request-sampler.d.ts +40 -11
- package/dist/request-sampler.d.ts.map +1 -1
- package/dist/request-sampler.js +51 -76
- package/dist/request-sampler.js.map +1 -1
- package/dist/sampler-paths.d.ts +19 -0
- package/dist/sampler-paths.d.ts.map +1 -0
- package/dist/sampler-paths.js +22 -0
- package/dist/sampler-paths.js.map +1 -0
- package/dist/selectors/nearest-paths.d.ts +14 -0
- package/dist/selectors/nearest-paths.d.ts.map +1 -0
- package/dist/selectors/nearest-paths.js +32 -0
- package/dist/selectors/nearest-paths.js.map +1 -0
- package/dist/selectors/path-glob.d.ts +94 -0
- package/dist/selectors/path-glob.d.ts.map +1 -0
- package/dist/selectors/path-glob.js +136 -0
- package/dist/selectors/path-glob.js.map +1 -0
- package/dist/selectors/selector-errors.d.ts +17 -0
- package/dist/selectors/selector-errors.d.ts.map +1 -0
- package/dist/selectors/selector-errors.js +139 -0
- package/dist/selectors/selector-errors.js.map +1 -0
- package/dist/selectors/selector.d.ts +55 -0
- package/dist/selectors/selector.d.ts.map +1 -0
- package/dist/selectors/selector.js +305 -0
- package/dist/selectors/selector.js.map +1 -0
- package/dist/selectors/transaction-catalog.d.ts +96 -0
- package/dist/selectors/transaction-catalog.d.ts.map +1 -0
- package/dist/selectors/transaction-catalog.js +193 -0
- package/dist/selectors/transaction-catalog.js.map +1 -0
- package/dist/selectors/transaction-filter.d.ts +119 -0
- package/dist/selectors/transaction-filter.d.ts.map +1 -0
- package/dist/selectors/transaction-filter.js +191 -0
- package/dist/selectors/transaction-filter.js.map +1 -0
- package/dist/ts-path.d.ts +36 -0
- package/dist/ts-path.d.ts.map +1 -0
- package/dist/ts-path.js +46 -0
- package/dist/ts-path.js.map +1 -0
- package/dist/utils.d.ts +1 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -20
- package/dist/utils.js.map +1 -1
- package/dist/validation/canonicalize.d.ts +17 -0
- package/dist/validation/canonicalize.d.ts.map +1 -0
- package/dist/validation/canonicalize.js +29 -0
- package/dist/validation/canonicalize.js.map +1 -0
- package/dist/validation/shared-compiler-host.d.ts +17 -0
- package/dist/validation/shared-compiler-host.d.ts.map +1 -0
- package/dist/validation/shared-compiler-host.js +58 -0
- package/dist/validation/shared-compiler-host.js.map +1 -0
- package/dist/validation/typecheck-hooks.d.ts +37 -0
- package/dist/validation/typecheck-hooks.d.ts.map +1 -0
- package/dist/validation/typecheck-hooks.js +169 -0
- package/dist/validation/typecheck-hooks.js.map +1 -0
- package/dist/validation/validate-sampler.d.ts +79 -0
- package/dist/validation/validate-sampler.d.ts.map +1 -0
- package/dist/validation/validate-sampler.js +89 -0
- package/dist/validation/validate-sampler.js.map +1 -0
- package/package.json +7 -11
- package/dist/cli/commands/sampler/generate/hook.d.ts +0 -10
- package/dist/cli/commands/sampler/generate/hook.d.ts.map +0 -1
- package/dist/cli/commands/sampler/generate/hook.js +0 -19
- package/dist/cli/commands/sampler/generate/hook.js.map +0 -1
- package/dist/cli/generate-hook.d.ts +0 -4
- package/dist/cli/generate-hook.d.ts.map +0 -1
- package/dist/cli/generate-hook.js +0 -96
- package/dist/cli/generate-hook.js.map +0 -1
- package/dist/cli/templates.d.ts +0 -4
- package/dist/cli/templates.d.ts.map +0 -1
- package/dist/cli/templates.js +0 -28
- package/dist/cli/templates.js.map +0 -1
- package/dist/constants.d.ts +0 -12
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -16
- package/dist/constants.js.map +0 -1
- package/dist/generation/generate-samples-for-thymian-format.d.ts +0 -4
- package/dist/generation/generate-samples-for-thymian-format.d.ts.map +0 -1
- package/dist/generation/generate-samples-for-thymian-format.js +0 -18
- package/dist/generation/generate-samples-for-thymian-format.js.map +0 -1
- package/dist/generation/generate-samples.d.ts +0 -4
- package/dist/generation/generate-samples.d.ts.map +0 -1
- package/dist/generation/generate-samples.js.map +0 -1
- package/dist/hooks/generate-request-types.d.ts +0 -34
- package/dist/hooks/generate-request-types.d.ts.map +0 -1
- package/dist/hooks/generate-request-types.js +0 -458
- package/dist/hooks/generate-request-types.js.map +0 -1
- package/dist/hooks/hook-types.d.ts +0 -7
- package/dist/hooks/hook-types.d.ts.map +0 -1
- package/dist/hooks/hook-types.js +0 -2
- package/dist/hooks/hook-types.js.map +0 -1
- package/dist/hooks/load-hooks-from-samples.d.ts +0 -3
- package/dist/hooks/load-hooks-from-samples.d.ts.map +0 -1
- package/dist/hooks/load-hooks-from-samples.js +0 -22
- package/dist/hooks/load-hooks-from-samples.js.map +0 -1
- package/dist/hooks/ts-config.d.ts +0 -10
- package/dist/hooks/ts-config.d.ts.map +0 -1
- package/dist/hooks/ts-config.js +0 -10
- package/dist/hooks/ts-config.js.map +0 -1
- package/dist/samples-structure/file-request-sample.d.ts +0 -20
- package/dist/samples-structure/file-request-sample.d.ts.map +0 -1
- package/dist/samples-structure/file-request-sample.js +0 -5
- package/dist/samples-structure/file-request-sample.js.map +0 -1
- package/dist/samples-structure/get-path-transaction-id.d.ts +0 -3
- package/dist/samples-structure/get-path-transaction-id.d.ts.map +0 -1
- package/dist/samples-structure/get-path-transaction-id.js +0 -22
- package/dist/samples-structure/get-path-transaction-id.js.map +0 -1
- package/dist/samples-structure/merge-tree.d.ts +0 -5
- package/dist/samples-structure/merge-tree.d.ts.map +0 -1
- package/dist/samples-structure/merge-tree.js +0 -118
- package/dist/samples-structure/merge-tree.js.map +0 -1
- package/dist/samples-structure/read-samples-from-dir.d.ts +0 -13
- package/dist/samples-structure/read-samples-from-dir.d.ts.map +0 -1
- package/dist/samples-structure/read-samples-from-dir.js +0 -205
- package/dist/samples-structure/read-samples-from-dir.js.map +0 -1
- package/dist/samples-structure/samples-from-transactions.d.ts +0 -5
- package/dist/samples-structure/samples-from-transactions.d.ts.map +0 -1
- package/dist/samples-structure/samples-from-transactions.js +0 -120
- package/dist/samples-structure/samples-from-transactions.js.map +0 -1
- package/dist/samples-structure/samples-tree-structure.d.ts +0 -106
- package/dist/samples-structure/samples-tree-structure.d.ts.map +0 -1
- package/dist/samples-structure/samples-tree-structure.js +0 -26
- package/dist/samples-structure/samples-tree-structure.js.map +0 -1
- package/dist/samples-structure/structure-meta-on-disc.d.ts +0 -11
- package/dist/samples-structure/structure-meta-on-disc.d.ts.map +0 -1
- package/dist/samples-structure/structure-meta-on-disc.js +0 -2
- package/dist/samples-structure/structure-meta-on-disc.js.map +0 -1
- package/dist/samples-structure/traverse.d.ts +0 -4
- package/dist/samples-structure/traverse.d.ts.map +0 -1
- package/dist/samples-structure/traverse.js +0 -13
- package/dist/samples-structure/traverse.js.map +0 -1
- package/dist/samples-structure/write-samples-to-dir.d.ts +0 -37
- package/dist/samples-structure/write-samples-to-dir.d.ts.map +0 -1
- package/dist/samples-structure/write-samples-to-dir.js +0 -183
- package/dist/samples-structure/write-samples-to-dir.js.map +0 -1
- package/dist/validation/validate-sampler-output.d.ts +0 -38
- package/dist/validation/validate-sampler-output.d.ts.map +0 -1
- package/dist/validation/validate-sampler-output.js +0 -288
- 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"}
|
package/dist/ts-path.js
ADDED
|
@@ -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
|
-
|
|
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
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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
|
|
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;
|
|
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"}
|