celery-env 0.1.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.
@@ -0,0 +1,811 @@
1
+ const STR = 0, INT = 1, NUM = 2, BOOL = 3, ENUM = 4, URL_T = 5, JSON_T = 6, LIST = 7;
2
+ const W = "(c > 8 && c < 14 || c === 32 || c === 160 || c === 5760 || c > 8191 && c < 8203 || c === 8232 || c === 8233 || c === 8239 || c === 8287 || c === 12288 || c === 65279)";
3
+ const H = Object.hasOwn;
4
+
5
+ export function generateValidator(schema, options = {}) {
6
+ const ctx = emitterContext(options);
7
+ const entries = assertSchema(schema);
8
+ const fn = functionName(options);
9
+ const param = options.processDefault === false ? "env" : "env = process.env";
10
+ const hasProto = hasProtoKey(entries);
11
+
12
+ if (!hasProto && ctx.t === 512 && entries.length >= 128 && entries.length <= 512) return generateObjectValidator(entries, fn, param, ctx, options);
13
+ if (entries.length >= ctx.t) return generateSplitValidator(entries, fn, param, ctx, options);
14
+ if (!hasProto && entries.length >= 128 && entries.length <= 384) return generateObjectValidator(entries, fn, param, ctx, options);
15
+
16
+ const body = [];
17
+ let needsListTemp = false;
18
+ for (let i = 0; i < entries.length; i++) {
19
+ const [key, rule] = entries[i];
20
+ if (rule.t === LIST && needsListTempVar(rule)) needsListTemp = true;
21
+ body.push(...emitRule(key, rule, `_${i}`, ctx));
22
+ }
23
+ const lines = ctx.g;
24
+ lines.push(`export function ${fn}(${param}) {`);
25
+ if (!ctx.f) lines.push(" let r;");
26
+ lines.push(" let v;");
27
+ if (entries.length) lines.push(` let ${entries.map((_, i) => `_${i}`).join(", ")};`);
28
+ if (needsListTemp) lines.push(" let x;");
29
+ lines.push(...body);
30
+
31
+ if (!ctx.f) lines.push(" if (r) throw Error(\"Invalid environment:\\n- \" + r.join(\"\\n- \"));");
32
+ lines.push(
33
+ ` return ${returnObject(entries)};`,
34
+ "}",
35
+ `export default ${fn};`,
36
+ ""
37
+ );
38
+
39
+ if (options.minify) return lines.map((line) => line.trim()).join("");
40
+ return lines.join("\n");
41
+ }
42
+
43
+ function generateObjectValidator(entries, fn, param, ctx, options) {
44
+ const body = [];
45
+ let needsListTemp = false;
46
+ for (let i = 0; i < entries.length; i++) {
47
+ const [key, rule] = entries[i];
48
+ if (rule.t === LIST && needsListTempVar(rule)) needsListTemp = true;
49
+ body.push(...emitRule(key, rule, `o${prop(key)}`, ctx));
50
+ }
51
+ const lines = ctx.g;
52
+ lines.push(`export function ${fn}(${param}) {`);
53
+ if (!ctx.f) lines.push(" let r;");
54
+ lines.push(" let v;", " const o = {};");
55
+ if (needsListTemp) lines.push(" let x;");
56
+ lines.push(...body);
57
+ if (!ctx.f) lines.push(" if (r) throw Error(\"Invalid environment:\\n- \" + r.join(\"\\n- \"));");
58
+ lines.push(" return o;", "}", `export default ${fn};`, "");
59
+ if (options.minify) return lines.map((line) => line.trim()).join("");
60
+ return lines.join("\n");
61
+ }
62
+
63
+ function generateSplitValidator(entries, fn, param, ctx, options) {
64
+ const lines = ctx.g;
65
+ let n = 0;
66
+ for (let start = 0; start < entries.length; start += 32) {
67
+ lines.push(...emitSplitChunk(entries, start, Math.min(start + 32, entries.length), n++, ctx));
68
+ }
69
+
70
+ lines.push(`export function ${fn}(${param}) {`);
71
+ if (!ctx.f) lines.push(" let r;");
72
+ lines.push(` const a = new Array(${entries.length});`);
73
+ for (let i = 0; i < n; i++) {
74
+ lines.push(ctx.f ? ` _c${i}(env, a);` : ` r = _c${i}(env, a, r);`);
75
+ }
76
+ if (!ctx.f) lines.push(" if (r) throw Error(\"Invalid environment:\\n- \" + r.join(\"\\n- \"));");
77
+ lines.push(
78
+ ` return ${returnObject(entries, "a")};`,
79
+ "}",
80
+ `export default ${fn};`,
81
+ ""
82
+ );
83
+
84
+ if (options.minify) return lines.map((line) => line.trim()).join("");
85
+ return lines.join("\n");
86
+ }
87
+
88
+ function emitSplitChunk(entries, start, end, index, ctx) {
89
+ const lines = [ctx.f ? `function _c${index}(env, a) {` : `function _c${index}(env, a, r) {`, " let v;"];
90
+ for (let i = start; i < end; i++) {
91
+ const rule = entries[i][1];
92
+ if (rule.t === LIST && needsListTempVar(rule)) {
93
+ lines.push(" let x;");
94
+ break;
95
+ }
96
+ }
97
+ for (let i = start; i < end; i++) {
98
+ const [key, rule] = entries[i];
99
+ lines.push(...emitRule(key, rule, `a[${i}]`, ctx));
100
+ }
101
+ if (!ctx.f) lines.push(" return r;");
102
+ lines.push("}");
103
+ return lines;
104
+ }
105
+
106
+ export function generateTypes(schema, options = {}) {
107
+ const entries = assertSchema(schema);
108
+ const fn = functionName(options);
109
+ const envParam = options.processDefault === false ? "env: Record<string, string | undefined>" : "env?: Record<string, string | undefined>";
110
+ const lines = ["export type Env = {"];
111
+
112
+ for (const [key, rule] of entries) {
113
+ const type = innerTypeFor(rule);
114
+ lines.push(` readonly ${prop(key, 1)}: ${rule.optional && !hasDefault(rule) ? `${type} | undefined` : type};`);
115
+ }
116
+
117
+ lines.push("};", `export declare function ${fn}(${envParam}): Env;`, `export default ${fn};`, "");
118
+ return lines.join("\n");
119
+ }
120
+
121
+ export function generateExample(schema) {
122
+ const lines = [];
123
+ for (const [key, rule] of assertSchema(schema)) {
124
+ if (rule.desc) pushComment(lines, rule.desc);
125
+ if (rule.docs) pushComment(lines, `Docs: ${rule.docs}`);
126
+ if (rule.optional) lines.push("# Optional");
127
+ if (rule.requiredWhen) lines.push("# Conditionally required");
128
+ if (H(rule, "devDefault")) pushComment(lines, `Development default: ${envValue(rule.devDefault, rule)}`);
129
+ if (H(rule, "testDefault")) pushComment(lines, `Test default: ${envValue(rule.testDefault, rule)}`);
130
+ lines.push(`${envKey(key)}=${exampleValue(rule)}`);
131
+ lines.push("");
132
+ }
133
+ return lines.join("\n");
134
+ }
135
+
136
+ export function generateJsonSchema(schema, options = {}) {
137
+ const entries = assertSchema(schema);
138
+ const out = {
139
+ $schema: "https://json-schema.org/draft/2020-12/schema",
140
+ type: "object",
141
+ additionalProperties: options.additionalProperties ?? false,
142
+ properties: {}
143
+ };
144
+ if (options.title) out.title = options.title;
145
+
146
+ const required = [];
147
+ for (const [key, rule] of entries) {
148
+ out.properties[key] = jsonSchemaFor(rule);
149
+ if (!rule.optional && !hasDefault(rule)) required.push(key);
150
+ }
151
+ if (required.length) out.required = required;
152
+ return out;
153
+ }
154
+
155
+ function jsonSchemaFor(rule) {
156
+ const out = jsonShapeFor(rule);
157
+ annotateJsonSchema(out, rule);
158
+ return out;
159
+ }
160
+
161
+ function jsonShapeFor(rule) {
162
+ switch (rule.t) {
163
+ case STR:
164
+ return {
165
+ type: "string",
166
+ ...(rule.min != null ? { minLength: rule.min } : {}),
167
+ ...(rule.max != null ? { maxLength: rule.max } : {}),
168
+ ...(rule.startsWith != null ? { "x-celery-startsWith": rule.startsWith } : {}),
169
+ ...(rule.includes != null ? { "x-celery-includes": rule.includes } : {})
170
+ };
171
+ case INT:
172
+ return numberJsonSchema(rule, "integer");
173
+ case NUM:
174
+ return numberJsonSchema(rule, "number");
175
+ case BOOL:
176
+ return { type: "boolean" };
177
+ case ENUM:
178
+ return { enum: rule.values.slice() };
179
+ case URL_T:
180
+ return {
181
+ type: "string",
182
+ format: "uri",
183
+ ...(rule.protocols ? { "x-celery-protocols": rule.protocols.slice() } : {})
184
+ };
185
+ case JSON_T:
186
+ return {};
187
+ case LIST:
188
+ return {
189
+ type: "array",
190
+ items: jsonSchemaFor(rule.item),
191
+ ...(rule.separator !== "," ? { "x-celery-separator": rule.separator } : {}),
192
+ ...(rule.trim === false ? { "x-celery-trim": false } : {})
193
+ };
194
+ default:
195
+ throw new Error(`unknown validator kind ${rule.t}`);
196
+ }
197
+ }
198
+
199
+ function numberJsonSchema(rule, type) {
200
+ return {
201
+ type,
202
+ ...(rule.min != null ? { minimum: rule.min } : {}),
203
+ ...(rule.max != null ? { maximum: rule.max } : {}),
204
+ ...(rule.strict ? { "x-celery-strict": true } : {})
205
+ };
206
+ }
207
+
208
+ function annotateJsonSchema(out, rule) {
209
+ if (rule.desc) out.description = rule.desc;
210
+ if (rule.docs) out["x-celery-docs"] = rule.docs;
211
+ if (H(rule, "example")) out.examples = [rule.example];
212
+ if (H(rule, "default")) out.default = rule.default;
213
+ if (H(rule, "devDefault")) out["x-celery-devDefault"] = rule.devDefault;
214
+ if (H(rule, "testDefault")) out["x-celery-testDefault"] = rule.testDefault;
215
+ if (rule.optional) out["x-celery-optional"] = true;
216
+ if (rule.requiredWhen) out["x-celery-requiredWhen"] = true;
217
+ }
218
+
219
+ function assertSchema(schema) {
220
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) throw new TypeError("Expected schema object");
221
+ const entries = Object.entries(schema);
222
+ for (const [key, rule] of entries) {
223
+ if (!(rule && typeof rule === "object" && H(rule, "t") && typeof rule.t === "number")) throw new TypeError(`${key}: invalid celery-env spec`);
224
+ if (rule.t === LIST && rule.item.t === LIST) throw new TypeError(`${key}: nested list generation is not supported`);
225
+ if (rule.requiredWhen != null && typeof rule.requiredWhen !== "function") throw new TypeError(`${key}: requiredWhen must be a function`);
226
+ for (const field of ["default", "devDefault", "testDefault"]) {
227
+ if (H(rule, field) && !validDefault(rule, rule[field])) {
228
+ throw new TypeError(`${key}: ${field} does not satisfy validator`);
229
+ }
230
+ }
231
+ }
232
+ return entries;
233
+ }
234
+
235
+ function validDefault(rule, value) {
236
+ if (value === undefined) return rule.optional === true;
237
+ switch (rule.t) {
238
+ case STR:
239
+ return typeof value === "string" &&
240
+ (rule.min == null || value.length >= rule.min) &&
241
+ (rule.max == null || value.length <= rule.max) &&
242
+ (rule.startsWith == null || value.startsWith(rule.startsWith)) &&
243
+ (rule.includes == null || value.includes(rule.includes));
244
+ case INT:
245
+ return Number.isInteger(value) && (rule.min == null || value >= rule.min) && (rule.max == null || value <= rule.max);
246
+ case NUM:
247
+ return Number.isFinite(value) && (rule.min == null || value >= rule.min) && (rule.max == null || value <= rule.max);
248
+ case BOOL:
249
+ return typeof value === "boolean";
250
+ case ENUM:
251
+ return rule.values.includes(value);
252
+ case URL_T:
253
+ try {
254
+ return typeof value === "string" && (!rule.protocols || rule.ps.includes(new URL(value).protocol));
255
+ } catch {
256
+ return false;
257
+ }
258
+ case JSON_T:
259
+ return jsonDefaultSafe(value);
260
+ case LIST:
261
+ return Array.isArray(value) && value.every(v=>validDefault(rule.item, v));
262
+ default:
263
+ return false;
264
+ }
265
+ }
266
+
267
+ function exampleValue(rule) {
268
+ for (const field of ["example", "default", "devDefault", "testDefault"]) {
269
+ if (H(rule, field)) return envValue(rule[field], rule);
270
+ }
271
+ return "";
272
+ }
273
+
274
+ function envValue(value, rule) {
275
+ if (value == null) return "";
276
+ if (rule.t === LIST && Array.isArray(value)) return value.map(envScalar).join(rule.separator || ",");
277
+ if (rule.t === JSON_T) return safeLine(typeof value === "string" ? value : JSON.stringify(value));
278
+ return envScalar(value);
279
+ }
280
+
281
+ function envScalar(value) {
282
+ return safeLine(typeof value === "string" ? value : String(value));
283
+ }
284
+
285
+ function envKey(key) {
286
+ return String(key).replace(/[=\r\n]/g, "_");
287
+ }
288
+
289
+ function pushComment(lines, text) {
290
+ for (const line of String(text).split(/\r?\n/)) lines.push(`# ${safeLine(line)}`);
291
+ }
292
+
293
+ function safeLine(value) {
294
+ return String(value).replace(/\r?\n/g, "\\n");
295
+ }
296
+
297
+ function emitterContext(options) {
298
+ const optimize = options.optimize;
299
+ if (optimize && optimize !== "default" && optimize !== "speed") throw new TypeError(`Unknown optimize mode: ${optimize}`);
300
+ const t = options.splitLarge === false ? 1/0 : options.splitLargeThreshold ?? 512;
301
+ if (!Number.isInteger(t) && t !== 1/0) throw new TypeError("splitLargeThreshold must be an integer");
302
+ return { e: 0, f: options.failFast === true, g: [], h: 0, j: 0, s: optimize > "default", t };
303
+ }
304
+
305
+ function functionName(options) {
306
+ const fn = options.functionName || "loadEnv";
307
+ if (!/^[A-Za-z_$][\w$]*$/.test(fn) || reserved(fn)) throw new TypeError("functionName must be a JavaScript identifier");
308
+ return fn;
309
+ }
310
+
311
+ function reserved(value) {
312
+ return /^(?:arguments|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|eval|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)$/.test(value);
313
+ }
314
+
315
+ function hasProtoKey(entries) {
316
+ return entries.some(([key]) => key === "__proto__");
317
+ }
318
+
319
+ function needsListTempVar(rule) {
320
+ const k = emitFastListKind(rule);
321
+ return !k || k === 3 && rule.item.includes != null || k === 4;
322
+ }
323
+
324
+ function emitRule(key, rule, target, ctx) {
325
+ const out = [` v = ${envRead(key, ctx)};`];
326
+
327
+ const simple = simpleMissing(rule);
328
+ if (simple && rule.optional && target<"o") {
329
+ out.push(" if (v != null && v !== \"\") {");
330
+ } else if (simple) {
331
+ out.push(` if (v == null || v === "") ${rule.optional ? `${target} = undefined` : err(`${key} is required`, ctx)};`);
332
+ out.push(" else {");
333
+ } else {
334
+ out.push(" if (v == null || v === \"\") {");
335
+ emitMissing(key, rule, " ", target, out, ctx);
336
+ out.push(" }");
337
+ out.push(" else {");
338
+ }
339
+ out.push(...emitPresent(key, rule, " ", target, ctx));
340
+ out.push(" }");
341
+
342
+ return out;
343
+ }
344
+
345
+ function envRead(key, ctx) {
346
+ if(!ctx.h++)ctx.g.push("const H=Object.hasOwn;");
347
+ return `(H(env,${literal(key)})?env${prop(key)}:undefined)`;
348
+ }
349
+
350
+ function envFacade(ctx) {
351
+ if(!ctx.e++)ctx.g.push("function E(e){const o=Object.create(null);for(const k of Object.keys(e))o[k]=e[k];return o}");
352
+ return "E(env)";
353
+ }
354
+
355
+ function simpleMissing(rule) {
356
+ return !hasDefault(rule) && !rule.requiredWhen;
357
+ }
358
+
359
+ function emitMissing(key, rule, pad, target, out, ctx) {
360
+ let used = false;
361
+ if (H(rule, "testDefault")) {
362
+ out.push(`${pad}if (${envRead("NODE_ENV", ctx)} === "test") ${target} = ${literal(rule.testDefault)};`);
363
+ used = true;
364
+ }
365
+ if (H(rule, "devDefault")) {
366
+ out.push(`${pad}${used ? "else " : ""}if (${envRead("NODE_ENV", ctx)} !== "production") ${target} = ${literal(rule.devDefault)};`);
367
+ used = true;
368
+ }
369
+ if (H(rule, "default")) {
370
+ out.push(`${pad}${used ? "else " : ""}${target} = ${literal(rule.default)};`);
371
+ } else if (rule.optional && rule.requiredWhen) {
372
+ out.push(`${pad}${used ? "else " : ""}if (${requiredWhenExpr(rule)}(${envFacade(ctx)}) === true) ${err(`${key} is required`, ctx)};`);
373
+ if (target > "b") out.push(`${pad}else ${target} = undefined;`);
374
+ } else if (rule.optional) {
375
+ out.push(`${pad}${used ? "else " : ""}${target} = undefined;`);
376
+ } else {
377
+ out.push(`${pad}${used ? "else " : ""}${err(`${key} is required`, ctx)};`);
378
+ }
379
+ }
380
+
381
+ function emitPresent(key, rule, pad, target, ctx, value = "v") {
382
+ switch (rule.t) {
383
+ case STR:
384
+ return emitString(key, rule, pad, target, value, ctx);
385
+ case INT:
386
+ return emitNumber(key, rule, pad, target, true, value, ctx);
387
+ case NUM:
388
+ return emitNumber(key, rule, pad, target, false, value, ctx);
389
+ case BOOL:
390
+ return emitBool(key, pad, target, value, ctx);
391
+ case ENUM:
392
+ return emitEnum(key, rule, pad, target, value, ctx);
393
+ case URL_T:
394
+ return emitUrl(key, rule, pad, target, value, ctx);
395
+ case JSON_T:
396
+ return emitJson(key, pad, target, value, ctx);
397
+ case LIST:
398
+ return emitList(key, rule, pad, target, ctx);
399
+ default:
400
+ throw new Error(`${key}: unknown validator kind ${rule.t}`);
401
+ }
402
+ }
403
+
404
+ function emitJson(key, pad, target, value, ctx) {
405
+ if(!ctx.j++)ctx.g.push(`function J(v){return v[0]=="{"&&v[v.length-1]!="}"||v[0]=="["&&v[v.length-1]!="]"}`);
406
+ const er=err(`${key} must be valid JSON`,ctx);
407
+ return [`${pad}try { if (J(${value})) throw 0; ${target} = JSON.parse(${value}); } catch { ${er}; }`];
408
+ }
409
+
410
+ function emitString(key, rule, pad, target, value, ctx) {
411
+ const out = [];
412
+ if (rule.min != null && !skipMin(rule)) out.push(`${pad}if (${value}.length < ${num(rule.min)}) ${err(`${key} must have length >= ${rule.min}`, ctx)};`);
413
+ if (rule.max != null) out.push(`${out.length ? pad + "else " : pad}if (${value}.length > ${num(rule.max)}) ${err(`${key} must have length <= ${rule.max}`, ctx)};`);
414
+ if (rule.startsWith != null) out.push(`${out.length ? pad + "else " : pad}if (!${value}.startsWith(${literal(rule.startsWith)})) ${err(`${key} must start with ${rule.startsWith}`, ctx)};`);
415
+ if (rule.includes != null) out.push(`${out.length ? pad + "else " : pad}if (!${value}.includes(${literal(rule.includes)})) ${err(`${key} must include ${rule.includes}`, ctx)};`);
416
+ out.push(`${out.length ? pad + "else " : pad}${target} = ${value};`);
417
+ return out;
418
+ }
419
+
420
+ function emitNumber(key, rule, pad, target, integer, value, ctx) {
421
+ if (integer && rule.strict && int32Bounded(rule)) {
422
+ return emitStrictIntScalar(key, rule, pad, target, value, ctx);
423
+ }
424
+ if (!integer && rule.strict && ctx.s) {
425
+ return emitStrictNumScalar(key, rule, pad, target, value, ctx);
426
+ }
427
+ const out = [];
428
+ if (rule.strict) {
429
+ const re = integer ? "/^[+-]?\\d+$/" : "/^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)$/";
430
+ out.push(`${pad}if (!${re}.test(${value})) ${err(`${key} must be ${integer ? "a strict integer" : "a strict number"}`, ctx)};`);
431
+ out.push(`${pad}else {`);
432
+ pad += " ";
433
+ }
434
+ out.push(`${pad}${value} = +${value};`);
435
+ out.push(`${pad}if (${integer ? int32Bounded(rule) ? `(${value} | 0) !== ${value}` : `!Number.isInteger(${value})` : `!isFinite(${value})`}) ${err(`${key} must be ${integer ? "an integer" : "a number"}`, ctx)};`);
436
+ if (rule.min != null) out.push(`${pad}else if (${value} < ${num(rule.min)}) ${err(`${key} must be >= ${rule.min}`, ctx)};`);
437
+ if (rule.max != null) out.push(`${pad}else if (${value} > ${num(rule.max)}) ${err(`${key} must be <= ${rule.max}`, ctx)};`);
438
+ out.push(`${pad}else ${target} = ${value};`);
439
+ if (rule.strict) out.push(`${pad.slice(0, -2)}}`);
440
+ return out;
441
+ }
442
+
443
+ function emitStrictNumScalar(key, rule, pad, target, value, ctx) {
444
+ const out = [
445
+ `${pad}{`,
446
+ `${pad} let q = 0;`,
447
+ `${pad} let d;`,
448
+ `${pad} let h;`,
449
+ `${pad} let c = ${value}.charCodeAt(q);`,
450
+ `${pad} if (c === 43 || c === 45) q++;`,
451
+ `${pad} for (; q < ${value}.length; q++) {`,
452
+ `${pad} c = ${value}.charCodeAt(q);`,
453
+ `${pad} if (c === 46 && !h) h = 1;`,
454
+ `${pad} else if (c < 48 || c > 57) break;`,
455
+ `${pad} else d = 1;`,
456
+ `${pad} }`,
457
+ `${pad} if (!d || q !== ${value}.length) ${err(`${key} must be a strict number`, ctx)};`,
458
+ `${pad} else {`,
459
+ `${pad} ${value} = +${value};`,
460
+ `${pad} if (!isFinite(${value})) ${err(`${key} must be a number`, ctx)};`
461
+ ];
462
+ if (rule.min != null) out.push(`${pad} else if (${value} < ${num(rule.min)}) ${err(`${key} must be >= ${rule.min}`, ctx)};`);
463
+ if (rule.max != null) out.push(`${pad} else if (${value} > ${num(rule.max)}) ${err(`${key} must be <= ${rule.max}`, ctx)};`);
464
+ out.push(
465
+ `${pad} else ${target} = ${value};`,
466
+ `${pad} }`,
467
+ `${pad}}`
468
+ );
469
+ return out;
470
+ }
471
+
472
+ function emitStrictIntScalar(key, rule, pad, target, value, ctx) {
473
+ const out = [
474
+ `${pad}{`,
475
+ `${pad} let q = 0;`,
476
+ `${pad} let z = ${value}.length;`,
477
+ `${pad} let c = ${value}.charCodeAt(q);`,
478
+ `${pad} let g = 1;`,
479
+ `${pad} if (c === 43 || c === 45) { g = c === 45 ? -1 : 1; q++; }`,
480
+ `${pad} if (q === z) ${err(`${key} must be a strict integer`, ctx)};`,
481
+ `${pad} else {`,
482
+ `${pad} let n = 0;`,
483
+ `${pad} for (; q < z; q++) {`,
484
+ `${pad} c = ${value}.charCodeAt(q);`,
485
+ `${pad} if (c < 48 || c > 57) break;`,
486
+ `${pad} n = n * 10 + c - 48;`,
487
+ `${pad} }`,
488
+ `${pad} if (q !== z) ${err(`${key} must be a strict integer`, ctx)};`,
489
+ `${pad} else {`,
490
+ `${pad} n *= g;`,
491
+ `${pad} if ((n | 0) !== n) ${err(`${key} must be an integer`, ctx)};`
492
+ ];
493
+ if (rule.min != null) out.push(`${pad} else if (n < ${num(rule.min)}) ${err(`${key} must be >= ${rule.min}`, ctx)};`);
494
+ if (rule.max != null) out.push(`${pad} else if (n > ${num(rule.max)}) ${err(`${key} must be <= ${rule.max}`, ctx)};`);
495
+ out.push(
496
+ `${pad} else ${target} = n;`,
497
+ `${pad} }`,
498
+ `${pad} }`,
499
+ `${pad}}`
500
+ );
501
+ return out;
502
+ }
503
+
504
+ function emitEnum(key, rule, pad, target, value, ctx) {
505
+ if (rule.values.every(v=>typeof v==="string")) {
506
+ const checks = rule.values.map(v=>`${value} === ${literal(v)}`).join(" || ");
507
+ return [
508
+ `${pad}if (${checks}) ${target} = ${value};`,
509
+ `${pad}else ${err(`${key} must be one of ${rule.values.join(", ")}`, ctx)};`
510
+ ];
511
+ }
512
+ const out = [`${pad}switch (${value}) {`];
513
+ for (const v of rule.values) {
514
+ out.push(`${pad} case ${literal(String(v))}: ${target} = ${literal(v)}; break;`);
515
+ }
516
+ out.push(`${pad} default: ${err(`${key} must be one of ${rule.values.join(", ")}`, ctx)};`);
517
+ out.push(`${pad}}`);
518
+ return out;
519
+ }
520
+
521
+ function emitUrl(key, rule, pad, target, value, ctx) {
522
+ if (rule.protocols) {
523
+ const cases = rule.ps.map((protocol) => `case ${literal(protocol)}:`).join(" ");
524
+ return [`${pad}try { switch (new URL(${value}).protocol) { ${cases} ${target} = ${value}; break; default: ${err(`${key} must use protocol ${rule.protocols.join(", ")}`, ctx)}; } } catch { ${err(`${key} must be a URL`, ctx)}; }`];
525
+ }
526
+ return [`${pad}try { new URL(${value}); ${target} = ${value}; } catch { ${err(`${key} must be a URL`, ctx)}; }`];
527
+ }
528
+
529
+ function emitList(key, rule, pad, target, ctx) {
530
+ const fast = emitFastList(key, rule, pad, target, ctx);
531
+ if (fast) return fast;
532
+
533
+ if (rule.separator === "") {
534
+ const out = [
535
+ `${pad}{`,
536
+ `${pad} const l = new Array(v.length);`
537
+ ];
538
+ out.push(
539
+ `${pad} for (let i = 0; i < v.length; i++) {`,
540
+ `${pad} x = ${rule.trim === false ? "v[i]" : "v[i].trim()"};`
541
+ );
542
+ return finishList(out, key, rule, pad, target, ctx);
543
+ }
544
+ const out = [
545
+ `${pad}{`,
546
+ `${pad} const l = [];`
547
+ ];
548
+ out.push(
549
+ `${pad} for (let i = 0, s = 0, e;; i++, s = e + ${rule.separator.length}) {`,
550
+ `${pad} e = v.indexOf(${literal(rule.separator)}, s);`,
551
+ `${pad} x = e < 0 ? v.slice(s) : v.slice(s, e);`
552
+ );
553
+ if (rule.trim !== false) out.push(`${pad} x = x.trim();`);
554
+ out.push(...emitListItem(key, rule.item, `${pad} `, ctx));
555
+ out.push(
556
+ `${pad} if (e < 0) break;`,
557
+ `${pad} }`
558
+ );
559
+ out.push(listAssign(pad, target), `${pad}}`);
560
+ return out;
561
+ }
562
+
563
+ function emitFastListKind(rule) {
564
+ const item = rule.item;
565
+ if (rule.separator === "" && item.t === STR && rule.trim === false && item.min == null && item.max == null && item.startsWith == null && item.includes == null) return 1;
566
+ if (item.requiredWhen || "devDefault" in item || "testDefault" in item) return;
567
+ if (rule.separator !== "" && item.t === INT && item.strict && int32Bounded(item)) return 2;
568
+ if ("default" in item || item.optional) return;
569
+ if (rule.separator === "") return;
570
+ if (item.t === STR) return 3;
571
+ if (item.t === ENUM && item.values.length > 15 && !item.values.some(v=>typeof v!=="string")) return 4;
572
+ }
573
+
574
+ function emitFastList(key, rule, pad, target, ctx) {
575
+ switch (emitFastListKind(rule)) {
576
+ case 1:
577
+ return [`${pad}${target} = v.split("");`];
578
+ case 2:
579
+ return emitFastStrictIntList(key, rule, pad, target, ctx);
580
+ case 3:
581
+ return emitSegmentStringList(key, rule, pad, target, ctx);
582
+ case 4:
583
+ return emitSetList(key, rule, pad, target, ctx);
584
+ }
585
+ }
586
+
587
+ function emitSetList(key, rule, pad, target, ctx) {
588
+ const item = rule.item;
589
+ const n = `S${ctx.g.length}`;
590
+ ctx.g.push(`const ${n}=new Set(${literal(item.values)});`);
591
+ const out = segmentListHeader(rule, pad, ctx);
592
+ out.push(
593
+ `${pad} if (a === z) ${err(`${key} item is required`, ctx)};`,
594
+ `${pad} else if (${n}.has(x = v.slice(a, z))) l[i] = x;`,
595
+ `${pad} else ${err(`${key} item must be one of ${item.values.join(", ")}`, ctx)};`
596
+ );
597
+ return finishSegmentList(out, pad, target, ctx);
598
+ }
599
+
600
+ function emitFastStrictIntList(key, rule, pad, target, ctx) {
601
+ const item = rule.item;
602
+ const out = segmentListHeader(rule, pad, ctx);
603
+ out.push(
604
+ `${pad} if (a === z) ${"default" in item || item.optional ? `l[i] = ${literal(item.default)}` : err(`${key} item must be a strict integer`, ctx)};`,
605
+ `${pad} else {`,
606
+ `${pad} let q = a;`,
607
+ `${pad} let c = v.charCodeAt(q);`,
608
+ `${pad} let g = 1;`,
609
+ `${pad} if (c === 43 || c === 45) { g = c === 45 ? -1 : 1; q++; }`,
610
+ `${pad} if (q === z) ${err(`${key} item must be a strict integer`, ctx)};`,
611
+ `${pad} else {`,
612
+ `${pad} let n = 0;`,
613
+ `${pad} for (; q < z; q++) {`,
614
+ `${pad} c = v.charCodeAt(q);`,
615
+ `${pad} if (c < 48 || c > 57) break;`,
616
+ `${pad} n = n * 10 + c - 48;`,
617
+ `${pad} }`,
618
+ `${pad} if (q !== z) ${err(`${key} item must be a strict integer`, ctx)};`,
619
+ `${pad} else {`,
620
+ `${pad} n *= g;`,
621
+ `${pad} if ((n | 0) !== n) ${err(`${key} item must be an integer`, ctx)};`
622
+ );
623
+ if (item.min != null) out.push(`${pad} else if (n < ${num(item.min)}) ${err(`${key} item must be >= ${item.min}`, ctx)};`);
624
+ if (item.max != null) out.push(`${pad} else if (n > ${num(item.max)}) ${err(`${key} item must be <= ${item.max}`, ctx)};`);
625
+ out.push(
626
+ `${pad} else l[i] = n;`,
627
+ `${pad} }`,
628
+ `${pad} }`,
629
+ `${pad} }`,
630
+ `${pad} if (e < 0) break;`,
631
+ `${pad} }`
632
+ );
633
+ out.push(listAssign(pad, target), `${pad}}`);
634
+ return out;
635
+ }
636
+
637
+ function emitSegmentStringList(key, rule, pad, target, ctx) {
638
+ const item = rule.item;
639
+ const out = segmentListHeader(rule, pad, ctx);
640
+ const label = `${key} item`;
641
+ const len = "z - a";
642
+ let checked = false;
643
+ if (item.min != null && !skipMin(item)) {
644
+ out.push(`${pad} if (${len} < ${num(item.min)}) ${err(`${label} must have length >= ${item.min}`, ctx)};`);
645
+ checked = true;
646
+ }
647
+ if (item.max != null) {
648
+ out.push(`${checked ? pad + " else " : pad + " "}if (${len} > ${num(item.max)}) ${err(`${label} must have length <= ${item.max}`, ctx)};`);
649
+ checked = true;
650
+ }
651
+ if (item.startsWith != null) {
652
+ const prefix = literal(item.startsWith);
653
+ out.push(`${checked ? pad + " else " : pad + " "}if (${len} < ${item.startsWith.length} || !v.startsWith(${prefix}, a)) ${err(`${label} must start with ${item.startsWith}`, ctx)};`);
654
+ checked = true;
655
+ }
656
+ if (item.includes != null) {
657
+ const needle = literal(item.includes);
658
+ const needleLen = item.includes.length;
659
+ out.push(`${checked ? pad + " else " : pad + " "}if (((x = v.indexOf(${needle}, a)) < 0) || x + ${needleLen} > z) ${err(`${label} must include ${item.includes}`, ctx)};`);
660
+ checked = true;
661
+ }
662
+ out.push(`${checked ? pad + " else " : pad + " "}l[i] = v.slice(a, z);`);
663
+ return finishSegmentList(out, pad, target, ctx);
664
+ }
665
+
666
+ function segmentListHeader(rule, pad, ctx) {
667
+ const out = [
668
+ `${pad}{`,
669
+ `${pad} const l = [];`
670
+ ];
671
+ out.push(
672
+ `${pad} for (let i = 0, s = 0, e;; i++, s = e + ${rule.separator.length}) {`,
673
+ `${pad} e = v.indexOf(${literal(rule.separator)}, s);`,
674
+ `${pad} let a = s;`,
675
+ `${pad} let z = e < 0 ? v.length : e;`
676
+ );
677
+ if (rule.trim !== false) {
678
+ out.push(
679
+ `${pad} while (a < z) { const c = v.charCodeAt(a); if (!${W}) break; a++; }`,
680
+ `${pad} while (z > a) { const c = v.charCodeAt(z - 1); if (!${W}) break; z--; }`
681
+ );
682
+ }
683
+ return out;
684
+ }
685
+
686
+ function finishSegmentList(out, pad, target, ctx) {
687
+ out.push(
688
+ `${pad} if (e < 0) break;`,
689
+ `${pad} }`
690
+ );
691
+ out.push(listAssign(pad, target), `${pad}}`);
692
+ return out;
693
+ }
694
+
695
+ function finishList(out, key, rule, pad, target, ctx) {
696
+ out.push(...emitListItem(key, rule.item, `${pad} `, ctx));
697
+ out.push(
698
+ `${pad} }`,
699
+ listAssign(pad, target),
700
+ `${pad}}`
701
+ );
702
+ return out;
703
+ }
704
+
705
+ function listAssign(pad, target) {
706
+ return `${pad} ${target} = l;`;
707
+ }
708
+
709
+ function emitListItem(key, rule, pad, ctx) {
710
+ key = `${key} item`;
711
+ if (rule.optional || !simpleMissing(rule)) {
712
+ const out = [`${pad}if (x === "") {`];
713
+ emitMissing(key, rule, `${pad} `, "l[i]", out, ctx);
714
+ out.push(`${pad}} else {`, ...emitPresent(key, rule, `${pad} `, "l[i]", ctx, "x"), `${pad}}`);
715
+ return out;
716
+ }
717
+ return emitPresent(key, rule, pad, "l[i]", ctx, "x");
718
+ }
719
+
720
+ function emitBool(key, pad, target, value, ctx) {
721
+ return [
722
+ `${pad}if (${value}==="true"||${value}==="1"||${value}==="yes"||${value}==="on") ${target} = true;`,
723
+ `${pad}else if (${value}==="false"||${value}==="0"||${value}==="no"||${value}==="off") ${target} = false;`,
724
+ `${pad}else ${err(`${key} must be a boolean`, ctx)};`
725
+ ];
726
+ }
727
+
728
+ function innerTypeFor(rule) {
729
+ switch (rule.t) {
730
+ case STR:
731
+ case URL_T:
732
+ return "string";
733
+ case INT:
734
+ case NUM:
735
+ return "number";
736
+ case BOOL:
737
+ return "boolean";
738
+ case ENUM:
739
+ return rule.values.map(literal).join(" | ");
740
+ case JSON_T:
741
+ return "unknown";
742
+ case LIST:
743
+ return `readonly ${innerTypeFor(rule.item)}[]`;
744
+ default:
745
+ return "never";
746
+ }
747
+ }
748
+
749
+ function err(message, ctx) {
750
+ return ctx.f ? `throw Error(${literal(`Invalid environment:\n- ${message}`)})` : `(r ??= []).push(${literal(message)})`;
751
+ }
752
+
753
+ function literal(value) {
754
+ return JSON.stringify(value);
755
+ }
756
+
757
+ function returnObject(entries, source) {
758
+ return entries.length ? `{ ${entries.map(([key], i) => `${prop(key, 1)}: ${source ? `${source}[${i}]` : `_${i}`}`).join(", ")} }` : "{}";
759
+ }
760
+
761
+ function num(value) {
762
+ if (!Number.isFinite(value)) throw new TypeError(`Invalid numeric option: ${value}`);
763
+ return String(value);
764
+ }
765
+
766
+ function hasDefault(rule) {
767
+ return H(rule, "default") || H(rule, "devDefault") || H(rule, "testDefault");
768
+ }
769
+
770
+ function skipMin(rule) {
771
+ return rule.includes?.length >= rule.min || rule.startsWith?.length >= rule.min || rule.min < 2 && (rule.optional || H(rule, "default"));
772
+ }
773
+
774
+ function requiredWhenExpr(rule) {
775
+ const source = Function.prototype.toString.call(rule.requiredWhen);
776
+ if (source.includes("[native code]")) throw new TypeError("requiredWhen must be a source-serializable function");
777
+ if (!/^(?:async\s+)?(?:function\b|(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>)/.test(source.trim())) throw new TypeError("requiredWhen must serialize to a function expression");
778
+ return `(${source})`;
779
+ }
780
+
781
+ function jsonDefaultSafe(value) {
782
+ if (value == null) return value === null;
783
+ switch (typeof value) {
784
+ case "string":
785
+ case "boolean":
786
+ return true;
787
+ case "number":
788
+ return Number.isFinite(value);
789
+ case "object": {
790
+ if (Array.isArray(value)) {
791
+ for (let i = 0; i < value.length; i++) if (!H(value, i) || !jsonDefaultSafe(value[i])) return false;
792
+ return true;
793
+ }
794
+ const proto = Object.getPrototypeOf(value);
795
+ if (proto !== Object.prototype && proto !== null) return false;
796
+ if (Object.getOwnPropertySymbols(value).length || Object.getOwnPropertyNames(value).length !== Object.keys(value).length) return false;
797
+ return Object.keys(value).every((key) => jsonDefaultSafe(value[key]));
798
+ }
799
+ default:
800
+ return false;
801
+ }
802
+ }
803
+
804
+ function int32Bounded(rule) {
805
+ return Number.isInteger(rule.min) && Number.isInteger(rule.max) && rule.min >= -2147483648 && rule.max <= 2147483647;
806
+ }
807
+
808
+ function prop(key, bare) {
809
+ if (key === "__proto__") return `[${literal(key)}]`;
810
+ return /^[A-Za-z_$][\w$]*$/.test(key)?bare?key:`.${key}`:bare?literal(key):`[${literal(key)}]`;
811
+ }