@jterrazz/test 7.1.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +209 -230
  2. package/dist/checker.d.ts +1 -0
  3. package/dist/checker.js +741 -0
  4. package/dist/index.d.ts +1296 -529
  5. package/dist/index.js +3303 -1261
  6. package/dist/intercept.js +129 -290
  7. package/dist/match.js +153 -0
  8. package/dist/oxlint.cjs +2931 -0
  9. package/dist/oxlint.d.cts +150 -0
  10. package/dist/oxlint.d.ts +150 -0
  11. package/dist/oxlint.js +2925 -0
  12. package/package.json +47 -41
  13. package/dist/chunk.cjs +0 -28
  14. package/dist/index.cjs +0 -1814
  15. package/dist/index.cjs.map +0 -1
  16. package/dist/index.d.cts +0 -734
  17. package/dist/index.js.map +0 -1
  18. package/dist/intercept.cjs +0 -313
  19. package/dist/intercept.cjs.map +0 -1
  20. package/dist/intercept.d.cts +0 -105
  21. package/dist/intercept.d.ts +0 -105
  22. package/dist/intercept.js.map +0 -1
  23. package/dist/intercept2.cjs +0 -81
  24. package/dist/intercept2.cjs.map +0 -1
  25. package/dist/intercept2.js +0 -81
  26. package/dist/intercept2.js.map +0 -1
  27. package/dist/mock-of.cjs +0 -32
  28. package/dist/mock-of.cjs.map +0 -1
  29. package/dist/mock-of.d.cts +0 -27
  30. package/dist/mock-of.d.ts +0 -27
  31. package/dist/mock-of.js +0 -19
  32. package/dist/mock-of.js.map +0 -1
  33. package/dist/mock.cjs +0 -4
  34. package/dist/mock.d.cts +0 -2
  35. package/dist/mock.d.ts +0 -2
  36. package/dist/mock.js +0 -2
  37. package/dist/services.cjs +0 -5
  38. package/dist/services.d.cts +0 -2
  39. package/dist/services.d.ts +0 -2
  40. package/dist/services.js +0 -2
  41. package/dist/sqlite.adapter.cjs +0 -350
  42. package/dist/sqlite.adapter.cjs.map +0 -1
  43. package/dist/sqlite.adapter.d.cts +0 -209
  44. package/dist/sqlite.adapter.d.ts +0 -209
  45. package/dist/sqlite.adapter.js +0 -331
  46. package/dist/sqlite.adapter.js.map +0 -1
  47. package/dist/types.d.cts +0 -42
  48. package/dist/types.d.ts +0 -42
