@sdxc/spec 0.0.0-pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +924 -0
  3. package/dist/ast.d.ts +193 -0
  4. package/dist/ast.js +9 -0
  5. package/dist/builtins.d.ts +29 -0
  6. package/dist/builtins.js +66 -0
  7. package/dist/cli.d.ts +21 -0
  8. package/dist/cli.js +297 -0
  9. package/dist/diagnostics.d.ts +47 -0
  10. package/dist/diagnostics.js +8 -0
  11. package/dist/errors.d.ts +131 -0
  12. package/dist/errors.js +159 -0
  13. package/dist/executor.d.ts +66 -0
  14. package/dist/executor.js +320 -0
  15. package/dist/expectation.d.ts +61 -0
  16. package/dist/expectation.js +222 -0
  17. package/dist/index.d.ts +51 -0
  18. package/dist/index.js +36 -0
  19. package/dist/lexer.d.ts +22 -0
  20. package/dist/lexer.js +284 -0
  21. package/dist/loader.d.ts +21 -0
  22. package/dist/loader.js +81 -0
  23. package/dist/parser.d.ts +24 -0
  24. package/dist/parser.js +502 -0
  25. package/dist/permissions.d.ts +139 -0
  26. package/dist/permissions.js +325 -0
  27. package/dist/plugin.d.ts +90 -0
  28. package/dist/plugin.js +9 -0
  29. package/dist/plugins/browser.d.ts +24 -0
  30. package/dist/plugins/browser.js +896 -0
  31. package/dist/plugins/cli.d.ts +17 -0
  32. package/dist/plugins/cli.js +134 -0
  33. package/dist/plugins/db-e2e-probe.d.ts +14 -0
  34. package/dist/plugins/db-e2e-probe.js +112 -0
  35. package/dist/plugins/db.d.ts +19 -0
  36. package/dist/plugins/db.js +199 -0
  37. package/dist/plugins/demo.d.ts +17 -0
  38. package/dist/plugins/demo.js +70 -0
  39. package/dist/plugins/env.d.ts +18 -0
  40. package/dist/plugins/env.js +87 -0
  41. package/dist/plugins/fs.d.ts +16 -0
  42. package/dist/plugins/fs.js +415 -0
  43. package/dist/plugins/http.d.ts +19 -0
  44. package/dist/plugins/http.js +505 -0
  45. package/dist/plugins/jwt.d.ts +17 -0
  46. package/dist/plugins/jwt.js +342 -0
  47. package/dist/plugins/sample.d.ts +27 -0
  48. package/dist/plugins/sample.js +400 -0
  49. package/dist/plugins/url.d.ts +18 -0
  50. package/dist/plugins/url.js +126 -0
  51. package/dist/project-config.d.ts +163 -0
  52. package/dist/project-config.js +497 -0
  53. package/dist/registry.d.ts +56 -0
  54. package/dist/registry.js +110 -0
  55. package/dist/reporter.d.ts +30 -0
  56. package/dist/reporter.js +237 -0
  57. package/dist/run.d.ts +74 -0
  58. package/dist/run.js +179 -0
  59. package/dist/runner.d.ts +52 -0
  60. package/dist/runner.js +38 -0
  61. package/dist/source.d.ts +37 -0
  62. package/dist/source.js +31 -0
  63. package/dist/sources.d.ts +45 -0
  64. package/dist/sources.js +54 -0
  65. package/dist/tokens.d.ts +34 -0
  66. package/dist/tokens.js +25 -0
  67. package/dist/transport-stdio.d.ts +34 -0
  68. package/dist/transport-stdio.js +400 -0
  69. package/dist/values.d.ts +48 -0
  70. package/dist/values.js +52 -0
  71. package/dist/workers.d.ts +40 -0
  72. package/dist/workers.js +26 -0
  73. package/dist/workspace-none.d.ts +23 -0
  74. package/dist/workspace-none.js +33 -0
  75. package/dist/workspace.d.ts +47 -0
  76. package/dist/workspace.js +116 -0
  77. package/package.json +28 -0
