@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,432 @@
|
|
|
1
|
+
import { readdir, realpath } from 'node:fs/promises';
|
|
2
|
+
import { createRequire, Module } from 'node:module';
|
|
3
|
+
import { dirname, isAbsolute, join, relative, sep } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { createJiti } from 'jiti';
|
|
6
|
+
import { toTypeScriptPath } from '../ts-path.js';
|
|
7
|
+
import { entryExists } from '../utils.js';
|
|
8
|
+
import { hookFileImportError } from './hook-diagnostics.js';
|
|
9
|
+
import { createdRegistrations, isHookRegistration, } from './hook-registration.js';
|
|
10
|
+
import { resolveTargeting } from './resolve-targeting.js';
|
|
11
|
+
/**
|
|
12
|
+
* `@thymian/hooks` is resolved by **this** alias — not by a tsconfig `paths`
|
|
13
|
+
* entry and not by a committed `.d.ts` — which is what lets a hook file import
|
|
14
|
+
* runtime values in a workspace with no generated directory, no generated
|
|
15
|
+
* tsconfig and `sampler init` never run.
|
|
16
|
+
*
|
|
17
|
+
* Two properties are load-bearing:
|
|
18
|
+
*
|
|
19
|
+
* - **An absolute path, never a bare specifier.** The package exports only `"."`
|
|
20
|
+
* and `"./package.json"`, so `'@thymian/plugin-sampler/hook-api'` would be
|
|
21
|
+
* blocked by the exports map in a published install, and adding a subpath
|
|
22
|
+
* export would put an internal module on the public surface. Deriving the path
|
|
23
|
+
* from `import.meta.url` sidesteps the exports map entirely.
|
|
24
|
+
* - **Extensionless.** jiti resolves `.js .mjs .cjs .ts .mts .cts` itself, so one
|
|
25
|
+
* target serves `dist/hooks/hook-api.js` in an installed CLI and
|
|
26
|
+
* `src/hooks/hook-api.ts` when running from source, identically.
|
|
27
|
+
*/
|
|
28
|
+
const hooksRuntimeModule = fileURLToPath(new URL('./hook-api.js', import.meta.url)).replace(/\.[cm]?js$/, '');
|
|
29
|
+
/** The specifier a hook file writes. */
|
|
30
|
+
export const HOOKS_RUNTIME_SPECIFIER = '@thymian/hooks';
|
|
31
|
+
/**
|
|
32
|
+
* Keep a file iff its name ends in one of the six module extensions and is not a
|
|
33
|
+
* declaration file.
|
|
34
|
+
*
|
|
35
|
+
* `.tsx`/`.jsx` are not among them: JSX needs a transform pragma this jiti
|
|
36
|
+
* instance is not configured for, so such a file would be scanned only to fail
|
|
37
|
+
* on its own syntax.
|
|
38
|
+
*
|
|
39
|
+
* The declaration-file exclusion is case-insensitive while the keep pattern is
|
|
40
|
+
* not. `types.D.ts` ends in `.ts` and so matched the keep pattern, but missed a
|
|
41
|
+
* case-sensitive `\.d\.ts$` — and a hand-written declaration file handed to jiti
|
|
42
|
+
* fails on its own `declare module` syntax. Widening the exclusion can only ever
|
|
43
|
+
* skip a file that could not have executed; widening the keep pattern the same
|
|
44
|
+
* way would make the scan depend on filesystem case sensitivity.
|
|
45
|
+
*/
|
|
46
|
+
export function isHookFile(name) {
|
|
47
|
+
if (/\.d\.[cm]?ts$/i.test(name)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return /\.[cm]?[jt]s$/.test(name);
|
|
51
|
+
}
|
|
52
|
+
/** Hooks-dir-relative, `/`-normalized — the sort key and the diagnostic label. */
|
|
53
|
+
function hooksDirRelative(hooksDir, full) {
|
|
54
|
+
return toTypeScriptPath(relative(hooksDir, full));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* One directory level, then its subdirectories.
|
|
58
|
+
*
|
|
59
|
+
* Deliberately not `readdir({ recursive: true })`: that is a single call, so an
|
|
60
|
+
* `EACCES` on any nested directory rejects the *whole* walk and loses every
|
|
61
|
+
* healthy hook in the tree. Walking a level at a time costs one `readdir` per
|
|
62
|
+
* directory and confines a failure to the subtree that actually failed.
|
|
63
|
+
*
|
|
64
|
+
* Dot-directories are skipped, so an editor's or a tool's cache inside the hooks
|
|
65
|
+
* tree is not scanned.
|
|
66
|
+
*/
|
|
67
|
+
async function walkHookDirectory(hooksDir, dir, isRoot, files, warnings) {
|
|
68
|
+
let entries;
|
|
69
|
+
try {
|
|
70
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (isRoot) {
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
warnings.push(`The hooks subdirectory "${hooksDirRelative(hooksDir, dir)}" could not be read (${error instanceof Error ? error.message : String(error)}), so any hooks inside it were not loaded.`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const directories = [];
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const full = join(dir, entry.name);
|
|
82
|
+
// `Dirent`'s type flags describe the entry itself, never the symlink's
|
|
83
|
+
// target — `isFile()`/`isDirectory()` are both false for a symlink, which
|
|
84
|
+
// already keeps one out of `files`/`directories` below. What was missing
|
|
85
|
+
// is the word: a hook that silently never runs because its file is a
|
|
86
|
+
// symlink looks exactly like a Thymian bug, so it is named rather than
|
|
87
|
+
// dropped.
|
|
88
|
+
if (entry.isSymbolicLink()) {
|
|
89
|
+
warnings.push(`The hooks entry "${hooksDirRelative(hooksDir, full)}" is a symlink and was skipped: symlinked hook files and directories are not followed.`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
// `node_modules` is never a source of hooks, however it got there —
|
|
94
|
+
// installed by the user's own tooling or vendored by hand — so it is
|
|
95
|
+
// excluded by name rather than relying on every install layout to
|
|
96
|
+
// happen to start with a dot.
|
|
97
|
+
if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
98
|
+
directories.push(full);
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (entry.isFile() && isHookFile(entry.name)) {
|
|
103
|
+
files.push(full);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const directory of directories) {
|
|
107
|
+
await walkHookDirectory(hooksDir, directory, false, files, warnings);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* A stable load order: hooks-dir-relative key, by code unit.
|
|
112
|
+
*
|
|
113
|
+
* `readdir` order is filesystem-dependent, and composition order is observable,
|
|
114
|
+
* so the order hooks are loaded in is a decision rather than an accident.
|
|
115
|
+
*/
|
|
116
|
+
function compareKeys(a, b) {
|
|
117
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Every registration exported by one module namespace, with its export name.
|
|
121
|
+
*
|
|
122
|
+
* Both a registration and an array of them count, at any export name including
|
|
123
|
+
* `default`. Nothing here calls a value to find out what it is: functions are
|
|
124
|
+
* rejected structurally by {@link isHookRegistration}.
|
|
125
|
+
*/
|
|
126
|
+
function collectFromNamespace(namespace, file, dir) {
|
|
127
|
+
if (typeof namespace !== 'object' || namespace === null) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const collected = [];
|
|
131
|
+
for (const exportName of Object.keys(namespace)) {
|
|
132
|
+
let value;
|
|
133
|
+
try {
|
|
134
|
+
value = namespace[exportName];
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// A getter that throws is not a hook; the module's other exports stand.
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (isHookRegistration(value)) {
|
|
141
|
+
collected.push({
|
|
142
|
+
registration: value,
|
|
143
|
+
file,
|
|
144
|
+
dir,
|
|
145
|
+
exportName,
|
|
146
|
+
sequence: 0,
|
|
147
|
+
});
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (Array.isArray(value)) {
|
|
151
|
+
for (const element of value) {
|
|
152
|
+
if (isHookRegistration(element)) {
|
|
153
|
+
collected.push({
|
|
154
|
+
registration: element,
|
|
155
|
+
file,
|
|
156
|
+
dir,
|
|
157
|
+
exportName,
|
|
158
|
+
sequence: 0,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return collected;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* jiti's module cache is Node's own `require.cache`, which is one object per
|
|
168
|
+
* process, so the loader can read it through any `require`.
|
|
169
|
+
*/
|
|
170
|
+
function moduleCache() {
|
|
171
|
+
return createRequire(import.meta.url).cache;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Forget every module one scan evaluated, so the next scan evaluates the
|
|
175
|
+
* files as they are then.
|
|
176
|
+
*
|
|
177
|
+
* The module cache is on for the scan's sake, not the process's: a hook file
|
|
178
|
+
* may be edited between two scans of one long-lived process — `thymian serve`
|
|
179
|
+
* dispatching `validate` after every save — and so may a module a hook file
|
|
180
|
+
* imports. Evicting exactly what the scan added, rather than what lies under
|
|
181
|
+
* the hooks directory, is what covers that second case, and it keeps the
|
|
182
|
+
* cache from growing by one scan's worth of entries per call. A registration
|
|
183
|
+
* already collected is unaffected: it is held by reference, and the cache
|
|
184
|
+
* entry was only ever the way to reach it.
|
|
185
|
+
*/
|
|
186
|
+
function evictScannedModules(cachedBefore) {
|
|
187
|
+
const cache = moduleCache();
|
|
188
|
+
for (const id of Object.keys(cache)) {
|
|
189
|
+
// An installed package is not edited between scans, and jiti's own
|
|
190
|
+
// transformer is among them: evicting it would re-load the transpiler
|
|
191
|
+
// on every scan for nothing.
|
|
192
|
+
if (!cachedBefore.has(id) && !/[\\/]node_modules[\\/]/.test(id)) {
|
|
193
|
+
delete cache[id];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Make every path jiti resolves under the hooks directory take ONE spelling,
|
|
199
|
+
* so a file is one module-cache entry however it was reached. Returns the
|
|
200
|
+
* function that undoes it.
|
|
201
|
+
*
|
|
202
|
+
* jiti keys its module cache by the path its resolver returns, and it has two
|
|
203
|
+
* resolvers that spell a path differently on Windows. A path that exists as
|
|
204
|
+
* written — every file the scan imports, `./shared.ts`, `./shared` — goes
|
|
205
|
+
* through exsolve and comes back with forward slashes. `./shared.js` naming a
|
|
206
|
+
* `.ts` file does not exist as written, so jiti rewrites it to `.ts` and asks
|
|
207
|
+
* Node's `require.resolve`, which answers with backslashes. `C:/…/shared.ts`
|
|
208
|
+
* and `C:\…\shared.ts` are two cache entries, so the file was evaluated twice:
|
|
209
|
+
* a re-exported hook was two objects, a re-exported `defineSample` a conflict
|
|
210
|
+
* with itself, and an import cycle re-entered its first file and created a
|
|
211
|
+
* hook nothing exported. On POSIX both spellings are the same string, which is
|
|
212
|
+
* why only Windows saw it.
|
|
213
|
+
*
|
|
214
|
+
* `Module._resolveFilename` is what `require.resolve` calls, so rewriting its
|
|
215
|
+
* answer to forward slashes aligns the second resolver with the first. It is
|
|
216
|
+
* scoped to the scan and to paths under `root`, so nothing else in the process
|
|
217
|
+
* resolves differently, and it is a no-op where the separator already is `/`.
|
|
218
|
+
*/
|
|
219
|
+
function unifyResolvedPathSpelling(root) {
|
|
220
|
+
if (sep === '/') {
|
|
221
|
+
return () => undefined;
|
|
222
|
+
}
|
|
223
|
+
// Private Node API, absent from the type declarations.
|
|
224
|
+
const internals = Module;
|
|
225
|
+
const original = internals._resolveFilename;
|
|
226
|
+
internals._resolveFilename = function (request, ...rest) {
|
|
227
|
+
const resolved = original.call(this, request, ...rest);
|
|
228
|
+
const fromRoot = relative(root, resolved);
|
|
229
|
+
return fromRoot.startsWith('..') || isAbsolute(fromRoot)
|
|
230
|
+
? resolved
|
|
231
|
+
: resolved.replaceAll('\\', '/');
|
|
232
|
+
};
|
|
233
|
+
return () => {
|
|
234
|
+
internals._resolveFilename = original;
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function emptyTransactionHooks() {
|
|
238
|
+
return {
|
|
239
|
+
defineSample: [],
|
|
240
|
+
beforeEach: [],
|
|
241
|
+
afterEach: [],
|
|
242
|
+
authorize: [],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Scan the hooks directory and bind every registration it exports to the
|
|
247
|
+
* Transactions it names.
|
|
248
|
+
*
|
|
249
|
+
* A hooks directory that does not exist is not an error — `sampler init` is
|
|
250
|
+
* optional and a project may have no hooks at all — it is an empty result.
|
|
251
|
+
*/
|
|
252
|
+
export async function loadUserHooks(hooksDir, catalog) {
|
|
253
|
+
const diagnostics = [];
|
|
254
|
+
const conflicts = [];
|
|
255
|
+
const unexported = [];
|
|
256
|
+
const warnings = [];
|
|
257
|
+
const byTransactionId = new Map();
|
|
258
|
+
const runScoped = {
|
|
259
|
+
beforeAll: [],
|
|
260
|
+
afterAll: [],
|
|
261
|
+
};
|
|
262
|
+
const globalAuthorize = [];
|
|
263
|
+
if (!(await entryExists(hooksDir))) {
|
|
264
|
+
return {
|
|
265
|
+
files: [],
|
|
266
|
+
byTransactionId,
|
|
267
|
+
runScoped,
|
|
268
|
+
globalAuthorize,
|
|
269
|
+
diagnostics,
|
|
270
|
+
conflicts,
|
|
271
|
+
unexported,
|
|
272
|
+
warnings,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
// The REAL path, because the module cache is keyed by what jiti resolves an
|
|
276
|
+
// import to, and jiti resolves through symlinks. Handed a path with a
|
|
277
|
+
// symlink in it — macOS's `/var` is one onto `/private/var` — the file the
|
|
278
|
+
// scan imports and the same file reached through `./shared.js` were two
|
|
279
|
+
// cache entries, evaluated twice, and the re-export was two objects again.
|
|
280
|
+
const root = await realpath(hooksDir);
|
|
281
|
+
const found = [];
|
|
282
|
+
await walkHookDirectory(root, root, true, found, warnings);
|
|
283
|
+
const files = found
|
|
284
|
+
.map((full) => ({ full, key: hooksDirRelative(root, full) }))
|
|
285
|
+
.sort((a, b) => compareKeys(a.key, b.key));
|
|
286
|
+
const jiti = createJiti(hooksRuntimeModule, {
|
|
287
|
+
alias: { [HOOKS_RUNTIME_SPECIFIER]: hooksRuntimeModule },
|
|
288
|
+
// A hook file is user code that may have been edited between runs, and a
|
|
289
|
+
// run is short-lived: nothing is gained by caching its transpilation, and a
|
|
290
|
+
// stale entry would be served silently.
|
|
291
|
+
fsCache: false,
|
|
292
|
+
// Cached for the span of ONE scan — see `evictScannedModules`. With the
|
|
293
|
+
// cache off, a module two hook files import was evaluated once per
|
|
294
|
+
// importer, so a re-exported hook was a different object per exporting
|
|
295
|
+
// file: bound twice, and a re-exported `defineSample` a conflict with
|
|
296
|
+
// itself. A re-export is the same binding in ESM; caching for the scan
|
|
297
|
+
// makes it the same object here too, which is what lets `collected` be
|
|
298
|
+
// deduplicated by identity below without touching the conflict check
|
|
299
|
+
// — two genuinely distinct hooks remain two objects.
|
|
300
|
+
moduleCache: true,
|
|
301
|
+
});
|
|
302
|
+
const collected = [];
|
|
303
|
+
// Emptied per scan, so what it holds is this scan's registrations and the
|
|
304
|
+
// slice taken after each import is that file's.
|
|
305
|
+
const created = createdRegistrations();
|
|
306
|
+
const createdPerFile = [];
|
|
307
|
+
const exported = new Set();
|
|
308
|
+
const cachedBefore = new Set(Object.keys(moduleCache()));
|
|
309
|
+
created.length = 0;
|
|
310
|
+
const restoreResolution = unifyResolvedPathSpelling(root);
|
|
311
|
+
try {
|
|
312
|
+
for (const { full, key } of files) {
|
|
313
|
+
const createdBefore = created.length;
|
|
314
|
+
let namespace;
|
|
315
|
+
try {
|
|
316
|
+
namespace = await jiti.import(full);
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
throw hookFileImportError(key, error);
|
|
320
|
+
}
|
|
321
|
+
// Registration order **within the file**, not export order: an ESM
|
|
322
|
+
// namespace exposes its keys sorted, so `order` is the only thing that
|
|
323
|
+
// knows which hook the file created first.
|
|
324
|
+
//
|
|
325
|
+
// Sorted per file rather than once at the end: `order` is stamped by the
|
|
326
|
+
// hooks runtime at creation, and a hook created in a shared module is
|
|
327
|
+
// created when the FIRST importer is evaluated, so a global sort by
|
|
328
|
+
// `order` would interleave the files. File order is the outer key and
|
|
329
|
+
// it is already deterministic; `order` only has to sequence what one
|
|
330
|
+
// file exports.
|
|
331
|
+
const fromFile = collectFromNamespace(namespace, key, dirname(full)).sort((a, b) => a.registration.order - b.registration.order);
|
|
332
|
+
createdPerFile.push({ file: key, created: created.slice(createdBefore) });
|
|
333
|
+
for (const entry of fromFile) {
|
|
334
|
+
// One hook, however many files re-export it. The first exporting file
|
|
335
|
+
// in load order is the one it is attributed to.
|
|
336
|
+
if (exported.has(entry.registration)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
exported.add(entry.registration);
|
|
340
|
+
collected.push(entry);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
restoreResolution();
|
|
346
|
+
evictScannedModules(cachedBefore);
|
|
347
|
+
}
|
|
348
|
+
// A hook created and never exported is unreachable: discovery is
|
|
349
|
+
// export-based, so it never fires, and a hook that never fires looks exactly
|
|
350
|
+
// like one with nothing to do. It was the only failure here that was silent.
|
|
351
|
+
//
|
|
352
|
+
// Checked only once every file has been scanned, because a hook created in a
|
|
353
|
+
// shared module is created when the first file that IMPORTS it is evaluated
|
|
354
|
+
// — accusing that file would fail a run over a hook that another file
|
|
355
|
+
// exports and that does fire.
|
|
356
|
+
for (const { file, created: createdHere } of createdPerFile) {
|
|
357
|
+
for (const registration of createdHere) {
|
|
358
|
+
if (exported.has(registration)) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
unexported.push({
|
|
362
|
+
file,
|
|
363
|
+
reason: `a ${registration.kind} hook was created here but never exported, so nothing can reach it`,
|
|
364
|
+
suggestions: [
|
|
365
|
+
`Assign it to an export, as in: export const shape = ${registration.kind}(...)`,
|
|
366
|
+
],
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// One number ordering every hook in the run against every other. Assigned
|
|
371
|
+
// here, once the file-then-registration order is settled, because that is the
|
|
372
|
+
// only place both halves are known.
|
|
373
|
+
collected.forEach((entry, index) => {
|
|
374
|
+
entry.sequence = index;
|
|
375
|
+
});
|
|
376
|
+
for (const entry of collected) {
|
|
377
|
+
const { registration } = entry;
|
|
378
|
+
if (registration.kind === 'beforeAll' || registration.kind === 'afterAll') {
|
|
379
|
+
runScoped[registration.kind].push(entry);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (registration.kind === 'authorize' &&
|
|
383
|
+
registration.target === undefined) {
|
|
384
|
+
globalAuthorize.push(entry);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const targeted = resolveTargeting(registration.kind, registration.target, entry, catalog, diagnostics);
|
|
388
|
+
for (const transaction of targeted) {
|
|
389
|
+
let hooks = byTransactionId.get(transaction.transactionId);
|
|
390
|
+
if (!hooks) {
|
|
391
|
+
hooks = emptyTransactionHooks();
|
|
392
|
+
byTransactionId.set(transaction.transactionId, hooks);
|
|
393
|
+
}
|
|
394
|
+
// `defineSample` is set-once per Transaction. A second one is a conflict
|
|
395
|
+
// rather than a last-wins override, and it is reported at load time —
|
|
396
|
+
// not when that Transaction happens to be sampled — because the mistake
|
|
397
|
+
// exists whether or not the run reaches it.
|
|
398
|
+
const conflicting = hooks.defineSample[0];
|
|
399
|
+
if (registration.kind === 'defineSample' && conflicting) {
|
|
400
|
+
conflicts.push({
|
|
401
|
+
file: entry.file,
|
|
402
|
+
exportName: entry.exportName,
|
|
403
|
+
reason: `defineSample is already defined for the selector "${catalog.selectorFor(transaction.transactionId) ?? transaction.transactionId}" by "${conflicting.exportName}" in ${conflicting.file}; a Transaction can have only one`,
|
|
404
|
+
suggestions: [
|
|
405
|
+
'Merge the two into one defineSample, or target a different selector.',
|
|
406
|
+
],
|
|
407
|
+
});
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
// Two authorize hooks in the same tier on one Transaction: the later one
|
|
411
|
+
// wins, which is what lets a file override an earlier one — but silently
|
|
412
|
+
// dropping a set of credentials is worth a word, since a reader who just
|
|
413
|
+
// met the `defineSample` conflict will expect one.
|
|
414
|
+
const supersededAuthorize = registration.kind === 'authorize' ? hooks.authorize.at(-1) : undefined;
|
|
415
|
+
if (supersededAuthorize) {
|
|
416
|
+
warnings.push(`Two authorize hooks target the same transaction: "${entry.exportName}" in ${entry.file} supersedes "${supersededAuthorize.exportName}" in ${supersededAuthorize.file}. Only the last one runs.`);
|
|
417
|
+
}
|
|
418
|
+
hooks[registration.kind].push(entry);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
files: files.map(({ key }) => key),
|
|
423
|
+
byTransactionId,
|
|
424
|
+
runScoped,
|
|
425
|
+
globalAuthorize,
|
|
426
|
+
diagnostics,
|
|
427
|
+
conflicts,
|
|
428
|
+
unexported,
|
|
429
|
+
warnings,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
//# sourceMappingURL=load-user-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-user-hooks.js","sourceRoot":"","sources":["../../src/hooks/load-user-hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ThymianBaseError } from '@thymian/core';
|
|
2
|
+
import type { Selector } from '../selectors/selector.js';
|
|
3
|
+
/**
|
|
4
|
+
* The Selectors whose pipelines are currently on the stack.
|
|
5
|
+
*
|
|
6
|
+
* Travels with the hook context rather than living in the runner, because that
|
|
7
|
+
* is what makes the guard reflect *this* call's ancestry instead of whatever
|
|
8
|
+
* the runner happened to be doing last.
|
|
9
|
+
*/
|
|
10
|
+
export type SelectorChain = readonly Selector[];
|
|
11
|
+
/**
|
|
12
|
+
* A seeding call has re-entered a Transaction already on the stack.
|
|
13
|
+
*
|
|
14
|
+
* Printing the chain is the whole point: `A → B → A` is a mistake three files
|
|
15
|
+
* apart, and the middle of the cycle is where the fix goes. Without it the run
|
|
16
|
+
* would recurse until the stack gave out, and a stack overflow names nothing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function requestCycleError(chain: SelectorChain, selector: Selector): ThymianBaseError;
|
|
19
|
+
/** Whether `selector` is already on `chain`. */
|
|
20
|
+
export declare function isOnChain(chain: SelectorChain, selector: Selector): boolean;
|
|
21
|
+
//# sourceMappingURL=nested-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-request.d.ts","sourceRoot":"","sources":["../../src/hooks/nested-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,QAAQ,EAAE,CAAC;AAEhD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,QAAQ,GACjB,gBAAgB,CAclB;AAED,gDAAgD;AAChD,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAE3E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ThymianBaseError } from '@thymian/core';
|
|
2
|
+
/**
|
|
3
|
+
* A seeding call has re-entered a Transaction already on the stack.
|
|
4
|
+
*
|
|
5
|
+
* Printing the chain is the whole point: `A → B → A` is a mistake three files
|
|
6
|
+
* apart, and the middle of the cycle is where the fix goes. Without it the run
|
|
7
|
+
* would recurse until the stack gave out, and a stack overflow names nothing.
|
|
8
|
+
*/
|
|
9
|
+
export function requestCycleError(chain, selector) {
|
|
10
|
+
const path = [...chain, selector].map((entry) => `"${entry}"`).join('\n → ');
|
|
11
|
+
return new ThymianBaseError(`A cross-endpoint request would re-enter "${selector}", which is already running.`, {
|
|
12
|
+
name: 'RequestCycleError',
|
|
13
|
+
ref: 'https://thymian.dev/references/errors/request-cycle-error/',
|
|
14
|
+
suggestions: [
|
|
15
|
+
`The chain is:\n ${path}`,
|
|
16
|
+
'Break the cycle, or pass `{ runHooks: false }` to seed from the raw request without running the target’s hooks.',
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** Whether `selector` is already on `chain`. */
|
|
21
|
+
export function isOnChain(chain, selector) {
|
|
22
|
+
return chain.includes(selector);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=nested-request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-request.js","sourceRoot":"","sources":["../../src/hooks/nested-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAajD;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAoB,EACpB,QAAkB;IAElB,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAE9E,OAAO,IAAI,gBAAgB,CACzB,4CAA4C,QAAQ,8BAA8B,EAClF;QACE,IAAI,EAAE,mBAAmB;QACzB,GAAG,EAAE,4DAA4D;QACjE,WAAW,EAAE;YACX,oBAAoB,IAAI,EAAE;YAC1B,iHAAiH;SAClH;KACF,CACF,CAAC;AACJ,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,SAAS,CAAC,KAAoB,EAAE,QAAkB;IAChE,OAAO,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { HttpRequestTemplate } from '@thymian/core';
|
|
2
|
+
import type { EndpointRequest } from './hook-utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* Overlays a caller's arguments onto a generated request.
|
|
5
|
+
*
|
|
6
|
+
* The generated sample plus the target's own hooks already produce a complete,
|
|
7
|
+
* schema-satisfying request. Arguments are therefore an **overlay**, not a
|
|
8
|
+
* replacement: a seeding call states the one field that has to differ — a name
|
|
9
|
+
* it will assert on later, an id it just created — and inherits everything
|
|
10
|
+
* else. Requiring the whole request instead made the caller re-state a body
|
|
11
|
+
* the description had already described.
|
|
12
|
+
*
|
|
13
|
+
* Parameter groups merge per key. The body deep-merges (see
|
|
14
|
+
* {@link deepMergeBody}).
|
|
15
|
+
*/
|
|
16
|
+
export declare function applyArgs(template: HttpRequestTemplate, args: EndpointRequest): HttpRequestTemplate;
|
|
17
|
+
/**
|
|
18
|
+
* Merges an overlay body onto the generated one: **objects recurse, arrays and
|
|
19
|
+
* primitives replace, `null` overrides.**
|
|
20
|
+
*
|
|
21
|
+
* Arrays replace rather than merge because a positional merge needs an index
|
|
22
|
+
* the caller cannot know is stable, and a per-element merge would produce
|
|
23
|
+
* elements the schema never described. `null` overrides for the same reason it
|
|
24
|
+
* is not "absent": a description that allows `null` means it, and merging
|
|
25
|
+
* around it would make the one value you cannot express the one you meant.
|
|
26
|
+
*
|
|
27
|
+
* Written here rather than taken from a library so that the rule the generated
|
|
28
|
+
* `DeepPartial` types promise and the rule the runtime applies are the same
|
|
29
|
+
* three sentences, in one place.
|
|
30
|
+
*
|
|
31
|
+
* Every key is handled as an **own property**, never through `in` or a bare
|
|
32
|
+
* `[key] =` assignment. `key in base` is true for `'toString'` on a base that
|
|
33
|
+
* never declared it — it walks the prototype chain — and `merged[key] = …`
|
|
34
|
+
* for `key === '__proto__'` does not create a data property at all: it
|
|
35
|
+
* invokes the inherited setter and repoints `merged`'s own prototype. Neither
|
|
36
|
+
* is a filter field's schema value; a body field named `__proto__` is exactly
|
|
37
|
+
* as ordinary as one named anything else, and this is what makes it one.
|
|
38
|
+
*/
|
|
39
|
+
export declare function deepMergeBody(base: unknown, overlay: unknown): unknown;
|
|
40
|
+
//# sourceMappingURL=overlay-args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-args.d.ts","sourceRoot":"","sources":["../../src/hooks/overlay-args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,mBAAmB,EAC7B,IAAI,EAAE,eAAe,GACpB,mBAAmB,CAerB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAsBtE"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlays a caller's arguments onto a generated request.
|
|
3
|
+
*
|
|
4
|
+
* The generated sample plus the target's own hooks already produce a complete,
|
|
5
|
+
* schema-satisfying request. Arguments are therefore an **overlay**, not a
|
|
6
|
+
* replacement: a seeding call states the one field that has to differ — a name
|
|
7
|
+
* it will assert on later, an id it just created — and inherits everything
|
|
8
|
+
* else. Requiring the whole request instead made the caller re-state a body
|
|
9
|
+
* the description had already described.
|
|
10
|
+
*
|
|
11
|
+
* Parameter groups merge per key. The body deep-merges (see
|
|
12
|
+
* {@link deepMergeBody}).
|
|
13
|
+
*/
|
|
14
|
+
export function applyArgs(template, args) {
|
|
15
|
+
return {
|
|
16
|
+
...template,
|
|
17
|
+
headers: { ...template.headers, ...args.headers },
|
|
18
|
+
query: { ...template.query, ...args.query },
|
|
19
|
+
cookies: { ...template.cookies, ...args.cookies },
|
|
20
|
+
pathParameters: { ...template.pathParameters, ...args.path },
|
|
21
|
+
// `Object.hasOwn`, never `in`: `in` walks the prototype chain, so an args
|
|
22
|
+
// object inheriting a `body` — from `Object.prototype` being polluted, or
|
|
23
|
+
// from a caller's own base object — would overlay a body the caller never
|
|
24
|
+
// passed.
|
|
25
|
+
...(Object.hasOwn(args, 'body')
|
|
26
|
+
? { body: deepMergeBody(template.body, args.body) }
|
|
27
|
+
: {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Merges an overlay body onto the generated one: **objects recurse, arrays and
|
|
32
|
+
* primitives replace, `null` overrides.**
|
|
33
|
+
*
|
|
34
|
+
* Arrays replace rather than merge because a positional merge needs an index
|
|
35
|
+
* the caller cannot know is stable, and a per-element merge would produce
|
|
36
|
+
* elements the schema never described. `null` overrides for the same reason it
|
|
37
|
+
* is not "absent": a description that allows `null` means it, and merging
|
|
38
|
+
* around it would make the one value you cannot express the one you meant.
|
|
39
|
+
*
|
|
40
|
+
* Written here rather than taken from a library so that the rule the generated
|
|
41
|
+
* `DeepPartial` types promise and the rule the runtime applies are the same
|
|
42
|
+
* three sentences, in one place.
|
|
43
|
+
*
|
|
44
|
+
* Every key is handled as an **own property**, never through `in` or a bare
|
|
45
|
+
* `[key] =` assignment. `key in base` is true for `'toString'` on a base that
|
|
46
|
+
* never declared it — it walks the prototype chain — and `merged[key] = …`
|
|
47
|
+
* for `key === '__proto__'` does not create a data property at all: it
|
|
48
|
+
* invokes the inherited setter and repoints `merged`'s own prototype. Neither
|
|
49
|
+
* is a filter field's schema value; a body field named `__proto__` is exactly
|
|
50
|
+
* as ordinary as one named anything else, and this is what makes it one.
|
|
51
|
+
*/
|
|
52
|
+
export function deepMergeBody(base, overlay) {
|
|
53
|
+
if (!isPlainObject(base) || !isPlainObject(overlay)) {
|
|
54
|
+
return overlay;
|
|
55
|
+
}
|
|
56
|
+
const merged = { ...base };
|
|
57
|
+
for (const key of Object.keys(overlay)) {
|
|
58
|
+
const value = overlay[key];
|
|
59
|
+
const next = Object.prototype.hasOwnProperty.call(base, key)
|
|
60
|
+
? deepMergeBody(base[key], value)
|
|
61
|
+
: value;
|
|
62
|
+
Object.defineProperty(merged, key, {
|
|
63
|
+
value: next,
|
|
64
|
+
writable: true,
|
|
65
|
+
enumerable: true,
|
|
66
|
+
configurable: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return merged;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A JSON object, and nothing that merely looks like one.
|
|
73
|
+
*
|
|
74
|
+
* `null`, arrays, `Buffer`s and dates all answer `'object'` to `typeof`, and
|
|
75
|
+
* every one of them is a value the caller means to replace whole.
|
|
76
|
+
*/
|
|
77
|
+
function isPlainObject(value) {
|
|
78
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const prototype = Object.getPrototypeOf(value);
|
|
82
|
+
return prototype === Object.prototype || prototype === null;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=overlay-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-args.js","sourceRoot":"","sources":["../../src/hooks/overlay-args.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,SAAS,CACvB,QAA6B,EAC7B,IAAqB;IAErB,OAAO;QACL,GAAG,QAAQ;QACX,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE;QACjD,KAAK,EAAE,EAAE,GAAG,QAAQ,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE;QAC3C,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE;QACjD,cAAc,EAAE,EAAE,GAAG,QAAQ,CAAC,cAAc,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE;QAC5D,0EAA0E;QAC1E,0EAA0E;QAC1E,0EAA0E;QAC1E,UAAU;QACV,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC;YAC7B,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;YACnD,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa,EAAE,OAAgB;IAC3D,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAA4B,EAAE,GAAG,IAAI,EAAE,CAAC;IAEpD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC;YAC1D,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC;YACjC,CAAC,CAAC,KAAK,CAAC;QAEV,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;YACjC,KAAK,EAAE,IAAI;YACX,QAAQ,EAAE,IAAI;YACd,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAY,CAAC;IAE1D,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ThymianHttpTransaction } from '@thymian/core';
|
|
2
|
+
import type { TransactionCatalog } from '../selectors/transaction-catalog.js';
|
|
3
|
+
import type { HookDiagnostic } from './hook-diagnostics.js';
|
|
4
|
+
import type { CollectedRegistration } from './load-user-hooks.js';
|
|
5
|
+
/**
|
|
6
|
+
* The Transactions a target covers, reporting whatever stops it covering any.
|
|
7
|
+
*
|
|
8
|
+
* A target that resolves to nothing is a diagnostic, never an empty result that
|
|
9
|
+
* runs quietly. There are four ways a hook can silently stop doing its job — a
|
|
10
|
+
* dangling Selector, a value that is not a legal filter value, a path or glob
|
|
11
|
+
* that names nothing, and a filter whose legal values intersect nothing — and
|
|
12
|
+
* each one gets its own sentence, because "this matched nothing" is true and
|
|
13
|
+
* useless when the cause is one typo.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveTargeting(kind: string, target: unknown, entry: CollectedRegistration, catalog: TransactionCatalog, diagnostics: HookDiagnostic[]): readonly ThymianHttpTransaction[];
|
|
16
|
+
//# sourceMappingURL=resolve-targeting.d.ts.map
|