@oh-my-pi/omptype 17.2.6 → 17.2.7

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.
@@ -0,0 +1,273 @@
1
+ /** Emit the requested JSON Schema dialect represented by an IR tree. */
2
+ export function irToJsonSchema(ir, options) {
3
+ let schema = emit(ir, options);
4
+ if (options?.target === "draft-07")
5
+ schema = toDraft7(schema);
6
+ const dialect = options?.dialect ?? dialectFor(options?.target);
7
+ if (dialect !== undefined)
8
+ schema.$schema = dialect;
9
+ if (options?.description !== undefined)
10
+ schema.description = options.description;
11
+ return schema;
12
+ }
13
+ function dialectFor(target) {
14
+ if (target === "draft-2020-12")
15
+ return "https://json-schema.org/draft/2020-12/schema";
16
+ if (target === "draft-07")
17
+ return "http://json-schema.org/draft-07/schema#";
18
+ return target?.startsWith("http://") || target?.startsWith("https://") ? target : undefined;
19
+ }
20
+ function fallback(schema, options) {
21
+ const replacement = options?.fallback?.({ base: schema });
22
+ if (replacement === true)
23
+ return {};
24
+ if (replacement === false)
25
+ return { not: {} };
26
+ return typeof replacement === "object" && replacement !== null ? replacement : schema;
27
+ }
28
+ function toDraft7(schema) {
29
+ const converted = {};
30
+ for (const key in schema) {
31
+ const value = schema[key];
32
+ if (key === "prefixItems" && Array.isArray(value)) {
33
+ converted.items = value.map(item => typeof item === "object" && item !== null ? toDraft7(item) : item);
34
+ }
35
+ else if (key === "items" && "prefixItems" in schema) {
36
+ converted.additionalItems =
37
+ typeof value === "object" && value !== null ? toDraft7(value) : value;
38
+ }
39
+ else if (Array.isArray(value)) {
40
+ converted[key] = value.map(item => typeof item === "object" && item !== null ? toDraft7(item) : item);
41
+ }
42
+ else if (typeof value === "object" && value !== null) {
43
+ converted[key] = toDraft7(value);
44
+ }
45
+ else {
46
+ converted[key] = value;
47
+ }
48
+ }
49
+ return converted;
50
+ }
51
+ function emit(ir, options) {
52
+ let schema;
53
+ switch (ir.k) {
54
+ case "unknown":
55
+ schema = {};
56
+ break;
57
+ case "undefined":
58
+ schema = fallback({}, options);
59
+ break;
60
+ case "null":
61
+ schema = { type: "null" };
62
+ break;
63
+ case "boolean":
64
+ schema = { type: "boolean" };
65
+ break;
66
+ case "bigint":
67
+ schema = { type: "integer" };
68
+ break;
69
+ case "symbol":
70
+ schema = fallback({}, options);
71
+ break;
72
+ case "never":
73
+ schema = { not: {} };
74
+ break;
75
+ case "anyobject":
76
+ schema = { type: "object" };
77
+ break;
78
+ case "string":
79
+ schema = emitString(ir);
80
+ break;
81
+ case "number":
82
+ schema = emitNumber(ir);
83
+ break;
84
+ case "lit":
85
+ schema = emitLiteral(ir.v);
86
+ break;
87
+ case "union":
88
+ schema = emitUnion(ir.members, options);
89
+ break;
90
+ case "intersection":
91
+ schema = { allOf: ir.members.map(member => emit(member, options)) };
92
+ break;
93
+ case "array":
94
+ schema = { type: "array", items: emit(ir.el, options) };
95
+ if (ir.min !== undefined)
96
+ schema.minItems = ir.min;
97
+ if (ir.max !== undefined)
98
+ schema.maxItems = ir.max;
99
+ break;
100
+ case "tuple": {
101
+ const prefixItems = ir.prefix.map(item => {
102
+ const itemSchema = emit(item.val, options);
103
+ if (item.hasDefault) {
104
+ itemSchema.default = item.defFactory && typeof item.def === "function" ? item.def() : item.def;
105
+ }
106
+ return itemSchema;
107
+ });
108
+ const required = ir.prefix.reduce((count, item) => count + (item.opt || item.hasDefault ? 0 : 1), ir.postfix.length);
109
+ schema = { type: "array", prefixItems, minItems: required };
110
+ if (ir.variadic === undefined) {
111
+ schema.maxItems = ir.prefix.length + ir.postfix.length;
112
+ schema.items = false;
113
+ }
114
+ else {
115
+ schema.items = emit(ir.variadic, options);
116
+ }
117
+ break;
118
+ }
119
+ case "object":
120
+ schema = emitObject(ir.props, ir.index, ir.extras, options);
121
+ break;
122
+ case "instance":
123
+ schema = ir.ctor === Date ? { type: "string", format: "date-time" } : fallback({ type: "object" }, options);
124
+ break;
125
+ case "refine":
126
+ schema = emit(ir.base, options);
127
+ if (ir.json !== undefined)
128
+ Object.assign(schema, ir.json);
129
+ break;
130
+ case "morph":
131
+ schema = fallback(emit(ir.out ?? ir.input, options), options);
132
+ break;
133
+ case "alias":
134
+ schema = emit(ir.resolve(), options);
135
+ break;
136
+ case "sub":
137
+ schema = ir.schema.hasSteps ? fallback(emit(ir.schema.ir, options), options) : emit(ir.schema.ir, options);
138
+ if (ir.schema.description !== undefined)
139
+ schema.description = ir.schema.description;
140
+ break;
141
+ }
142
+ if (ir.desc !== undefined)
143
+ schema.description = ir.desc;
144
+ return schema;
145
+ }
146
+ function emitString(ir) {
147
+ const schema = { type: "string" };
148
+ if (ir.min !== undefined)
149
+ schema.minLength = ir.min;
150
+ if (ir.max !== undefined)
151
+ schema.maxLength = ir.max;
152
+ if (ir.url)
153
+ schema.format = "uri";
154
+ return schema;
155
+ }
156
+ function emitNumber(ir) {
157
+ const schema = { type: ir.int ? "integer" : "number" };
158
+ if (ir.min !== undefined)
159
+ schema[ir.xmin ? "exclusiveMinimum" : "minimum"] = ir.min;
160
+ if (ir.max !== undefined)
161
+ schema[ir.xmax ? "exclusiveMaximum" : "maximum"] = ir.max;
162
+ if (ir.divisor !== undefined)
163
+ schema.multipleOf = ir.divisor;
164
+ return schema;
165
+ }
166
+ function emitLiteral(value) {
167
+ if (value instanceof Date)
168
+ return { type: "string", format: "date-time", const: value.toISOString() };
169
+ if (isJsonValue(value))
170
+ return { const: value };
171
+ switch (typeof value) {
172
+ case "string":
173
+ return { type: "string" };
174
+ case "number":
175
+ return { type: "number" };
176
+ case "boolean":
177
+ return { type: "boolean" };
178
+ case "bigint":
179
+ return { type: "integer" };
180
+ case "object":
181
+ return { type: "object" };
182
+ default:
183
+ return {};
184
+ }
185
+ }
186
+ function emitUnion(members, options) {
187
+ const defined = members.filter(member => member.k !== "undefined");
188
+ if (defined.length === 0)
189
+ return {};
190
+ if (defined.length === 1)
191
+ return emit(defined[0], options);
192
+ if (defined.every(member => member.k === "lit" && isJsonValue(member.v))) {
193
+ const values = defined.map(member => member.v);
194
+ const schema = { enum: values };
195
+ const scalarType = homogeneousScalarType(values);
196
+ if (scalarType !== undefined)
197
+ schema.type = scalarType;
198
+ return schema;
199
+ }
200
+ return { anyOf: defined.map(member => emit(member, options)) };
201
+ }
202
+ function homogeneousScalarType(values) {
203
+ const first = jsonScalarType(values[0]);
204
+ if (first === undefined)
205
+ return undefined;
206
+ for (let i = 1; i < values.length; i++) {
207
+ if (jsonScalarType(values[i]) !== first)
208
+ return undefined;
209
+ }
210
+ return first;
211
+ }
212
+ function jsonScalarType(value) {
213
+ if (value === null)
214
+ return "null";
215
+ switch (typeof value) {
216
+ case "string":
217
+ case "number":
218
+ case "boolean":
219
+ return typeof value;
220
+ default:
221
+ return undefined;
222
+ }
223
+ }
224
+ function emitObject(props, index, extras, options) {
225
+ const properties = {};
226
+ const required = [];
227
+ // ArkType emits required properties first (each group in declaration
228
+ // order); downstream wire consumers rely on that stable ordering.
229
+ const ordered = [...props.filter(p => !p.opt && !p.hasDefault), ...props.filter(p => p.opt || p.hasDefault)];
230
+ for (const prop of ordered) {
231
+ const propertySchema = emit(prop.val, options);
232
+ if (prop.hasDefault) {
233
+ propertySchema.default = prop.defFactory ? prop.def() : prop.def;
234
+ }
235
+ properties[prop.key] = propertySchema;
236
+ if (!prop.opt && !prop.hasDefault)
237
+ required.push(prop.key);
238
+ }
239
+ const schema = { type: "object", properties };
240
+ if (required.length > 0)
241
+ schema.required = required;
242
+ if (index !== undefined)
243
+ schema.additionalProperties = emit(index, options);
244
+ else if (extras === "reject")
245
+ schema.additionalProperties = false;
246
+ return schema;
247
+ }
248
+ function isJsonValue(value, seen = new Set()) {
249
+ if (value === null || typeof value === "string" || typeof value === "boolean")
250
+ return true;
251
+ if (typeof value === "number")
252
+ return Number.isFinite(value);
253
+ if (typeof value !== "object" || seen.has(value))
254
+ return false;
255
+ seen.add(value);
256
+ if (Array.isArray(value)) {
257
+ for (const item of value) {
258
+ if (!isJsonValue(item, seen))
259
+ return false;
260
+ }
261
+ seen.delete(value);
262
+ return true;
263
+ }
264
+ const prototype = Object.getPrototypeOf(value);
265
+ if (prototype !== Object.prototype && prototype !== null)
266
+ return false;
267
+ for (const key in value) {
268
+ if (Object.hasOwn(value, key) && !isJsonValue(value[key], seen))
269
+ return false;
270
+ }
271
+ seen.delete(value);
272
+ return true;
273
+ }
@@ -0,0 +1,233 @@
1
+ const NUMERIC = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
2
+ const INTEGER = /^[+-]?(?:0|[1-9]\d*)$/;
3
+ const EMAIL = /^[\w%+.-]+@[\d.A-Za-z-]+\.[A-Za-z]{2,}$/;
4
+ const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][\dA-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][\dA-Za-z-]*))*))?(?:\+([\dA-Za-z-]+(?:\.[\dA-Za-z-]+)*))?$/;
5
+ const UUID = /^[\da-f]{8}-[\da-f]{4}-[1-8][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i;
6
+ const IPV4_SEGMENT = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
7
+ const IPV4_ADDRESS = `(?:${IPV4_SEGMENT}[.]){3}${IPV4_SEGMENT}`;
8
+ const IPV4 = new RegExp(`^${IPV4_ADDRESS}$`);
9
+ const IPV6_SEGMENT = "(?:[0-9a-fA-F]{1,4})";
10
+ const IPV6 = new RegExp("^(" +
11
+ `(?:${IPV6_SEGMENT}:){7}(?:${IPV6_SEGMENT}|:)|` +
12
+ `(?:${IPV6_SEGMENT}:){6}(?:${IPV4_ADDRESS}|:${IPV6_SEGMENT}|:)|` +
13
+ `(?:${IPV6_SEGMENT}:){5}(?::${IPV4_ADDRESS}|(:${IPV6_SEGMENT}){1,2}|:)|` +
14
+ `(?:${IPV6_SEGMENT}:){4}(?:(:${IPV6_SEGMENT}){0,1}:${IPV4_ADDRESS}|(:${IPV6_SEGMENT}){1,3}|:)|` +
15
+ `(?:${IPV6_SEGMENT}:){3}(?:(:${IPV6_SEGMENT}){0,2}:${IPV4_ADDRESS}|(:${IPV6_SEGMENT}){1,4}|:)|` +
16
+ `(?:${IPV6_SEGMENT}:){2}(?:(:${IPV6_SEGMENT}){0,3}:${IPV4_ADDRESS}|(:${IPV6_SEGMENT}){1,5}|:)|` +
17
+ `(?:${IPV6_SEGMENT}:){1}(?:(:${IPV6_SEGMENT}){0,4}:${IPV4_ADDRESS}|(:${IPV6_SEGMENT}){1,6}|:)|` +
18
+ `(?::((?::${IPV6_SEGMENT}){0,5}:${IPV4_ADDRESS}|(?::${IPV6_SEGMENT}){1,7}|:))` +
19
+ ")(?:%[\\d.A-Za-z]{1,})?$");
20
+ const ISO_DATE = /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))(T((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([,.]\d+(?!:))?)?(\17[0-5]\d([,.]\d+)?)?([Zz]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
21
+ function pattern(regex, expected, format) {
22
+ return {
23
+ k: "refine",
24
+ base: { k: "string" },
25
+ pred: value => {
26
+ regex.lastIndex = 0;
27
+ return regex.test(value);
28
+ },
29
+ expected,
30
+ json: format === undefined ? { pattern: regex.source } : { pattern: regex.source, format },
31
+ };
32
+ }
33
+ function morph(input, fn, out) {
34
+ return { k: "morph", input, fn: (value, context) => fn(value, context), out };
35
+ }
36
+ function parsableJson() {
37
+ return {
38
+ k: "refine",
39
+ base: { k: "string" },
40
+ pred: value => {
41
+ try {
42
+ JSON.parse(value);
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ },
49
+ expected: "a JSON string",
50
+ json: { contentMediaType: "application/json" },
51
+ };
52
+ }
53
+ function parsableDate(expected = "a parsable date") {
54
+ return {
55
+ k: "refine",
56
+ base: { k: "string" },
57
+ pred: value => !Number.isNaN(new Date(value).valueOf()),
58
+ expected,
59
+ json: { format: "date-time" },
60
+ };
61
+ }
62
+ function normalize(form) {
63
+ return morph({ k: "string" }, value => value.normalize(form), patternForNormalized(form));
64
+ }
65
+ function patternForNormalized(form) {
66
+ return {
67
+ k: "refine",
68
+ base: { k: "string" },
69
+ pred: value => value.normalize(form) === value,
70
+ expected: `${form}-normalized unicode`,
71
+ };
72
+ }
73
+ function uuidVersion(version) {
74
+ return pattern(new RegExp(`^[\\da-f]{8}-[\\da-f]{4}-${version}[\\da-f]{3}-[89ab][\\da-f]{3}-[\\da-f]{12}$`, "i"), `a UUIDv${version}`, "uuid");
75
+ }
76
+ function isLuhnValid(input) {
77
+ const value = input.replace(/[ -]+/g, "");
78
+ let sum = 0;
79
+ let double = false;
80
+ for (let index = value.length - 1; index >= 0; index--) {
81
+ let digit = Number(value[index]);
82
+ if (double) {
83
+ digit *= 2;
84
+ if (digit > 9)
85
+ digit -= 9;
86
+ }
87
+ sum += digit;
88
+ double = !double;
89
+ }
90
+ return value.length > 0 && sum % 10 === 0;
91
+ }
92
+ const keywordFactories = {
93
+ string: () => ({ k: "string" }),
94
+ "string.alpha": () => pattern(/^[A-Za-z]*$/, "only letters"),
95
+ "string.alphanumeric": () => pattern(/^[\dA-Za-z]*$/, "only letters and digits 0-9"),
96
+ "string.hex": () => pattern(/^[\dA-Fa-f]+$/, "hex characters only"),
97
+ "string.base64": () => pattern(/^(?:[\d+/A-Za-z]{4})*(?:[\d+/A-Za-z]{2}==|[\d+/A-Za-z]{3}=)?$/, "base64-encoded"),
98
+ "string.base64.url": () => pattern(/^(?:[\w-]{4})*(?:[\w-]{2}(?:==|%3D%3D)?|[\w-]{3}(?:=|%3D)?)?$/, "base64url-encoded"),
99
+ "string.capitalize": () => morph({ k: "string" }, value => value.charAt(0).toUpperCase() + value.slice(1)),
100
+ "string.capitalize.preformatted": () => pattern(/^[A-Z].*$/, "capitalized"),
101
+ "string.creditCard": () => ({
102
+ k: "refine",
103
+ base: pattern(/^(?:4\d{12}(?:\d{3,6})?|5[1-5]\d{14}|3[47]\d{13}|6(?:011|5\d\d)\d{12,15})$/, "a credit card number"),
104
+ pred: value => isLuhnValid(value),
105
+ expected: "a credit card number",
106
+ }),
107
+ "string.date": () => parsableDate(),
108
+ "string.date.parse": () => morph(parsableDate(), value => new Date(value), { k: "instance", ctor: Date, expected: "a Date" }),
109
+ "string.date.iso": () => pattern(ISO_DATE, "an ISO 8601 date", "date-time"),
110
+ "string.date.iso.parse": () => morph(pattern(ISO_DATE, "an ISO 8601 date", "date-time"), value => new Date(value), {
111
+ k: "instance",
112
+ ctor: Date,
113
+ expected: "a Date",
114
+ }),
115
+ "string.date.epoch": () => pattern(INTEGER, "an integer string representing a Unix timestamp"),
116
+ "string.date.epoch.parse": () => morph(pattern(INTEGER, "an integer string representing a Unix timestamp"), value => new Date(Number(value)), {
117
+ k: "instance",
118
+ ctor: Date,
119
+ expected: "a Date",
120
+ }),
121
+ "string.digits": () => pattern(/^\d*$/, "only digits 0-9"),
122
+ "string.email": () => pattern(EMAIL, "an email address", "email"),
123
+ "string.integer": () => pattern(INTEGER, "a well-formed integer string"),
124
+ "string.integer.parse": () => morph(pattern(INTEGER, "a well-formed integer string"), (value, context) => {
125
+ const parsed = Number.parseInt(value, 10);
126
+ return Number.isSafeInteger(parsed) ? parsed : context.error("a safe integer string");
127
+ }, { k: "number", int: true }),
128
+ "string.ip": () => ({
129
+ k: "union",
130
+ members: [pattern(IPV4, "an IPv4 address", "ipv4"), pattern(IPV6, "an IPv6 address", "ipv6")],
131
+ }),
132
+ "string.ip.v4": () => pattern(IPV4, "an IPv4 address", "ipv4"),
133
+ "string.ip.v6": () => pattern(IPV6, "an IPv6 address", "ipv6"),
134
+ "string.json": () => parsableJson(),
135
+ "string.json.parse": () => morph(parsableJson(), (value, context) => {
136
+ try {
137
+ return JSON.parse(value);
138
+ }
139
+ catch {
140
+ return context.error("a JSON string");
141
+ }
142
+ }),
143
+ "string.lower": () => morph({ k: "string" }, value => value.toLowerCase()),
144
+ "string.lower.preformatted": () => pattern(/^[a-z]*$/, "only lowercase letters"),
145
+ "string.normalize": () => normalize("NFC"),
146
+ "string.normalize.NFC": () => normalize("NFC"),
147
+ "string.normalize.NFC.preformatted": () => patternForNormalized("NFC"),
148
+ "string.normalize.NFD": () => normalize("NFD"),
149
+ "string.normalize.NFD.preformatted": () => patternForNormalized("NFD"),
150
+ "string.normalize.NFKC": () => normalize("NFKC"),
151
+ "string.normalize.NFKC.preformatted": () => patternForNormalized("NFKC"),
152
+ "string.normalize.NFKD": () => normalize("NFKD"),
153
+ "string.normalize.NFKD.preformatted": () => patternForNormalized("NFKD"),
154
+ "string.numeric": () => pattern(NUMERIC, "a well-formed numeric string"),
155
+ "string.numeric.parse": () => morph(pattern(NUMERIC, "a well-formed numeric string"), value => Number.parseFloat(value), { k: "number" }),
156
+ "string.regex": () => ({
157
+ k: "refine",
158
+ base: { k: "string" },
159
+ pred: value => {
160
+ try {
161
+ new RegExp(value);
162
+ return true;
163
+ }
164
+ catch {
165
+ return false;
166
+ }
167
+ },
168
+ expected: "a regex pattern",
169
+ json: { format: "regex" },
170
+ }),
171
+ "string.semver": () => pattern(SEMVER, "a semantic version"),
172
+ "string.trim": () => morph({ k: "string" }, value => value.trim()),
173
+ "string.trim.preformatted": () => pattern(/^\S.*\S$|^\S?$/, "trimmed"),
174
+ "string.upper": () => morph({ k: "string" }, value => value.toUpperCase()),
175
+ "string.upper.preformatted": () => pattern(/^[A-Z]*$/, "only uppercase letters"),
176
+ "string.url": () => ({ k: "string", url: true }),
177
+ "string.url.parse": () => morph({ k: "string", url: true }, value => new URL(value), { k: "instance", ctor: URL, expected: "a URL" }),
178
+ "string.uuid": () => pattern(UUID, "a UUID", "uuid"),
179
+ "string.uuid.v1": () => uuidVersion("1"),
180
+ "string.uuid.v2": () => uuidVersion("2"),
181
+ "string.uuid.v3": () => uuidVersion("3"),
182
+ "string.uuid.v4": () => uuidVersion("4"),
183
+ "string.uuid.v5": () => uuidVersion("5"),
184
+ "string.uuid.v6": () => uuidVersion("6"),
185
+ "string.uuid.v7": () => uuidVersion("7"),
186
+ "string.uuid.v8": () => uuidVersion("8"),
187
+ "parse.number": () => morph(pattern(NUMERIC, "a well-formed numeric string"), value => Number.parseFloat(value), { k: "number" }),
188
+ "parse.integer": () => morph(pattern(INTEGER, "a well-formed integer string"), value => Number.parseInt(value, 10), {
189
+ k: "number",
190
+ int: true,
191
+ }),
192
+ "parse.json": () => keywordFactories["string.json.parse"](),
193
+ "parse.date": () => keywordFactories["string.date.parse"](),
194
+ "parse.url": () => keywordFactories["string.url.parse"](),
195
+ "parse.boolean": () => morph(pattern(/^(?:true|false)$/, "a boolean string"), value => value === "true", { k: "boolean" }),
196
+ "parse.bigint": () => morph(pattern(INTEGER, "an integer string"), value => BigInt(value), { k: "bigint" }),
197
+ };
198
+ /** Lower a built-in keyword into fresh validation IR. */
199
+ export function keywordIR(name) {
200
+ return keywordFactories[name]?.();
201
+ }
202
+ /** Lower a regular expression into a string refinement. */
203
+ export function patternIR(regex) {
204
+ return pattern(regex, `a string matching ${regex}`);
205
+ }
206
+ /** Lower an ArkType-style template literal into a string pattern. */
207
+ export function templateIR(source) {
208
+ let patternSource = "^";
209
+ let index = 0;
210
+ const placeholder = /\$\{(string|number|bigint|boolean)\}/g;
211
+ for (let match = placeholder.exec(source); match; match = placeholder.exec(source)) {
212
+ patternSource += escapeRegex(source.slice(index, match.index));
213
+ switch (match[1]) {
214
+ case "number":
215
+ patternSource += "[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)";
216
+ break;
217
+ case "bigint":
218
+ patternSource += "[+-]?\\d+";
219
+ break;
220
+ case "boolean":
221
+ patternSource += "(?:true|false)";
222
+ break;
223
+ default:
224
+ patternSource += ".*";
225
+ }
226
+ index = match.index + match[0].length;
227
+ }
228
+ patternSource += `${escapeRegex(source.slice(index))}$`;
229
+ return pattern(new RegExp(patternSource), `a string matching \`${source}\``);
230
+ }
231
+ function escapeRegex(value) {
232
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
233
+ }