@@ -0,0 +1,400 @@
1
+ /**
2
+ * The built-in `sample` capability: generated input for a spec that needs a
3
+ * name, an address, or fifty of something, without a literal typed into the
4
+ * suite. Every tool draws from the test's own stream, so the same test sees
5
+ * the same values on every run whatever else the suite is doing.
6
+ *
7
+ * A call target carries at most one dot, so a module reaches a spec as one
8
+ * zero-argument tool returning every field that module generates —
9
+ * `let who = sample.person` then `who.job_title`. Generators that need an
10
+ * argument keep a tool of their own. Fields are named the way a spec names
11
+ * things, in snake case.
12
+ *
13
+ * The tools are actions rather than observations: a draw advances the stream,
14
+ * and polling `eventually` until a random value matches is never what an
15
+ * author meant. None needs a permission — generation is computation, reaching
16
+ * nothing outside the process.
17
+ *
18
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
19
+ * @copyright Sergio Xalambrí 2026
20
+ */
21
+ import { failure, success } from "@sdxc/result";
22
+ import { createSample } from "@sdxc/sample";
23
+ import { ToolError } from "../errors.js";
24
+ /** One module, as the record a spec binds and reads fields off. */
25
+ const MODULE_TOOLS = [
26
+ "person",
27
+ "internet",
28
+ "location",
29
+ "company",
30
+ "lorem",
31
+ "date",
32
+ "string",
33
+ "number",
34
+ "color",
35
+ "datatype",
36
+ "git",
37
+ "hacker",
38
+ "phone",
39
+ "system",
40
+ ];
41
+ /** The generators that need an argument, plus the two shortest shortcuts. */
42
+ const CALL_TOOLS = ["email", "uuid", "int", "float", "words", "pick"];
43
+ /** One line of documentation per module tool, shown in diagnostics. */
44
+ const MODULE_SUMMARIES = {
45
+ person: "A person: names, titles, sex, job, contact details.",
46
+ internet: "Addresses, handles, links, and protocol values.",
47
+ location: "A place: country, city, street, postcode, coordinates.",
48
+ company: "A company name and the phrases around it.",
49
+ lorem: "Placeholder prose in every shape.",
50
+ date: "Instants around the test's own start time, as ISO timestamps.",
51
+ string: "Identifiers and character runs.",
52
+ number: "Numbers, in every base.",
53
+ color: "A color, as a name and in each notation.",
54
+ datatype: "A boolean.",
55
+ git: "A branch, a hash, a message, a whole log entry.",
56
+ hacker: "Technical-sounding filler.",
57
+ phone: "A phone number in each format, and an IMEI.",
58
+ system: "File names, paths, types, and machine identifiers.",
59
+ };
60
+ const DESCRIPTORS = [
61
+ ...MODULE_TOOLS.map((name) => ({
62
+ name,
63
+ summary: MODULE_SUMMARIES[name],
64
+ kind: "action",
65
+ params: [],
66
+ })),
67
+ {
68
+ name: "email",
69
+ summary: "An email address on a domain reserved for documentation.",
70
+ kind: "action",
71
+ params: [],
72
+ },
73
+ {
74
+ name: "uuid",
75
+ summary: "A version 4 UUID, drawn from the test's stream.",
76
+ kind: "action",
77
+ params: [],
78
+ },
79
+ {
80
+ name: "int",
81
+ summary: "An integer between two bounds, both included.",
82
+ kind: "action",
83
+ params: [
84
+ { name: "min", kind: "value", required: true, summary: "Lowest value, included." },
85
+ { name: "max", kind: "value", required: true, summary: "Highest value, included." },
86
+ ],
87
+ },
88
+ {
89
+ name: "float",
90
+ summary: "A number between two bounds, with two decimals.",
91
+ kind: "action",
92
+ params: [
93
+ { name: "min", kind: "value", required: true, summary: "Lowest value, included." },
94
+ { name: "max", kind: "value", required: true, summary: "Highest value, excluded." },
95
+ ],
96
+ },
97
+ {
98
+ name: "words",
99
+ summary: "Placeholder prose of a given number of words.",
100
+ kind: "action",
101
+ params: [
102
+ { name: "count", kind: "value", required: true, summary: "How many words to return." },
103
+ ],
104
+ },
105
+ {
106
+ name: "pick",
107
+ summary: "One element of a list.",
108
+ kind: "action",
109
+ params: [{ name: "list", kind: "value", required: true, summary: "The list to pick from." }],
110
+ },
111
+ ];
112
+ /**
113
+ * Create the built-in `sample` plugin (namespace `"sample"`). Values come from
114
+ * the stream on the call's context, so two runs of one test generate the same
115
+ * data and two tests never draw from each other's stream.
116
+ */
117
+ export function createSamplePlugin() {
118
+ return {
119
+ namespace: "sample",
120
+ describe() {
121
+ return DESCRIPTORS;
122
+ },
123
+ async call(tool, args, context) {
124
+ if (!isSampleTool(tool)) {
125
+ let available = [...MODULE_TOOLS, ...CALL_TOOLS].join(", ");
126
+ return failure(new ToolError(`sample has no tool "${tool}"; available tools: ${available}`));
127
+ }
128
+ return generate(tool, args, context);
129
+ },
130
+ };
131
+ }
132
+ function isSampleTool(tool) {
133
+ return (MODULE_TOOLS.includes(tool) ||
134
+ CALL_TOOLS.includes(tool));
135
+ }
136
+ function isModuleTool(tool) {
137
+ return MODULE_TOOLS.includes(tool);
138
+ }
139
+ /**
140
+ * Run one tool against the test's stream. The generator throws a `RangeError`
141
+ * on a bound it cannot honor; every throw becomes a `ToolError` naming the
142
+ * tool, since a plugin reports failure as a result rather than an exception.
143
+ */
144
+ function generate(tool, args, context) {
145
+ let sample = createSample({ seed: context.random, now: context.now });
146
+ try {
147
+ if (isModuleTool(tool))
148
+ return success(record(tool, sample));
149
+ if (tool === "email")
150
+ return success(sample.internet.email());
151
+ if (tool === "uuid")
152
+ return success(sample.string.uuid());
153
+ if (tool === "int") {
154
+ let min = readNumber(args, 0);
155
+ if (min === undefined)
156
+ return numberError(tool, "min");
157
+ let max = readNumber(args, 1);
158
+ if (max === undefined)
159
+ return numberError(tool, "max");
160
+ return success(sample.number.int({ min, max }));
161
+ }
162
+ if (tool === "float") {
163
+ let min = readNumber(args, 0);
164
+ if (min === undefined)
165
+ return numberError(tool, "min");
166
+ let max = readNumber(args, 1);
167
+ if (max === undefined)
168
+ return numberError(tool, "max");
169
+ return success(sample.number.float({ min, max }));
170
+ }
171
+ if (tool === "words") {
172
+ let count = readNumber(args, 0);
173
+ if (count === undefined)
174
+ return numberError(tool, "count");
175
+ return success(sample.lorem.words(count));
176
+ }
177
+ let list = args[0];
178
+ if (list === undefined || list.kind !== "value" || !Array.isArray(list.value)) {
179
+ return failure(new ToolError("sample.pick needs a list to pick from."));
180
+ }
181
+ if (list.value.length === 0) {
182
+ return failure(new ToolError("sample.pick needs a list with at least one item."));
183
+ }
184
+ return success(sample.helpers.pick(list.value));
185
+ }
186
+ catch (error) {
187
+ let reason = error instanceof Error ? error.message : String(error);
188
+ return failure(new ToolError(`sample.${tool} could not generate a value: ${reason}`));
189
+ }
190
+ }
191
+ /** Build one module's record, every field named as a spec names things. */
192
+ function record(tool, sample) {
193
+ if (tool === "person") {
194
+ let person = sample.person.record();
195
+ return {
196
+ first_name: person.firstName,
197
+ last_name: person.lastName,
198
+ middle_name: sample.person.middleName(),
199
+ full_name: person.fullName,
200
+ prefix: sample.person.prefix(),
201
+ suffix: sample.person.suffix(),
202
+ sex: person.sex,
203
+ gender: sample.person.gender(),
204
+ zodiac_sign: sample.person.zodiacSign(),
205
+ job_area: sample.person.jobArea(),
206
+ job_descriptor: sample.person.jobDescriptor(),
207
+ job_type: sample.person.jobType(),
208
+ job_title: person.jobTitle,
209
+ bio: sample.person.bio(),
210
+ email: person.email,
211
+ username: person.username,
212
+ phone: person.phone,
213
+ };
214
+ }
215
+ if (tool === "internet") {
216
+ return {
217
+ email: sample.internet.email(),
218
+ username: sample.internet.username(),
219
+ display_name: sample.internet.displayName(),
220
+ domain_name: sample.internet.domainName(),
221
+ domain_suffix: sample.internet.domainSuffix(),
222
+ domain_word: sample.internet.domainWord(),
223
+ url: sample.internet.url(),
224
+ password: sample.internet.password(),
225
+ emoji: sample.internet.emoji(),
226
+ http_method: sample.internet.httpMethod(),
227
+ http_status_code: sample.internet.httpStatusCode(),
228
+ ip: sample.internet.ip(),
229
+ ipv4: sample.internet.ipv4(),
230
+ ipv6: sample.internet.ipv6(),
231
+ mac: sample.internet.mac(),
232
+ port: sample.internet.port(),
233
+ protocol: sample.internet.protocol(),
234
+ jwt: sample.internet.jwt(),
235
+ jwt_algorithm: sample.internet.jwtAlgorithm(),
236
+ user_agent: sample.internet.userAgent(),
237
+ };
238
+ }
239
+ if (tool === "location") {
240
+ /**
241
+ * Built from one set of parts rather than field by field, so a spec that
242
+ * reads the country and the postal address off the same record sees one
243
+ * place rather than two.
244
+ */
245
+ let country = sample.location.country();
246
+ let city = sample.location.city({ country });
247
+ let streetAddress = sample.location.streetAddress();
248
+ let zipCode = sample.location.zipCode();
249
+ return {
250
+ country,
251
+ city,
252
+ country_code: sample.location.countryCode(),
253
+ continent: sample.location.continent(),
254
+ state: sample.location.state(),
255
+ state_abbreviation: sample.location.state({ abbreviated: true }),
256
+ county: sample.location.county(),
257
+ street: sample.location.street(),
258
+ building_number: sample.location.buildingNumber(),
259
+ street_address: streetAddress,
260
+ secondary_address: sample.location.secondaryAddress(),
261
+ zip_code: zipCode,
262
+ postal_address: `${streetAddress}, ${city} ${zipCode}, ${country}`,
263
+ direction: sample.location.direction(),
264
+ cardinal_direction: sample.location.cardinalDirection(),
265
+ ordinal_direction: sample.location.ordinalDirection(),
266
+ language: sample.location.language(),
267
+ time_zone: sample.location.timeZone(),
268
+ latitude: sample.location.latitude(),
269
+ longitude: sample.location.longitude(),
270
+ };
271
+ }
272
+ if (tool === "company") {
273
+ return {
274
+ name: sample.company.name(),
275
+ catch_phrase: sample.company.catchPhrase(),
276
+ catch_phrase_adjective: sample.company.catchPhraseAdjective(),
277
+ catch_phrase_descriptor: sample.company.catchPhraseDescriptor(),
278
+ catch_phrase_noun: sample.company.catchPhraseNoun(),
279
+ buzz_phrase: sample.company.buzzPhrase(),
280
+ buzz_adjective: sample.company.buzzAdjective(),
281
+ buzz_noun: sample.company.buzzNoun(),
282
+ buzz_verb: sample.company.buzzVerb(),
283
+ };
284
+ }
285
+ if (tool === "lorem") {
286
+ return {
287
+ word: sample.lorem.word(),
288
+ words: sample.lorem.words(5),
289
+ sentence: sample.lorem.sentence(),
290
+ paragraph: sample.lorem.paragraph(),
291
+ lines: sample.lorem.lines(3),
292
+ slug: sample.lorem.slug(),
293
+ text: sample.lorem.text(),
294
+ };
295
+ }
296
+ if (tool === "date") {
297
+ return {
298
+ past: sample.date.past().toISOString(),
299
+ future: sample.date.future().toISOString(),
300
+ recent: sample.date.recent().toISOString(),
301
+ soon: sample.date.soon().toISOString(),
302
+ anytime: sample.date.anytime().toISOString(),
303
+ birthdate: sample.date.birthdate().toISOString(),
304
+ month: sample.date.month(),
305
+ weekday: sample.date.weekday(),
306
+ time_zone: sample.date.timeZone(),
307
+ };
308
+ }
309
+ if (tool === "string") {
310
+ return {
311
+ uuid: sample.string.uuid(),
312
+ ulid: sample.string.ulid(),
313
+ nanoid: sample.string.nanoid(),
314
+ alpha: sample.string.alpha(10),
315
+ alphanumeric: sample.string.alphanumeric(10),
316
+ numeric: sample.string.numeric(10),
317
+ hexadecimal: sample.string.hexadecimal(16),
318
+ binary: sample.string.binary(8),
319
+ octal: sample.string.octal(8),
320
+ symbol: sample.string.symbol(4),
321
+ sample: sample.string.sample(10),
322
+ };
323
+ }
324
+ if (tool === "number") {
325
+ return {
326
+ int: sample.number.int(),
327
+ float: sample.number.float(),
328
+ hex: sample.number.hex(),
329
+ binary: sample.number.binary(),
330
+ octal: sample.number.octal(),
331
+ roman_numeral: sample.number.romanNumeral(),
332
+ big_int: String(sample.number.bigInt()),
333
+ };
334
+ }
335
+ if (tool === "color") {
336
+ return {
337
+ human: sample.color.human(),
338
+ hex: String(sample.color.rgb()),
339
+ rgb: String(sample.color.rgb({ format: "css" })),
340
+ hsl: String(sample.color.hsl({ format: "css" })),
341
+ space: sample.color.space(),
342
+ css_function: sample.color.cssSupportedFunction(),
343
+ };
344
+ }
345
+ if (tool === "datatype") {
346
+ return { boolean: sample.datatype.boolean() };
347
+ }
348
+ if (tool === "git") {
349
+ return {
350
+ branch: sample.git.branch(),
351
+ commit_sha: sample.git.commitSha(),
352
+ short_sha: sample.git.commitSha({ length: 7 }),
353
+ commit_message: sample.git.commitMessage(),
354
+ commit_date: sample.git.commitDate(),
355
+ commit_entry: sample.git.commitEntry(),
356
+ };
357
+ }
358
+ if (tool === "hacker") {
359
+ return {
360
+ abbreviation: sample.hacker.abbreviation(),
361
+ adjective: sample.hacker.adjective(),
362
+ noun: sample.hacker.noun(),
363
+ verb: sample.hacker.verb(),
364
+ ingverb: sample.hacker.ingverb(),
365
+ phrase: sample.hacker.phrase(),
366
+ };
367
+ }
368
+ if (tool === "phone") {
369
+ return {
370
+ number: sample.phone.number(),
371
+ national: sample.phone.number({ style: "national" }),
372
+ international: sample.phone.number({ style: "international" }),
373
+ imei: sample.phone.imei(),
374
+ };
375
+ }
376
+ return {
377
+ file_name: sample.system.fileName(),
378
+ file_ext: sample.system.fileExt(),
379
+ file_type: sample.system.fileType(),
380
+ common_file_name: sample.system.commonFileName(),
381
+ common_file_ext: sample.system.commonFileExt(),
382
+ common_file_type: sample.system.commonFileType(),
383
+ mime_type: sample.system.mimeType(),
384
+ directory_path: sample.system.directoryPath(),
385
+ file_path: sample.system.filePath(),
386
+ network_interface: sample.system.networkInterface(),
387
+ semver: sample.system.semver(),
388
+ cron: sample.system.cron(),
389
+ };
390
+ }
391
+ /** Read a positional argument as a number, or `undefined` when it is not one. */
392
+ function readNumber(args, index) {
393
+ let argument = args[index];
394
+ if (argument === undefined || argument.kind !== "value")
395
+ return undefined;
396
+ return typeof argument.value === "number" ? argument.value : undefined;
397
+ }
398
+ function numberError(tool, name) {
399
+ return failure(new ToolError(`sample.${tool} needs a number for "${name}".`));
400
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The built-in `url` capability: pure, permissionless parsing of an absolute
3
+ * URL string into a query-string parameter, a fragment parameter, the path, or
4
+ * the host. It lets a spec read a value out of a URL it already holds — most
5
+ * often the authorization `code` in the redirect URL a `browser.url`
6
+ * observation returned after an OAuth authorize step. Every tool is a pure
7
+ * computation, so all are `observable` and need no permission grant.
8
+ *
9
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
10
+ * @copyright Sergio Xalambrí 2026
11
+ */
12
+ import type { Plugin } from "../plugin.js";
13
+ /**
14
+ * Create the built-in `url` plugin (namespace `"url"`). Every tool parses its
15
+ * first argument as an absolute URL; a non-string argument, an unparseable
16
+ * URL, or a missing `query`/`fragment` parameter is a {@link ToolError}.
17
+ */
18
+ export declare function createUrlPlugin(): Plugin;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * The built-in `url` capability: pure, permissionless parsing of an absolute
3
+ * URL string into a query-string parameter, a fragment parameter, the path, or
4
+ * the host. It lets a spec read a value out of a URL it already holds — most
5
+ * often the authorization `code` in the redirect URL a `browser.url`
6
+ * observation returned after an OAuth authorize step. Every tool is a pure
7
+ * computation, so all are `observable` and need no permission grant.
8
+ *
9
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
10
+ * @copyright Sergio Xalambrí 2026
11
+ */
12
+ import { failure, isFailure, success } from "@sdxc/result";
13
+ import { ToolError } from "../errors.js";
14
+ const URL_TOOLS = ["query", "fragment", "path", "host"];
15
+ const DESCRIPTORS = [
16
+ {
17
+ name: "query",
18
+ summary: "Read the value of a query-string parameter from an absolute URL.",
19
+ kind: "observable",
20
+ params: [
21
+ { name: "url", kind: "value", required: true, summary: "The absolute URL to read." },
22
+ { name: "name", kind: "value", required: true, summary: "The query parameter to return." },
23
+ ],
24
+ },
25
+ {
26
+ name: "fragment",
27
+ summary: "Read the value of a parameter from a URL's fragment (after the #).",
28
+ kind: "observable",
29
+ params: [
30
+ { name: "url", kind: "value", required: true, summary: "The absolute URL to read." },
31
+ { name: "name", kind: "value", required: true, summary: "The fragment parameter to return." },
32
+ ],
33
+ },
34
+ {
35
+ name: "path",
36
+ summary: "Read an absolute URL's pathname.",
37
+ kind: "observable",
38
+ params: [{ name: "url", kind: "value", required: true, summary: "The absolute URL to read." }],
39
+ },
40
+ {
41
+ name: "host",
42
+ summary: "Read an absolute URL's host, including the port when present.",
43
+ kind: "observable",
44
+ params: [{ name: "url", kind: "value", required: true, summary: "The absolute URL to read." }],
45
+ },
46
+ ];
47
+ /**
48
+ * Create the built-in `url` plugin (namespace `"url"`). Every tool parses its
49
+ * first argument as an absolute URL; a non-string argument, an unparseable
50
+ * URL, or a missing `query`/`fragment` parameter is a {@link ToolError}.
51
+ */
52
+ export function createUrlPlugin() {
53
+ return {
54
+ namespace: "url",
55
+ describe() {
56
+ return DESCRIPTORS;
57
+ },
58
+ async call(tool, args) {
59
+ if (!isUrlTool(tool)) {
60
+ return failure(new ToolError(`url has no tool "${tool}"; available tools: ${URL_TOOLS.join(", ")}`));
61
+ }
62
+ if (tool === "query" || tool === "fragment")
63
+ return param(tool, args);
64
+ return part(tool, args);
65
+ },
66
+ };
67
+ }
68
+ function isUrlTool(tool) {
69
+ return URL_TOOLS.includes(tool);
70
+ }
71
+ /**
72
+ * A fragment parses as a query string, the shape an implicit/hybrid OAuth flow
73
+ * returns its tokens in. An absent parameter is a tool error naming both the
74
+ * parameter and the URL, so a spec fails loud at the point of the lookup.
75
+ */
76
+ function param(tool, args) {
77
+ let url = readUrl(tool, args);
78
+ if (isFailure(url))
79
+ return url;
80
+ let name = readName(tool, args);
81
+ if (isFailure(name))
82
+ return name;
83
+ let params = tool === "query" ? url.data.searchParams : new URLSearchParams(url.data.hash.replace(/^#/, ""));
84
+ let found = params.get(name.data);
85
+ if (found === null) {
86
+ let where = tool === "query" ? "query string" : "fragment";
87
+ return failure(new ToolError(`url.${tool} found no ${where} parameter "${name.data}" in the URL ${args0Raw(args)}`));
88
+ }
89
+ return success(found);
90
+ }
91
+ function part(tool, args) {
92
+ let url = readUrl(tool, args);
93
+ if (isFailure(url))
94
+ return url;
95
+ return success(tool === "path" ? url.data.pathname : url.data.host);
96
+ }
97
+ /**
98
+ * Validate and parse the first argument as an absolute URL. A missing
99
+ * argument, a non-string argument, or an unparseable URL is a tool error.
100
+ */
101
+ function readUrl(tool, args) {
102
+ let first = args[0];
103
+ if (first === undefined || first.kind !== "value" || typeof first.value !== "string") {
104
+ return failure(new ToolError(`url.${tool} requires its first argument to be a URL string`));
105
+ }
106
+ try {
107
+ return success(new URL(first.value));
108
+ }
109
+ catch {
110
+ return failure(new ToolError(`url.${tool} could not parse the URL "${first.value}"`));
111
+ }
112
+ }
113
+ function readName(tool, args) {
114
+ let second = args[1];
115
+ if (second === undefined || second.kind !== "value" || typeof second.value !== "string") {
116
+ return failure(new ToolError(`url.${tool} requires its second argument to be a parameter name`));
117
+ }
118
+ return success(second.value);
119
+ }
120
+ function args0Raw(args) {
121
+ let first = args[0];
122
+ if (first !== undefined && first.kind === "value" && typeof first.value === "string") {
123
+ return first.value;
124
+ }
125
+ return "the URL";
126
+ }