package/dist/intercept.js CHANGED
@@ -1,310 +1,149 @@
1
- //#region src/builder/common/intercept/adapters/anthropic.ts
2
- const ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
3
- function matchesFilter(body, filter) {
4
- if (filter.model) {
5
- const model = body?.model;
6
- if (typeof filter.model === "string" && model !== filter.model) return false;
7
- if (filter.model instanceof RegExp && !filter.model.test(model ?? "")) return false;
1
+ //#region src/integrations/msw/intercept.ts
2
+ let mswModule = null;
3
+ let mswHttp = null;
4
+ async function loadMsw() {
5
+ if (!mswModule) {
6
+ mswModule = await import("msw/node");
7
+ mswHttp = await import("msw");
8
8
  }
9
- if (filter.system) {
10
- const system = typeof body?.system === "string" ? body.system : "";
11
- if (typeof filter.system === "string" && !system.includes(filter.system)) return false;
12
- if (filter.system instanceof RegExp && !filter.system.test(system)) return false;
13
- }
14
- if (filter.user) {
15
- const userMsg = body?.messages?.find((m) => m.role === "user")?.content ?? "";
16
- const text = typeof userMsg === "string" ? userMsg : JSON.stringify(userMsg);
17
- if (typeof filter.user === "string" && !text.includes(filter.user)) return false;
18
- if (filter.user instanceof RegExp && !filter.user.test(text)) return false;
19
- }
20
- if (filter.tools) {
21
- const names = body?.tools?.map((t) => t.name).filter(Boolean) ?? [];
22
- if (!filter.tools.every((t) => names.includes(t))) return false;
23
- }
24
- return true;
25
- }
26
- function buildReply(data) {
27
9
  return {
28
- status: 200,
29
- body: {
30
- id: "msg-test",
31
- type: "message",
32
- role: "assistant",
33
- content: [{
34
- type: "text",
35
- text: typeof data === "string" ? data : JSON.stringify(data)
36
- }],
37
- model: "claude-sonnet-4-20250514",
38
- stop_reason: "end_turn",
39
- usage: {
40
- input_tokens: 10,
41
- output_tokens: 10
42
- }
43
- }
10
+ node: mswModule,
11
+ msw: mswHttp
44
12
  };
45
13
  }
14
+ let serverInstance = null;
46
15
  /**
47
- * Anthropic API intercept helpers.
16
+ * Start the MSW server (once per process).
48
17
  */
49
- const anthropic = {
50
- request(filter) {
51
- return {
52
- adapter: "anthropic",
53
- method: "POST",
54
- url: ANTHROPIC_MESSAGES_URL,
55
- match: filter ? (body) => matchesFilter(body, filter) : void 0,
56
- wrap: buildReply
57
- };
58
- },
59
- reply: buildReply,
60
- error(status, message) {
61
- return {
62
- status,
63
- body: {
64
- type: "error",
65
- error: {
66
- type: status === 429 ? "rate_limit_error" : "api_error",
67
- message: message ?? `Anthropic error (${status})`
68
- }
69
- }
70
- };
71
- },
72
- timeout() {
73
- return {
74
- status: 200,
75
- body: {},
76
- delay: 3e4
77
- };
78
- },
79
- messages(filter) {
80
- return anthropic.request(filter);
81
- },
82
- response: buildReply
83
- };
84
- //#endregion
85
- //#region src/builder/common/intercept/adapters/http.ts
86
- function wrapJson(data) {
87
- return {
88
- status: 200,
89
- body: data
90
- };
18
+ async function ensureInterceptServer() {
19
+ if (serverInstance) return;
20
+ const { node } = await loadMsw();
21
+ serverInstance = node.setupServer();
22
+ serverInstance.listen({ onUnhandledRequest: "bypass" });
23
+ }
24
+ function describeTrigger(entry) {
25
+ return `${entry.trigger.method === "*" ? "ANY" : entry.trigger.method} ${String(entry.trigger.url)}`;
91
26
  }
92
27
  /**
93
- * Generic HTTP intercept helpers for any URL.
94
- *
95
- * @example
96
- * .intercept(http.get('https://api.example.com/data'), 'http/response.json')
28
+ * Pure FIFO queue over the chain's intercept entries. Each observed request
29
+ * consumes the first unconsumed entry whose URL, method, and optional body
30
+ * matcher accept it. Exported for unit tests — MSW never reaches this class.
97
31
  */
98
- const http = {
99
- get(url) {
100
- return {
101
- adapter: "http",
102
- method: "GET",
103
- url,
104
- wrap: wrapJson
105
- };
106
- },
107
- post(url) {
108
- return {
109
- adapter: "http",
110
- method: "POST",
111
- url,
112
- wrap: wrapJson
113
- };
114
- },
115
- put(url) {
116
- return {
117
- adapter: "http",
118
- method: "PUT",
119
- url,
120
- wrap: wrapJson
121
- };
122
- },
123
- delete(url) {
124
- return {
125
- adapter: "http",
126
- method: "DELETE",
127
- url,
128
- wrap: wrapJson
129
- };
130
- },
131
- any(url) {
132
- return {
133
- adapter: "http",
134
- method: "*",
135
- url,
136
- wrap: wrapJson
137
- };
138
- },
139
- json(data, status = 200) {
140
- return {
141
- status,
142
- body: data
143
- };
144
- },
145
- error(status, message) {
146
- return {
147
- status,
148
- body: { error: message ?? `HTTP ${status}` }
149
- };
150
- }
151
- };
152
- //#endregion
153
- //#region src/builder/common/intercept/adapters/openai.ts
154
- const OPENAI_CHAT_URL = "https://api.openai.com/v1/chat/completions";
155
- const OPENAI_RESPONSES_URL = "https://api.openai.com/v1/responses";
156
- function matchesChatFilter(body, filter) {
157
- if (filter.model) {
158
- const model = body?.model;
159
- if (typeof filter.model === "string" && model !== filter.model) return false;
160
- if (filter.model instanceof RegExp && !filter.model.test(model ?? "")) return false;
161
- }
162
- if (filter.system) {
163
- const msg = body?.messages?.find((m) => m.role === "system")?.content ?? "";
164
- if (typeof filter.system === "string" && !msg.includes(filter.system)) return false;
165
- if (filter.system instanceof RegExp && !filter.system.test(msg)) return false;
166
- }
167
- if (filter.user) {
168
- const msg = body?.messages?.find((m) => m.role === "user")?.content ?? "";
169
- if (typeof filter.user === "string" && !msg.includes(filter.user)) return false;
170
- if (filter.user instanceof RegExp && !filter.user.test(msg)) return false;
171
- }
172
- if (filter.tools) {
173
- const names = body?.tools?.map((t) => t.function?.name).filter(Boolean) ?? [];
174
- if (!filter.tools.every((t) => names.includes(t))) return false;
32
+ var InterceptQueue = class {
33
+ consumed;
34
+ entries;
35
+ constructor(entries) {
36
+ this.entries = entries;
37
+ this.consumed = entries.map(() => false);
175
38
  }
176
- if (filter.temperature !== void 0 && body?.temperature !== filter.temperature) return false;
177
- return true;
178
- }
179
- function matchesResponsesFilter(body, filter) {
180
- if (filter.model) {
181
- const model = body?.model;
182
- if (typeof filter.model === "string" && model !== filter.model) return false;
183
- if (filter.model instanceof RegExp && !filter.model.test(model ?? "")) return false;
39
+ /**
40
+ * Unique trigger URLs, preserving RegExp/string identity for MSW routing.
41
+ * Deduped by string form — two RegExp objects with the same source are
42
+ * one trigger (and get one handler).
43
+ */
44
+ get urls() {
45
+ const urls = /* @__PURE__ */ new Map();
46
+ for (const entry of this.entries) {
47
+ const key = String(entry.trigger.url);
48
+ if (!urls.has(key)) urls.set(key, entry.trigger.url);
49
+ }
50
+ return [...urls.values()];
184
51
  }
185
- if (filter.system) {
186
- const instructions = body?.instructions ?? "";
187
- const systemInput = body?.input?.find?.((m) => m.role === "system")?.content ?? "";
188
- const text = instructions || systemInput;
189
- if (typeof filter.system === "string" && !text.includes(filter.system)) return false;
190
- if (filter.system instanceof RegExp && !filter.system.test(text)) return false;
52
+ /** Methods declared for a given trigger URL. */
53
+ methodsFor(url) {
54
+ const methods = /* @__PURE__ */ new Set();
55
+ for (const entry of this.entries) if (sameUrl(entry.trigger.url, url)) methods.add(entry.trigger.method);
56
+ return [...methods];
191
57
  }
192
- if (filter.user) {
193
- const msgs = (body?.input ?? []).filter((m) => m.role === "user").map((m) => typeof m.content === "string" ? m.content : JSON.stringify(m.content)).join(" ");
194
- if (typeof filter.user === "string" && !msgs.includes(filter.user)) return false;
195
- if (filter.user instanceof RegExp && !filter.user.test(msgs)) return false;
58
+ /**
59
+ * Consume and return the first unconsumed entry registered for
60
+ * `handlerUrl`/`method` that accepts the request, or null when the queue
61
+ * for that trigger is exhausted (or no matcher accepts).
62
+ */
63
+ take(handlerUrl, method, request) {
64
+ for (let i = 0; i < this.entries.length; i++) {
65
+ if (this.consumed[i]) continue;
66
+ const entry = this.entries[i];
67
+ if (!sameUrl(entry.trigger.url, handlerUrl)) continue;
68
+ if (entry.trigger.method !== method && entry.trigger.method !== "*") continue;
69
+ if (entry.trigger.match && !entry.trigger.match(request)) continue;
70
+ this.consumed[i] = true;
71
+ return entry;
72
+ }
73
+ return null;
196
74
  }
197
- if (filter.tools) {
198
- const names = body?.tools?.map((t) => t.name ?? t.function?.name).filter(Boolean) ?? [];
199
- if (!filter.tools.every((t) => names.includes(t))) return false;
75
+ /**
76
+ * Build the strict-intercept failure for a request that matched no
77
+ * registered intercept (CONVENTIONS D7): method + URL of the offending
78
+ * request, plus every registered trigger and its consumption state.
79
+ */
80
+ unmatchedError(method, url) {
81
+ const triggers = this.entries.length === 0 ? " (no intercepts registered)" : this.entries.map((entry, i) => ` - ${describeTrigger(entry)}${this.consumed[i] ? " (already consumed)" : ""}`).join("\n");
82
+ return /* @__PURE__ */ new Error(`Unmatched outgoing HTTP request during spec: ${method} ${url}\nRegistered intercepts:\n${triggers}\nEvery outgoing request of a chain that declares intercepts must match one — add an .intercept() for it (or one more if its queue is exhausted).`);
200
83
  }
201
- return true;
84
+ };
85
+ function sameUrl(a, b) {
86
+ return a === b || String(a) === String(b);
202
87
  }
203
- function buildChatReply(data) {
204
- const content = typeof data === "string" ? data : JSON.stringify(data);
205
- return {
206
- status: 200,
207
- body: {
208
- id: "chatcmpl-test",
209
- object: "chat.completion",
210
- created: Math.floor(Date.now() / 1e3),
211
- model: "gpt-4o-test",
212
- choices: [{
213
- index: 0,
214
- message: {
215
- role: "assistant",
216
- content
217
- },
218
- finish_reason: "stop"
219
- }],
220
- usage: {
221
- prompt_tokens: 10,
222
- completion_tokens: 10,
223
- total_tokens: 20
224
- }
225
- }
88
+ /**
89
+ * Register intercept entries as MSW handlers for one chain. A trailing
90
+ * catch-all handler records any request that no entry accepted — the
91
+ * builder rethrows it as the spec failure (rejecting the action promise,
92
+ * never an unhandled rejection).
93
+ */
94
+ async function registerIntercepts(entries) {
95
+ if (entries.length === 0) return {
96
+ cleanup: () => {},
97
+ violation: () => null
226
98
  };
227
- }
228
- function buildResponsesReply(data) {
229
- const text = typeof data === "string" ? data : JSON.stringify(data);
230
- return {
231
- status: 200,
232
- body: {
233
- id: "resp-test",
234
- object: "response",
235
- created_at: Math.floor(Date.now() / 1e3),
236
- model: "gpt-4o-test",
237
- output: [{
238
- type: "message",
239
- id: "msg-test",
240
- role: "assistant",
241
- content: [{
242
- type: "output_text",
243
- text,
244
- annotations: []
245
- }]
246
- }],
247
- usage: {
248
- input_tokens: 10,
249
- output_tokens: 10,
250
- total_tokens: 20
99
+ await ensureInterceptServer();
100
+ const { msw } = await loadMsw();
101
+ const queue = new InterceptQueue(entries);
102
+ let violation = null;
103
+ const recordViolation = (method, url) => {
104
+ violation ??= queue.unmatchedError(method, url);
105
+ return msw.HttpResponse.json({ error: `@jterrazz/test strict intercepts: unmatched request ${method} ${url}` }, { status: 501 });
106
+ };
107
+ const handlers = [];
108
+ for (const url of queue.urls) for (const method of queue.methodsFor(url)) {
109
+ const handlerFn = method === "*" ? msw.http.all : msw.http[method.toLowerCase()];
110
+ if (!handlerFn) continue;
111
+ const handler = handlerFn(url, async ({ request }) => {
112
+ let body = null;
113
+ const rawText = await request.clone().text();
114
+ if (rawText) try {
115
+ body = JSON.parse(rawText);
116
+ } catch {
117
+ body = rawText;
251
118
  }
252
- }
119
+ const headers = {};
120
+ request.headers.forEach((value, key) => {
121
+ headers[key.toLowerCase()] = value;
122
+ });
123
+ const observed = {
124
+ body,
125
+ headers,
126
+ url: request.url
127
+ };
128
+ const entry = queue.take(url, request.method, observed);
129
+ if (!entry) return recordViolation(request.method, request.url);
130
+ const response = typeof entry.response === "function" ? entry.response(observed) : entry.response;
131
+ if (response.delay) await new Promise((r) => setTimeout(r, response.delay));
132
+ return msw.HttpResponse.json(response.body, {
133
+ status: response.status ?? 200,
134
+ headers: response.headers
135
+ });
136
+ });
137
+ handlers.push(handler);
138
+ }
139
+ handlers.push(msw.http.all("*", ({ request }) => recordViolation(request.method, request.url)));
140
+ serverInstance.use(...handlers);
141
+ return {
142
+ cleanup: () => {
143
+ serverInstance.resetHandlers();
144
+ },
145
+ violation: () => violation
253
146
  };
254
147
  }
255
- /**
256
- * OpenAI API intercept helpers.
257
- */
258
- const openai = {
259
- request(filter) {
260
- return {
261
- adapter: "openai",
262
- method: "POST",
263
- url: OPENAI_CHAT_URL,
264
- match: filter ? (body) => matchesChatFilter(body, filter) : void 0,
265
- wrap: buildChatReply
266
- };
267
- },
268
- agent(filter, url) {
269
- return {
270
- adapter: "openai",
271
- method: "POST",
272
- url: url ?? OPENAI_RESPONSES_URL,
273
- match: filter ? (body) => matchesResponsesFilter(body, filter) : void 0,
274
- wrap: buildResponsesReply
275
- };
276
- },
277
- reply: buildChatReply,
278
- error(status, message) {
279
- return {
280
- status,
281
- body: { error: {
282
- message: message ?? `OpenAI error (${status})`,
283
- type: status === 429 ? "rate_limit_error" : "api_error",
284
- code: status === 429 ? "rate_limit_exceeded" : null
285
- } }
286
- };
287
- },
288
- malformed(content) {
289
- return buildChatReply(content);
290
- },
291
- timeout() {
292
- return {
293
- status: 200,
294
- body: {},
295
- delay: 3e4
296
- };
297
- },
298
- chat(filter) {
299
- return openai.request(filter);
300
- },
301
- response: buildChatReply,
302
- responses(filter, url) {
303
- return openai.agent(filter, url);
304
- },
305
- responsesResponse: buildResponsesReply
306
- };
307
148
  //#endregion
308
- export { anthropic, http, openai };
309
-
310
- //# sourceMappingURL=intercept.js.map
149
+ export { registerIntercepts };
package/dist/match.js ADDED
@@ -0,0 +1,153 @@
1
+ //#region src/core/matching/match.ts
2
+ /** Every kind usable as a `{{token}}` in fixture files (all but ref/regex). */
3
+ const TOKEN_KINDS = [
4
+ "any",
5
+ "base64",
6
+ "date",
7
+ "duration",
8
+ "email",
9
+ "float",
10
+ "hex",
11
+ "int",
12
+ "ip",
13
+ "iso8601",
14
+ "number",
15
+ "path",
16
+ "port",
17
+ "semver",
18
+ "sha",
19
+ "string",
20
+ "time",
21
+ "ulid",
22
+ "url",
23
+ "uuid",
24
+ "workdir"
25
+ ];
26
+ /**
27
+ * A dynamic-value matcher. Created via the {@link match} factories — never
28
+ * constructed directly by user code.
29
+ */
30
+ var Matcher = class {
31
+ kind;
32
+ /** For kind 'ref': the capture to assert inequality against. */
33
+ notRef;
34
+ /** For kind 'ref': the capture name. */
35
+ refName;
36
+ /** For kind 'regex': the pattern the actual value must match. */
37
+ regex;
38
+ constructor(kind, options = {}) {
39
+ this.kind = kind;
40
+ this.notRef = options.notRef;
41
+ this.refName = options.refName;
42
+ this.regex = options.regex;
43
+ }
44
+ /** Placeholder-style rendering used in failure diffs and serialization. */
45
+ toString() {
46
+ switch (this.kind) {
47
+ case "ref": return this.notRef ? `{{ref#${this.refName}!${this.notRef}}}` : `{{ref#${this.refName}}}`;
48
+ case "regex": return `{{regex:${this.regex?.source}}}`;
49
+ default: return this.refName ? `{{${this.kind}#${this.refName}}}` : `{{${this.kind}}}`;
50
+ }
51
+ }
52
+ toJSON() {
53
+ return this.toString();
54
+ }
55
+ };
56
+ const typed = (kind) => () => new Matcher(kind);
57
+ /**
58
+ * Dynamic-value matchers for structural comparisons — the code-side mirror of
59
+ * the `{{token}}` fixture grammar (CONVENTIONS D4).
60
+ *
61
+ * @example
62
+ * await expect(result.table('users', { database: 'db' })).toMatchRows({
63
+ * columns: ['id', 'name'],
64
+ * rows: [[match.uuid(), 'Alice']],
65
+ * });
66
+ */
67
+ const match = {
68
+ /** Matches anything. */
69
+ any: typed("any"),
70
+ /** Matches a base64 string. */
71
+ base64: typed("base64"),
72
+ /** Matches a calendar date (`YYYY-MM-DD`). */
73
+ date: typed("date"),
74
+ /** Matches a human duration (`12ms`, `1.5s`, `2m`, `3h`). */
75
+ duration: typed("duration"),
76
+ /** Matches an email address. */
77
+ email: typed("email"),
78
+ /**
79
+ * Matches a float. In JSON contexts any finite number passes (JSON does
80
+ * not distinguish `42` from `42.0`); in text contexts the decimal part
81
+ * is required (`4.2`, never `42`).
82
+ */
83
+ float: typed("float"),
84
+ /** Matches a hexadecimal string. */
85
+ hex: typed("hex"),
86
+ /** Matches an integer (or an integer string in text contexts). */
87
+ int: typed("int"),
88
+ /** Matches an IPv4 address. */
89
+ ip: typed("ip"),
90
+ /** Matches an ISO-8601 timestamp string. */
91
+ iso8601: typed("iso8601"),
92
+ /** Matches a number (or a numeric string in text contexts). */
93
+ number: typed("number"),
94
+ /** Matches a filesystem path (`/...` or `./...`). */
95
+ path: typed("path"),
96
+ /** Matches a TCP/UDP port number (0-65535). */
97
+ port: typed("port"),
98
+ /**
99
+ * Capture-and-compare. The first occurrence of `ref(name)` captures the
100
+ * actual value; every later occurrence must strictly equal the capture.
101
+ * `{ not: other }` additionally asserts inequality with the capture named
102
+ * `other`. Scope: the current spec execution (reset per chain).
103
+ */
104
+ ref: (name, options) => new Matcher("ref", {
105
+ notRef: options?.not,
106
+ refName: name
107
+ }),
108
+ /** Matches a string against the given regular expression. */
109
+ regex: (regex) => new Matcher("regex", { regex }),
110
+ /** Matches a semantic version (`1.2.3`, `2.0.0-rc.1`). */
111
+ semver: typed("semver"),
112
+ /** Matches a git SHA (7-64 hex chars). */
113
+ sha: typed("sha"),
114
+ /** Matches any string. */
115
+ string: typed("string"),
116
+ /** Matches a wall-clock time (`HH:MM` or `HH:MM:SS`). */
117
+ time: typed("time"),
118
+ /** Matches a ULID. */
119
+ ulid: typed("ulid"),
120
+ /** Matches an http(s) URL. */
121
+ url: typed("url"),
122
+ /** Matches a UUID string. */
123
+ uuid: typed("uuid"),
124
+ /** Matches the exact working directory of the current spec. */
125
+ workdir: typed("workdir")
126
+ };
127
+ /**
128
+ * Named captures recorded by `match.ref()` / `{{type#ref}}` placeholders.
129
+ * One scope lives on each spec result — every assertion chained off the same
130
+ * result shares it, and a new chain starts fresh.
131
+ */
132
+ var CaptureScope = class {
133
+ values = /* @__PURE__ */ new Map();
134
+ /**
135
+ * The exact working directory of the current spec, when the framework
136
+ * knows it (cli mode). Drives the `{{workdir}}` token / `match.workdir()`.
137
+ */
138
+ workdir;
139
+ constructor(workdir) {
140
+ this.workdir = workdir;
141
+ }
142
+ get(name) {
143
+ return this.values.get(name);
144
+ }
145
+ has(name) {
146
+ return this.values.has(name);
147
+ }
148
+ set(name, value) {
149
+ this.values.set(name, value);
150
+ }
151
+ };
152
+ //#endregion
153
+ export { match as i, Matcher as n, TOKEN_KINDS as r, CaptureScope as t };