@zenera/faker 1.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.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/box.d.ts +43 -0
- package/dist/box.js +158 -0
- package/dist/cache.d.ts +57 -0
- package/dist/cache.js +125 -0
- package/dist/command.d.ts +3 -0
- package/dist/command.js +339 -0
- package/dist/envelope.d.ts +13 -0
- package/dist/envelope.js +9 -0
- package/dist/generate.d.ts +38 -0
- package/dist/generate.js +125 -0
- package/dist/image.d.ts +25 -0
- package/dist/image.js +77 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.js +363 -0
- package/dist/probe.d.ts +7 -0
- package/dist/probe.js +278 -0
- package/dist/prompt.d.ts +11 -0
- package/dist/prompt.js +98 -0
- package/dist/router.d.ts +14 -0
- package/dist/router.js +83 -0
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +221 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +256 -0
- package/dist/setup.d.ts +35 -0
- package/dist/setup.js +81 -0
- package/dist/spec.d.ts +41 -0
- package/dist/spec.js +257 -0
- package/dist/validate.d.ts +21 -0
- package/dist/validate.js +83 -0
- package/package.json +51 -0
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { AVAILABLE } from "./image.js";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// What the model is told
|
|
4
|
+
//
|
|
5
|
+
// A contract, not a request. The generator is a program with a fixed calling
|
|
6
|
+
// convention that this package depends on in three places — the entry point,
|
|
7
|
+
// the input shape, the output file — so those are stated as rules rather than
|
|
8
|
+
// as suggestions, and everything discretionary is stated as taste.
|
|
9
|
+
//
|
|
10
|
+
// The one rule worth its own paragraph is the echo: a mock that answers
|
|
11
|
+
// `GET /users/12324` with somebody else's id is worse than useless, because it
|
|
12
|
+
// validates and quietly breaks whatever is being developed against it.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
export const SYSTEM = [
|
|
15
|
+
'You write one Python 3 file that fabricates a plausible HTTP response body.',
|
|
16
|
+
'',
|
|
17
|
+
'CALLING CONVENTION',
|
|
18
|
+
'- `sys.argv[1]` is a path to a JSON file to read; `sys.argv[2]` is a path to write to.',
|
|
19
|
+
'- Write exactly one JSON document to `sys.argv[2]` and print nothing to stdout.',
|
|
20
|
+
'- Exit non-zero with a message on stderr if you cannot produce a valid body.',
|
|
21
|
+
'',
|
|
22
|
+
'THE INPUT FILE holds:',
|
|
23
|
+
' operationId, method, path, pathParams, query, headers, body, seed',
|
|
24
|
+
'',
|
|
25
|
+
'ENVIRONMENT',
|
|
26
|
+
`- These libraries are installed: ${AVAILABLE.join(', ')}, plus the standard library.`,
|
|
27
|
+
'- There is no network and no pip. Importing anything else fails the file.',
|
|
28
|
+
'- Seed both `random.seed(seed)` and `Faker.seed(seed)` from the input, first thing.',
|
|
29
|
+
'',
|
|
30
|
+
'RULES',
|
|
31
|
+
'1. The output must validate against the response schema given below. Build the',
|
|
32
|
+
' object, then check it with `jsonschema.validate` before writing, and fix it',
|
|
33
|
+
' rather than emitting something that fails.',
|
|
34
|
+
'2. Echo the request. Where a path parameter has the same name as a property',
|
|
35
|
+
" in the response schema, that property must carry the request's value,",
|
|
36
|
+
' converted to the type the schema declares. `GET /users/{user_id}` called',
|
|
37
|
+
' with `user_id=12324` answers with `user_id` 12324, not a random one. Do',
|
|
38
|
+
' this generically, by looking the names up at run time. Do the same for a',
|
|
39
|
+
' query parameter where it plainly describes the content rather than',
|
|
40
|
+
' controlling the call.',
|
|
41
|
+
'3. Every required property must be present. Optional ones may be omitted',
|
|
42
|
+
' sometimes; that is what makes a mock useful.',
|
|
43
|
+
'4. Values must suit their names, not just their types. Use `faker` for anything',
|
|
44
|
+
' a person would recognise — names, emails, addresses, companies, phone',
|
|
45
|
+
' numbers, sentences. Use `exrex.getone(pattern)` when a string schema has a',
|
|
46
|
+
' `pattern`. Respect `enum`, `format`, `minimum`, `maxLength` and friends.',
|
|
47
|
+
'5. Arrays get 1 to 5 items unless the schema says otherwise.',
|
|
48
|
+
'6. The file is run once per request and must be deterministic for a given seed.',
|
|
49
|
+
'',
|
|
50
|
+
'ANSWER WITH THE FILE AND NOTHING ELSE. No explanation, no markdown fence.',
|
|
51
|
+
].join('\n');
|
|
52
|
+
export function brief(operation) {
|
|
53
|
+
const lines = [
|
|
54
|
+
`OPERATION ${operation.method.toUpperCase()} ${operation.path}`,
|
|
55
|
+
`operationId: ${operation.operationId}`,
|
|
56
|
+
];
|
|
57
|
+
if (operation.summary) {
|
|
58
|
+
lines.push(`summary: ${operation.summary}`);
|
|
59
|
+
}
|
|
60
|
+
if (operation.description && operation.description !== operation.summary) {
|
|
61
|
+
lines.push(`description: ${operation.description.slice(0, 600)}`);
|
|
62
|
+
}
|
|
63
|
+
lines.push('', 'PARAMETERS');
|
|
64
|
+
if (operation.params.length === 0) {
|
|
65
|
+
lines.push(' (none)');
|
|
66
|
+
}
|
|
67
|
+
for (const p of operation.params) {
|
|
68
|
+
const note = p.description ? ` # ${p.description.split('\n')[0].slice(0, 100)}` : '';
|
|
69
|
+
lines.push(` ${p.name} (in ${p.in}${p.required ? ', required' : ''}): ${JSON.stringify(p.schema)}${note}`);
|
|
70
|
+
}
|
|
71
|
+
if (operation.requestBody) {
|
|
72
|
+
lines.push('', 'REQUEST BODY SCHEMA (arrives as `body`)', json(operation.requestBody.schema));
|
|
73
|
+
}
|
|
74
|
+
lines.push('', `RESPONSE SCHEMA (status ${operation.success.status})`);
|
|
75
|
+
lines.push(operation.success.schema ? json(operation.success.schema) : ' (no body)');
|
|
76
|
+
return lines.join('\n');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Repeated in the file the model writes, so it is worth spelling out: the
|
|
80
|
+
* schema it validates against must be the one it was shown, embedded, not
|
|
81
|
+
* loaded from anywhere.
|
|
82
|
+
*/
|
|
83
|
+
export function instruction(operation) {
|
|
84
|
+
return [
|
|
85
|
+
brief(operation),
|
|
86
|
+
'',
|
|
87
|
+
'Embed the response schema in the file as a literal and validate against it.',
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
90
|
+
export function retry(diagnostics) {
|
|
91
|
+
return [
|
|
92
|
+
'That file did not pass. Fix it and answer with the whole file again.',
|
|
93
|
+
'',
|
|
94
|
+
...diagnostics,
|
|
95
|
+
].join('\n');
|
|
96
|
+
}
|
|
97
|
+
const json = (value) => JSON.stringify(value, null, 1);
|
|
98
|
+
//# sourceMappingURL=prompt.js.map
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Method, type Operation } from './spec.ts';
|
|
2
|
+
export interface Match {
|
|
3
|
+
operation: Operation;
|
|
4
|
+
pathParams: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export declare class Router {
|
|
7
|
+
#private;
|
|
8
|
+
readonly operations: readonly Operation[];
|
|
9
|
+
constructor(operations: readonly Operation[]);
|
|
10
|
+
match(method: string, pathname: string): Match | undefined;
|
|
11
|
+
/** Whether the path exists under some other method — a 405, not a 404. */
|
|
12
|
+
allowed(pathname: string): Method[];
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=router.d.ts.map
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { SpecError } from "./spec.js";
|
|
2
|
+
export class Router {
|
|
3
|
+
#routes = new Map();
|
|
4
|
+
operations;
|
|
5
|
+
constructor(operations) {
|
|
6
|
+
this.operations = operations;
|
|
7
|
+
const seen = new Map();
|
|
8
|
+
for (const operation of operations) {
|
|
9
|
+
const id = `${operation.method} ${operation.path}`;
|
|
10
|
+
const clash = seen.get(id);
|
|
11
|
+
if (clash) {
|
|
12
|
+
throw new SpecError(`${id} is declared twice`, `${clash.source} and ${operation.source} both define it`);
|
|
13
|
+
}
|
|
14
|
+
seen.set(id, operation);
|
|
15
|
+
const segments = compile(operation.path);
|
|
16
|
+
const list = this.#routes.get(operation.method) ?? [];
|
|
17
|
+
list.push({
|
|
18
|
+
operation,
|
|
19
|
+
segments,
|
|
20
|
+
specificity: segments.filter((s) => s.name === undefined).length,
|
|
21
|
+
});
|
|
22
|
+
this.#routes.set(operation.method, list);
|
|
23
|
+
}
|
|
24
|
+
for (const list of this.#routes.values()) {
|
|
25
|
+
list.sort((a, b) => b.specificity - a.specificity);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
match(method, pathname) {
|
|
29
|
+
const parts = split(pathname);
|
|
30
|
+
for (const route of this.#routes.get(method.toLowerCase()) ?? []) {
|
|
31
|
+
const pathParams = apply(route.segments, parts);
|
|
32
|
+
if (pathParams) {
|
|
33
|
+
return { operation: route.operation, pathParams };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Whether the path exists under some other method — a 405, not a 404. */
|
|
39
|
+
allowed(pathname) {
|
|
40
|
+
const parts = split(pathname);
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const [method, list] of this.#routes) {
|
|
43
|
+
if (list.some((r) => apply(r.segments, parts) !== undefined)) {
|
|
44
|
+
out.push(method);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function compile(template) {
|
|
51
|
+
return split(template).map((raw) => {
|
|
52
|
+
const m = /^\{(.+)\}$/.exec(raw);
|
|
53
|
+
return m ? { name: m[1] } : { literal: decode(raw) };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const split = (path) => path.split('/').filter((s) => s.length > 0);
|
|
57
|
+
function apply(segments, parts) {
|
|
58
|
+
if (segments.length !== parts.length) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const out = {};
|
|
62
|
+
for (let i = 0; i < segments.length; i++) {
|
|
63
|
+
const segment = segments[i];
|
|
64
|
+
if (segment.name === undefined) {
|
|
65
|
+
if (segment.literal !== decode(parts[i])) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
out[segment.name] = decode(parts[i]);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/** A malformed escape is the client's problem; take the segment verbatim. */
|
|
75
|
+
function decode(part) {
|
|
76
|
+
try {
|
|
77
|
+
return decodeURIComponent(part);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return part;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=router.js.map
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type Schema = Record<string, unknown>;
|
|
2
|
+
export type Dialect = 'swagger-2.0' | 'openapi-3.0' | 'openapi-3.1';
|
|
3
|
+
/**
|
|
4
|
+
* Converts a dereferenced schema into 2020-12, hoisting anything reached more
|
|
5
|
+
* than once into `$defs`. The result is acyclic and safe to stringify.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalize(root: unknown, dialect: Dialect): Schema;
|
|
8
|
+
//# sourceMappingURL=schema.d.ts.map
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Schemas, made uniform
|
|
3
|
+
//
|
|
4
|
+
// Three dialects arrive here and one leaves. Swagger 2.0 carries Draft-4,
|
|
5
|
+
// OpenAPI 3.0 carries a Draft-4 derivative with its own `nullable`, and
|
|
6
|
+
// OpenAPI 3.1 is honest 2020-12. Ajv can be asked to speak any of them, but
|
|
7
|
+
// then every consumer downstream — the request check, the response check, the
|
|
8
|
+
// prompt the model reads, the `jsonschema` call inside the generator — has to
|
|
9
|
+
// be told which one it is looking at. So they are converted once, here, into
|
|
10
|
+
// 2020-12, and nothing below this file knows a dialect exists.
|
|
11
|
+
//
|
|
12
|
+
// The other job is cycles. `dereference` replaces every `$ref` with the object
|
|
13
|
+
// it pointed at, so a self-referencing schema comes back as a *cyclic JS
|
|
14
|
+
// object* — which Ajv cannot compile and `JSON.stringify` cannot print. Any
|
|
15
|
+
// node reached more than once is therefore hoisted into `$defs` and referred to
|
|
16
|
+
// by `$ref`, which restores the recursion in the one form every tool here can
|
|
17
|
+
// read. Shared-but-acyclic components take the same route, and the schema the
|
|
18
|
+
// model is shown gets smaller for free.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/** Subschema positions, by the shape of what sits in them. */
|
|
21
|
+
const ONE = [
|
|
22
|
+
'not',
|
|
23
|
+
'if',
|
|
24
|
+
'then',
|
|
25
|
+
'else',
|
|
26
|
+
'contains',
|
|
27
|
+
'propertyNames',
|
|
28
|
+
'additionalProperties',
|
|
29
|
+
'unevaluatedProperties',
|
|
30
|
+
'additionalItems',
|
|
31
|
+
'unevaluatedItems',
|
|
32
|
+
];
|
|
33
|
+
const MAP = ['properties', 'patternProperties', '$defs', 'definitions'];
|
|
34
|
+
const LIST = ['allOf', 'anyOf', 'oneOf', 'prefixItems'];
|
|
35
|
+
/** Annotations that mean nothing to a validator and cost prompt tokens. */
|
|
36
|
+
const DROP = new Set([
|
|
37
|
+
'example',
|
|
38
|
+
'externalDocs',
|
|
39
|
+
'xml',
|
|
40
|
+
'discriminator',
|
|
41
|
+
'deprecated',
|
|
42
|
+
'x-internal',
|
|
43
|
+
]);
|
|
44
|
+
const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
45
|
+
/**
|
|
46
|
+
* Converts a dereferenced schema into 2020-12, hoisting anything reached more
|
|
47
|
+
* than once into `$defs`. The result is acyclic and safe to stringify.
|
|
48
|
+
*/
|
|
49
|
+
export function normalize(root, dialect) {
|
|
50
|
+
if (!isObject(root)) {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
const shared = repeated(root);
|
|
54
|
+
const defs = new Map();
|
|
55
|
+
const bodies = new Map();
|
|
56
|
+
const walk = (node) => {
|
|
57
|
+
if (!isObject(node)) {
|
|
58
|
+
return node;
|
|
59
|
+
}
|
|
60
|
+
const name = defs.get(node);
|
|
61
|
+
if (name !== undefined) {
|
|
62
|
+
return { $ref: `#/$defs/${name}` };
|
|
63
|
+
}
|
|
64
|
+
if (!shared.has(node)) {
|
|
65
|
+
return convert(node, dialect, walk);
|
|
66
|
+
}
|
|
67
|
+
const id = `def${defs.size}`;
|
|
68
|
+
// Registered *before* the body is built, so a node that contains
|
|
69
|
+
// itself refers to the name rather than recursing forever.
|
|
70
|
+
defs.set(node, id);
|
|
71
|
+
bodies.set(id, convert(node, dialect, walk));
|
|
72
|
+
return { $ref: `#/$defs/${id}` };
|
|
73
|
+
};
|
|
74
|
+
// A root that is itself recursive comes back as a bare `$ref`; 2020-12
|
|
75
|
+
// allows siblings on one, so `$defs` and `$schema` still land here.
|
|
76
|
+
const out = walk(root);
|
|
77
|
+
if (bodies.size > 0) {
|
|
78
|
+
out.$defs = Object.fromEntries(bodies);
|
|
79
|
+
}
|
|
80
|
+
out.$schema = 'https://json-schema.org/draft/2020-12/schema';
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
function convert(node, dialect, walk) {
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const [key, value] of Object.entries(node)) {
|
|
86
|
+
if (DROP.has(key) || value === undefined) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (ONE.includes(key)) {
|
|
90
|
+
out[key] = typeof value === 'boolean' ? value : walk(value);
|
|
91
|
+
}
|
|
92
|
+
else if (MAP.includes(key) && isObject(value)) {
|
|
93
|
+
const target = key === 'definitions' ? '$defs' : key;
|
|
94
|
+
out[target] = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, typeof v === 'boolean' ? v : walk(v)]));
|
|
95
|
+
}
|
|
96
|
+
else if (LIST.includes(key) && Array.isArray(value)) {
|
|
97
|
+
out[key] = value.map(walk);
|
|
98
|
+
}
|
|
99
|
+
else if (key === 'items') {
|
|
100
|
+
// Draft-4 and Swagger 2.0 spell tuples as an array here; 2020-12
|
|
101
|
+
// spells them `prefixItems` and keeps `items` for the tail.
|
|
102
|
+
out[Array.isArray(value) ? 'prefixItems' : 'items'] = Array.isArray(value)
|
|
103
|
+
? value.map(walk)
|
|
104
|
+
: walk(value);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
out[key] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (dialect !== 'openapi-3.1') {
|
|
111
|
+
widenNullable(node, out);
|
|
112
|
+
fixExclusive(out);
|
|
113
|
+
}
|
|
114
|
+
fixPattern(out);
|
|
115
|
+
// `type: file` is Swagger 2.0's way of saying "bytes".
|
|
116
|
+
if (out.type === 'file') {
|
|
117
|
+
out.type = 'string';
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(out.required) && out.required.length === 0) {
|
|
120
|
+
delete out.required;
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* `pattern: /^[_a-z0-9-]+$/` — a JavaScript regex *literal*, delimiters and all
|
|
126
|
+
* — is written in real documents. JSON Schema wants the body alone, and with
|
|
127
|
+
* the slashes left on, the expression is unsatisfiable: no string both contains
|
|
128
|
+
* a slash and begins after it. Left alone it makes the endpoint impossible to
|
|
129
|
+
* answer rather than merely badly specified, so the delimiters come off, and a
|
|
130
|
+
* pattern that still will not compile is dropped instead of enforced.
|
|
131
|
+
*/
|
|
132
|
+
function fixPattern(out) {
|
|
133
|
+
const raw = out.pattern;
|
|
134
|
+
if (typeof raw !== 'string') {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const literal = /^\/(.+)\/[dgimsuvy]*$/s.exec(raw);
|
|
138
|
+
const body = literal ? literal[1] : raw;
|
|
139
|
+
try {
|
|
140
|
+
new RegExp(body);
|
|
141
|
+
out.pattern = body;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
delete out.pattern;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** `nullable: true` is not a 2020-12 keyword; a union type is. */
|
|
148
|
+
function widenNullable(node, out) {
|
|
149
|
+
if (node.nullable !== true) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
delete out.nullable;
|
|
153
|
+
const type = out.type;
|
|
154
|
+
if (typeof type === 'string') {
|
|
155
|
+
out.type = [type, 'null'];
|
|
156
|
+
}
|
|
157
|
+
else if (Array.isArray(type)) {
|
|
158
|
+
out.type = type.includes('null') ? type : [...type, 'null'];
|
|
159
|
+
}
|
|
160
|
+
else if (Array.isArray(out.enum) && !out.enum.includes(null)) {
|
|
161
|
+
out.enum = [...out.enum, null];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Draft-4 spells an exclusive bound as a boolean flag on the inclusive one.
|
|
166
|
+
* Left alone, Ajv reads `exclusiveMinimum: true` as "the minimum is 1".
|
|
167
|
+
*/
|
|
168
|
+
function fixExclusive(out) {
|
|
169
|
+
for (const [flag, bound] of [
|
|
170
|
+
['exclusiveMinimum', 'minimum'],
|
|
171
|
+
['exclusiveMaximum', 'maximum'],
|
|
172
|
+
]) {
|
|
173
|
+
if (typeof out[flag] !== 'boolean') {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const value = out[bound];
|
|
177
|
+
if (out[flag] === true && typeof value === 'number') {
|
|
178
|
+
out[flag] = value;
|
|
179
|
+
delete out[bound];
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
delete out[flag];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Every schema-position object reached by more than one path, or by itself. */
|
|
187
|
+
function repeated(root) {
|
|
188
|
+
const seen = new Set();
|
|
189
|
+
const twice = new Set();
|
|
190
|
+
const stack = [root];
|
|
191
|
+
while (stack.length > 0) {
|
|
192
|
+
const node = stack.pop();
|
|
193
|
+
if (!isObject(node)) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (seen.has(node)) {
|
|
197
|
+
twice.add(node);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
seen.add(node);
|
|
201
|
+
for (const key of ONE) {
|
|
202
|
+
stack.push(node[key]);
|
|
203
|
+
}
|
|
204
|
+
for (const key of LIST) {
|
|
205
|
+
const value = node[key];
|
|
206
|
+
if (Array.isArray(value)) {
|
|
207
|
+
stack.push(...value);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (const key of MAP) {
|
|
211
|
+
const value = node[key];
|
|
212
|
+
if (isObject(value)) {
|
|
213
|
+
stack.push(...Object.values(value));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const items = node.items;
|
|
217
|
+
stack.push(...(Array.isArray(items) ? items : [items]));
|
|
218
|
+
}
|
|
219
|
+
return twice;
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=schema.js.map
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Server } from 'node:http';
|
|
2
|
+
import type { Box } from './box.ts';
|
|
3
|
+
import { type Cache } from './cache.ts';
|
|
4
|
+
import type { Router } from './router.ts';
|
|
5
|
+
import { type Checks } from './validate.ts';
|
|
6
|
+
export interface ServerOptions {
|
|
7
|
+
router: Router;
|
|
8
|
+
cache: Cache;
|
|
9
|
+
checks: Checks;
|
|
10
|
+
box: Box;
|
|
11
|
+
/** fixed base seed — the same request then answers the same way */
|
|
12
|
+
seed?: number;
|
|
13
|
+
maxBody?: number;
|
|
14
|
+
onRequest?: (line: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface Listening {
|
|
17
|
+
server: Server;
|
|
18
|
+
port: number;
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare function build(opts: ServerOptions): Server;
|
|
22
|
+
export declare function listen(opts: ServerOptions, host: string, port: number): Promise<Listening>;
|
|
23
|
+
//# sourceMappingURL=server.d.ts.map
|