@zenera/gym 0.0.0-stage → 1.1.38
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 +200 -2
- package/dist/box.d.ts +66 -0
- package/dist/box.js +259 -0
- package/dist/cache.d.ts +87 -0
- package/dist/cache.js +275 -0
- package/dist/command.d.ts +3 -0
- package/dist/command.js +414 -0
- package/dist/envelope.d.ts +13 -0
- package/dist/envelope.js +9 -0
- package/dist/generate.d.ts +57 -0
- package/dist/generate.js +236 -0
- package/dist/image.d.ts +25 -0
- package/dist/image.js +81 -0
- package/dist/logger.d.ts +89 -0
- package/dist/logger.js +321 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.js +363 -0
- package/dist/page.d.ts +3 -0
- package/dist/page.js +499 -0
- package/dist/paging.d.ts +79 -0
- package/dist/paging.js +518 -0
- package/dist/probe.d.ts +12 -0
- package/dist/probe.js +307 -0
- package/dist/prompt.d.ts +25 -0
- package/dist/prompt.js +188 -0
- package/dist/router.d.ts +20 -0
- package/dist/router.js +101 -0
- package/dist/schema.d.ts +21 -0
- package/dist/schema.js +296 -0
- package/dist/server.d.ts +31 -0
- package/dist/server.js +626 -0
- package/dist/setup.d.ts +53 -0
- package/dist/setup.js +123 -0
- package/dist/spec.d.ts +53 -0
- package/dist/spec.js +303 -0
- package/dist/validate.d.ts +21 -0
- package/dist/validate.js +83 -0
- package/package.json +53 -5
package/dist/schema.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
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
|
+
/** Every property a schema declares, at any depth, nearest first. */
|
|
222
|
+
export function properties(root) {
|
|
223
|
+
const out = [];
|
|
224
|
+
const seen = new Set();
|
|
225
|
+
let level = [root];
|
|
226
|
+
for (let depth = 0; level.length > 0; depth++) {
|
|
227
|
+
const next = [];
|
|
228
|
+
for (const raw of level) {
|
|
229
|
+
const node = resolve(raw, root);
|
|
230
|
+
if (node === undefined || seen.has(node)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
seen.add(node);
|
|
234
|
+
const required = new Set(Array.isArray(node.required)
|
|
235
|
+
? node.required.filter((n) => typeof n === 'string')
|
|
236
|
+
: []);
|
|
237
|
+
const props = node.properties;
|
|
238
|
+
if (isObject(props)) {
|
|
239
|
+
for (const [name, sub] of Object.entries(props)) {
|
|
240
|
+
const target = resolve(sub, root);
|
|
241
|
+
if (target === undefined) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
out.push({ name, schema: target, required: required.has(name), depth });
|
|
245
|
+
next.push(sub);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const key of ONE) {
|
|
249
|
+
next.push(node[key]);
|
|
250
|
+
}
|
|
251
|
+
for (const key of LIST) {
|
|
252
|
+
const value = node[key];
|
|
253
|
+
if (Array.isArray(value)) {
|
|
254
|
+
next.push(...value);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
for (const key of MAP) {
|
|
258
|
+
if (key === 'properties') {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const value = node[key];
|
|
262
|
+
if (isObject(value)) {
|
|
263
|
+
next.push(...Object.values(value));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const items = node.items;
|
|
267
|
+
next.push(...(Array.isArray(items) ? items : [items]));
|
|
268
|
+
}
|
|
269
|
+
level = next;
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
/** Every property name a schema mentions, at any depth. */
|
|
274
|
+
export const propertyNames = (root) => new Set(properties(root).map((p) => p.name));
|
|
275
|
+
/** A schema, with a local `#/$defs/...` pointer followed as far as it goes. */
|
|
276
|
+
function resolve(value, root) {
|
|
277
|
+
let at = value;
|
|
278
|
+
for (let hop = 0; hop < MAX_HOPS; hop++) {
|
|
279
|
+
if (!isObject(at)) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
const ref = at.$ref;
|
|
283
|
+
if (typeof ref !== 'string' || !ref.startsWith(DEFS)) {
|
|
284
|
+
return at;
|
|
285
|
+
}
|
|
286
|
+
const defs = root.$defs;
|
|
287
|
+
if (!isObject(defs)) {
|
|
288
|
+
return at;
|
|
289
|
+
}
|
|
290
|
+
at = defs[ref.slice(DEFS.length)];
|
|
291
|
+
}
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
const DEFS = '#/$defs/';
|
|
295
|
+
const MAX_HOPS = 8;
|
|
296
|
+
//# sourceMappingURL=schema.js.map
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Server } from 'node:http';
|
|
2
|
+
import { type Box } from './box.ts';
|
|
3
|
+
import { type Cache } from './cache.ts';
|
|
4
|
+
import { RegenerationLimiter, RequestHistory } from './logger.ts';
|
|
5
|
+
import { PaginationLimiter } from './paging.ts';
|
|
6
|
+
import type { Router } from './router.ts';
|
|
7
|
+
import { type Checks } from './validate.ts';
|
|
8
|
+
export interface ServerOptions {
|
|
9
|
+
router: Router;
|
|
10
|
+
cache: Cache;
|
|
11
|
+
checks: Checks;
|
|
12
|
+
box: Box;
|
|
13
|
+
/** fixed base seed — the same request then answers the same way */
|
|
14
|
+
seed?: number;
|
|
15
|
+
maxBody?: number;
|
|
16
|
+
onRequest?: (line: string) => void;
|
|
17
|
+
requestsDir?: string;
|
|
18
|
+
regenLimit?: number;
|
|
19
|
+
history?: RequestHistory;
|
|
20
|
+
limiter?: RegenerationLimiter;
|
|
21
|
+
maxPages?: number | (() => number);
|
|
22
|
+
paginationLimiter?: PaginationLimiter;
|
|
23
|
+
}
|
|
24
|
+
export interface Listening {
|
|
25
|
+
server: Server;
|
|
26
|
+
port: number;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare function build(opts: ServerOptions): Server;
|
|
30
|
+
export declare function listen(opts: ServerOptions, host: string, port: number): Promise<Listening>;
|
|
31
|
+
//# sourceMappingURL=server.d.ts.map
|