@yrest/cli 0.11.0 → 0.11.2
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/README.md +96 -24
- package/assets/svg/0..1.svg +3 -0
- package/assets/svg/0..n.svg +3 -0
- package/assets/svg/1..1.svg +3 -0
- package/assets/svg/1..n.svg +3 -0
- package/assets/svg/n.svg +3 -0
- package/dist/cli/index.js +226 -83
- package/dist/cli/index.mjs +226 -83
- package/dist/index.d.mts +69 -3
- package/dist/index.d.ts +69 -3
- package/dist/index.js +228 -78
- package/dist/index.mjs +228 -78
- package/package.json +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -10,21 +10,87 @@ type Resource = Record<string, unknown>;
|
|
|
10
10
|
* All properties are optional — omit what you don't need. Fields not mentioned
|
|
11
11
|
* in `_schema` are inferred from the collection data and treated as optional.
|
|
12
12
|
*
|
|
13
|
+
* In the YAML file every property uses the `_` prefix convention:
|
|
14
|
+
* `_type`, `_required`, `_format`, `_enum`, `_description`, `_default`, etc.
|
|
15
|
+
*
|
|
13
16
|
* Shorthand: `fieldName: required` normalises to `{ required: true }`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```yaml
|
|
20
|
+
* _schema:
|
|
21
|
+
* users:
|
|
22
|
+
* name:
|
|
23
|
+
* _type: string
|
|
24
|
+
* _required: true
|
|
25
|
+
* _minLength: 2
|
|
26
|
+
* _maxLength: 100
|
|
27
|
+
* email:
|
|
28
|
+
* _type: string
|
|
29
|
+
* _required: true
|
|
30
|
+
* _format: email
|
|
31
|
+
* age:
|
|
32
|
+
* _type: integer
|
|
33
|
+
* _minimum: 0
|
|
34
|
+
* _maximum: 120
|
|
35
|
+
* _nullable: true
|
|
36
|
+
* role:
|
|
37
|
+
* _type: string
|
|
38
|
+
* _enum: [admin, editor, viewer]
|
|
39
|
+
* _default: viewer
|
|
40
|
+
* tags:
|
|
41
|
+
* _type: array
|
|
42
|
+
* _items:
|
|
43
|
+
* _type: string
|
|
44
|
+
* _uniqueItems: true
|
|
45
|
+
* ```
|
|
14
46
|
*/
|
|
15
47
|
type FieldDef = {
|
|
16
|
-
/** If `true`, the field is listed in the OpenAPI `required` array
|
|
48
|
+
/** If `true`, the field is listed in the OpenAPI `required` array. */
|
|
17
49
|
required?: boolean;
|
|
18
50
|
/** Explicit type override. If absent, inferred from the data. */
|
|
19
51
|
type?: "string" | "integer" | "number" | "boolean" | "object" | "array";
|
|
20
|
-
/** OpenAPI `format` hint (e.g. `email`, `date`, `uuid`, `uri`, `date-time`). */
|
|
52
|
+
/** OpenAPI `format` hint (e.g. `email`, `date`, `uuid`, `uri`, `date-time`, `binary`). */
|
|
21
53
|
format?: string;
|
|
22
|
-
/** Restricts the field to a fixed set of values. */
|
|
54
|
+
/** Restricts the field to a fixed set of allowed values. */
|
|
23
55
|
enum?: unknown[];
|
|
24
56
|
/** Human-readable description included in the OpenAPI spec. */
|
|
25
57
|
description?: string;
|
|
26
58
|
/** Default value included in the OpenAPI spec. */
|
|
27
59
|
default?: unknown;
|
|
60
|
+
/** Example value included in the OpenAPI spec. */
|
|
61
|
+
example?: unknown;
|
|
62
|
+
/** If `true`, the field may be `null` in addition to its declared type. */
|
|
63
|
+
nullable?: boolean;
|
|
64
|
+
/** Minimum string length (inclusive). Applies when `type` is `string`. */
|
|
65
|
+
minLength?: number;
|
|
66
|
+
/** Maximum string length (inclusive). Applies when `type` is `string`. */
|
|
67
|
+
maxLength?: number;
|
|
68
|
+
/** Regex pattern the string value must match. Applies when `type` is `string`. */
|
|
69
|
+
pattern?: string;
|
|
70
|
+
/** Minimum numeric value (inclusive). Applies when `type` is `number` or `integer`. */
|
|
71
|
+
minimum?: number;
|
|
72
|
+
/** Maximum numeric value (inclusive). Applies when `type` is `number` or `integer`. */
|
|
73
|
+
maximum?: number;
|
|
74
|
+
/** Exclusive lower bound. Applies when `type` is `number` or `integer`. */
|
|
75
|
+
exclusiveMinimum?: number;
|
|
76
|
+
/** Exclusive upper bound. Applies when `type` is `number` or `integer`. */
|
|
77
|
+
exclusiveMaximum?: number;
|
|
78
|
+
/** Value must be a multiple of this number. */
|
|
79
|
+
multipleOf?: number;
|
|
80
|
+
/** Schema descriptor for the items of an array field. */
|
|
81
|
+
items?: Pick<FieldDef, "type" | "format" | "enum">;
|
|
82
|
+
/** Minimum number of items in the array. */
|
|
83
|
+
minItems?: number;
|
|
84
|
+
/** Maximum number of items in the array. */
|
|
85
|
+
maxItems?: number;
|
|
86
|
+
/** If `true`, all array items must be unique. */
|
|
87
|
+
uniqueItems?: boolean;
|
|
88
|
+
/** If `true`, marks the field as deprecated in the OpenAPI spec. */
|
|
89
|
+
deprecated?: boolean;
|
|
90
|
+
/** If `true`, the field is only returned in responses (excluded from request bodies). */
|
|
91
|
+
readOnly?: boolean;
|
|
92
|
+
/** If `true`, the field is only accepted in request bodies (excluded from responses). */
|
|
93
|
+
writeOnly?: boolean;
|
|
28
94
|
};
|
|
29
95
|
/**
|
|
30
96
|
* Field-level schema declarations for one or more collections, parsed from `_schema` in the YAML.
|
package/dist/index.js
CHANGED
|
@@ -174,18 +174,48 @@ function normaliseFieldDef(value) {
|
|
|
174
174
|
const v = value;
|
|
175
175
|
const def = {};
|
|
176
176
|
if (v["_required"] === true || v["_required"] === false) def.required = v["_required"];
|
|
177
|
-
if (typeof v["_type"] === "string" &&
|
|
177
|
+
if (typeof v["_type"] === "string" && VALID_TYPES.has(v["_type"]))
|
|
178
178
|
def.type = v["_type"];
|
|
179
179
|
if (typeof v["_format"] === "string") def.format = v["_format"];
|
|
180
180
|
if (Array.isArray(v["_enum"])) def.enum = v["_enum"];
|
|
181
181
|
if (typeof v["_description"] === "string") def.description = v["_description"];
|
|
182
182
|
if (v["_default"] !== void 0) def.default = v["_default"];
|
|
183
|
+
if (v["_example"] !== void 0) def.example = v["_example"];
|
|
184
|
+
if (v["_nullable"] === true || v["_nullable"] === false) def.nullable = v["_nullable"];
|
|
185
|
+
if (typeof v["_minLength"] === "number") def.minLength = v["_minLength"];
|
|
186
|
+
if (typeof v["_maxLength"] === "number") def.maxLength = v["_maxLength"];
|
|
187
|
+
if (typeof v["_pattern"] === "string") def.pattern = v["_pattern"];
|
|
188
|
+
if (typeof v["_minimum"] === "number") def.minimum = v["_minimum"];
|
|
189
|
+
if (typeof v["_maximum"] === "number") def.maximum = v["_maximum"];
|
|
190
|
+
if (typeof v["_exclusiveMinimum"] === "number") def.exclusiveMinimum = v["_exclusiveMinimum"];
|
|
191
|
+
if (typeof v["_exclusiveMaximum"] === "number") def.exclusiveMaximum = v["_exclusiveMaximum"];
|
|
192
|
+
if (typeof v["_multipleOf"] === "number") def.multipleOf = v["_multipleOf"];
|
|
193
|
+
if (typeof v["_minItems"] === "number") def.minItems = v["_minItems"];
|
|
194
|
+
if (typeof v["_maxItems"] === "number") def.maxItems = v["_maxItems"];
|
|
195
|
+
if (v["_uniqueItems"] === true || v["_uniqueItems"] === false)
|
|
196
|
+
def.uniqueItems = v["_uniqueItems"];
|
|
197
|
+
if (v["_items"] && typeof v["_items"] === "object" && !Array.isArray(v["_items"])) {
|
|
198
|
+
const items = v["_items"];
|
|
199
|
+
const itemsDef = {};
|
|
200
|
+
if (typeof items["_type"] === "string" && VALID_TYPES.has(items["_type"]))
|
|
201
|
+
itemsDef.type = items["_type"];
|
|
202
|
+
if (typeof items["_format"] === "string") itemsDef.format = items["_format"];
|
|
203
|
+
if (Array.isArray(items["_enum"])) itemsDef.enum = items["_enum"];
|
|
204
|
+
def.items = itemsDef;
|
|
205
|
+
}
|
|
206
|
+
if (v["_deprecated"] === true || v["_deprecated"] === false)
|
|
207
|
+
def.deprecated = v["_deprecated"];
|
|
208
|
+
if (v["_readOnly"] === true || v["_readOnly"] === false) def.readOnly = v["_readOnly"];
|
|
209
|
+
if (v["_writeOnly"] === true || v["_writeOnly"] === false)
|
|
210
|
+
def.writeOnly = v["_writeOnly"];
|
|
183
211
|
return def;
|
|
184
212
|
}
|
|
213
|
+
var VALID_TYPES;
|
|
185
214
|
var init_parseSchema = __esm({
|
|
186
215
|
"src/storage/parseSchema.ts"() {
|
|
187
216
|
"use strict";
|
|
188
217
|
init_cjs_shims();
|
|
218
|
+
VALID_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean", "object", "array"]);
|
|
189
219
|
}
|
|
190
220
|
});
|
|
191
221
|
|
|
@@ -256,6 +286,30 @@ var init_parseRoutes = __esm({
|
|
|
256
286
|
}
|
|
257
287
|
});
|
|
258
288
|
|
|
289
|
+
// src/storage/parseData.ts
|
|
290
|
+
function parseData(raw) {
|
|
291
|
+
const block = raw["_data"];
|
|
292
|
+
const fromBlock = block && typeof block === "object" && !Array.isArray(block) ? Object.fromEntries(
|
|
293
|
+
Object.entries(block).filter(([, v]) => Array.isArray(v))
|
|
294
|
+
) : {};
|
|
295
|
+
const fromRoot = Object.fromEntries(
|
|
296
|
+
Object.entries(raw).filter(([key, val]) => !RESERVED.has(key) && Array.isArray(val))
|
|
297
|
+
);
|
|
298
|
+
return { ...fromBlock, ...fromRoot };
|
|
299
|
+
}
|
|
300
|
+
function hasDataBlock(raw) {
|
|
301
|
+
const d = raw["_data"];
|
|
302
|
+
return d !== null && d !== void 0 && typeof d === "object" && !Array.isArray(d);
|
|
303
|
+
}
|
|
304
|
+
var RESERVED;
|
|
305
|
+
var init_parseData = __esm({
|
|
306
|
+
"src/storage/parseData.ts"() {
|
|
307
|
+
"use strict";
|
|
308
|
+
init_cjs_shims();
|
|
309
|
+
RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema", "_data"]);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
259
313
|
// src/storage/yrestStorage.ts
|
|
260
314
|
var yrestStorage_exports = {};
|
|
261
315
|
__export(yrestStorage_exports, {
|
|
@@ -318,13 +372,11 @@ function serializeScenario(s) {
|
|
|
318
372
|
function createYrestStorage(filePath) {
|
|
319
373
|
const absPath = (0, import_node_path2.resolve)(filePath);
|
|
320
374
|
const raw = (0, import_yaml3.parse)((0, import_node_fs3.readFileSync)(absPath, "utf8")) ?? {};
|
|
321
|
-
const RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
322
375
|
const relations = parseRelations(raw["_rel"]);
|
|
323
376
|
const routes = parseRoutes(raw["_routes"]);
|
|
324
377
|
const schema = parseSchema(raw["_schema"]);
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
);
|
|
378
|
+
const dataBlock = hasDataBlock(raw);
|
|
379
|
+
const data = parseData(raw);
|
|
328
380
|
let snapshot = {
|
|
329
381
|
data: deepCopyData(data),
|
|
330
382
|
relations: { ...relations },
|
|
@@ -353,7 +405,11 @@ function createYrestStorage(filePath) {
|
|
|
353
405
|
const payload = {};
|
|
354
406
|
if (Object.keys(relations).length > 0) payload._rel = serializeRelations(relations);
|
|
355
407
|
if (routes.length > 0) payload._routes = serializeRoutes(routes);
|
|
356
|
-
|
|
408
|
+
if (dataBlock) {
|
|
409
|
+
payload._data = data;
|
|
410
|
+
} else {
|
|
411
|
+
Object.assign(payload, data);
|
|
412
|
+
}
|
|
357
413
|
const tmp = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(absPath), `.yrest-${(0, import_node_crypto2.randomUUID)()}.tmp`);
|
|
358
414
|
(0, import_node_fs3.writeFileSync)(tmp, (0, import_yaml3.stringify)(payload), "utf8");
|
|
359
415
|
(0, import_node_fs3.renameSync)(tmp, absPath);
|
|
@@ -361,9 +417,7 @@ function createYrestStorage(filePath) {
|
|
|
361
417
|
reload() {
|
|
362
418
|
const fresh = (0, import_yaml3.parse)((0, import_node_fs3.readFileSync)(absPath, "utf8")) ?? {};
|
|
363
419
|
const freshRelations = parseRelations(fresh["_rel"]);
|
|
364
|
-
const freshData =
|
|
365
|
-
Object.entries(fresh).filter(([key]) => !RESERVED.has(key))
|
|
366
|
-
);
|
|
420
|
+
const freshData = parseData(fresh);
|
|
367
421
|
for (const key of Object.keys(data)) delete data[key];
|
|
368
422
|
Object.assign(data, freshData);
|
|
369
423
|
for (const key of Object.keys(relations)) delete relations[key];
|
|
@@ -401,6 +455,7 @@ var init_yrestStorage = __esm({
|
|
|
401
455
|
init_parseRelations();
|
|
402
456
|
init_parseRoutes();
|
|
403
457
|
init_parseSchema();
|
|
458
|
+
init_parseData();
|
|
404
459
|
init_deepCopy();
|
|
405
460
|
}
|
|
406
461
|
});
|
|
@@ -867,9 +922,15 @@ function generateERData(storage) {
|
|
|
867
922
|
const relations = storage.getRelations();
|
|
868
923
|
const schema = storage.getSchema();
|
|
869
924
|
const pivotTables = /* @__PURE__ */ new Set();
|
|
925
|
+
const pivotFKs = /* @__PURE__ */ new Map();
|
|
870
926
|
for (const fields of Object.values(relations)) {
|
|
871
927
|
for (const def of Object.values(fields)) {
|
|
872
|
-
if (def.type === "many2many")
|
|
928
|
+
if (def.type === "many2many") {
|
|
929
|
+
pivotTables.add(def.through);
|
|
930
|
+
if (!pivotFKs.has(def.through)) pivotFKs.set(def.through, /* @__PURE__ */ new Set());
|
|
931
|
+
pivotFKs.get(def.through).add(def.foreignKey);
|
|
932
|
+
pivotFKs.get(def.through).add(def.otherKey);
|
|
933
|
+
}
|
|
873
934
|
}
|
|
874
935
|
}
|
|
875
936
|
const fkFields = /* @__PURE__ */ new Map();
|
|
@@ -905,6 +966,9 @@ function generateERData(storage) {
|
|
|
905
966
|
for (const [source, fields] of Object.entries(relations)) {
|
|
906
967
|
for (const [key, def] of Object.entries(fields)) {
|
|
907
968
|
if (def.type === "many2many") {
|
|
969
|
+
const throughRels = relations[def.through] ?? {};
|
|
970
|
+
const fkRel = throughRels[def.foreignKey];
|
|
971
|
+
const otherRel = throughRels[def.otherKey];
|
|
908
972
|
erRelations.push({
|
|
909
973
|
source,
|
|
910
974
|
target: def.target,
|
|
@@ -912,9 +976,14 @@ function generateERData(storage) {
|
|
|
912
976
|
label: def.through,
|
|
913
977
|
through: def.through,
|
|
914
978
|
foreignKey: def.foreignKey,
|
|
915
|
-
otherKey: def.otherKey
|
|
979
|
+
otherKey: def.otherKey,
|
|
980
|
+
fkCarDirect: fkRel?.carDirect,
|
|
981
|
+
fkCarInverse: fkRel?.carInverse,
|
|
982
|
+
otherCarDirect: otherRel?.carDirect,
|
|
983
|
+
otherCarInverse: otherRel?.carInverse
|
|
916
984
|
});
|
|
917
985
|
} else {
|
|
986
|
+
if (pivotFKs.get(source)?.has(key)) continue;
|
|
918
987
|
erRelations.push({
|
|
919
988
|
source,
|
|
920
989
|
target: def.target,
|
|
@@ -941,7 +1010,15 @@ function erDiagramSection(data) {
|
|
|
941
1010
|
|
|
942
1011
|
<div class="card" style="padding:0;overflow:hidden;position:relative;">
|
|
943
1012
|
<canvas id="er-canvas" style="width:100%;display:block;cursor:grab;"></canvas>
|
|
944
|
-
<
|
|
1013
|
+
<div style="position:absolute;top:10px;right:14px;display:flex;gap:6px;align-items:center;">
|
|
1014
|
+
<select id="er-scale" title="Download quality" style="background:#161b22;border:1px solid #30363d;color:#7d8590;padding:3px 6px;border-radius:6px;font-size:11px;cursor:pointer;">
|
|
1015
|
+
<option value="1">1\xD7</option>
|
|
1016
|
+
<option value="2">2\xD7</option>
|
|
1017
|
+
<option value="3" selected>3\xD7</option>
|
|
1018
|
+
<option value="4">4\xD7</option>
|
|
1019
|
+
</select>
|
|
1020
|
+
<button id="er-dl" title="Download PNG" style="background:#161b22;border:1px solid #30363d;color:#7d8590;padding:4px 10px;border-radius:6px;font-size:11px;cursor:pointer;line-height:1.4;">\u2193 PNG</button>
|
|
1021
|
+
</div>
|
|
945
1022
|
</div>
|
|
946
1023
|
|
|
947
1024
|
<script>${/*js*/
|
|
@@ -951,7 +1028,7 @@ function erDiagramSection(data) {
|
|
|
951
1028
|
var D=${json};
|
|
952
1029
|
|
|
953
1030
|
// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
954
|
-
var NW=178,HH=34,FH=22,FP=10,ROW_GAP=44,COL_GAP=100,BR=
|
|
1031
|
+
var NW=178,HH=34,FH=22,FP=10,ROW_GAP=44,COL_GAP=100,BR=12,BO=16,LGH=84,MIN_GAP=10;
|
|
955
1032
|
|
|
956
1033
|
var C={bg:'#0d1117',nb:'#161b22',nh:'#1c2128',pb:'#0d1117',ph:'#161b22',
|
|
957
1034
|
bo:'#30363d',bs:'#58a6ff',tx:'#e6edf3',mu:'#7d8590',
|
|
@@ -991,6 +1068,30 @@ function erDiagramSection(data) {
|
|
|
991
1068
|
}
|
|
992
1069
|
});
|
|
993
1070
|
|
|
1071
|
+
// \u2500\u2500 Dynamic node width \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1072
|
+
function computeNodeWidths(){
|
|
1073
|
+
nodes.forEach(function(n){
|
|
1074
|
+
var e=n.e;
|
|
1075
|
+
ctx.font='600 12px -apple-system,BlinkMacSystemFont,sans-serif';
|
|
1076
|
+
var hdrW=FP+ctx.measureText(e.name).width+FP;
|
|
1077
|
+
if(e.isPivot){
|
|
1078
|
+
ctx.font='9px monospace';
|
|
1079
|
+
hdrW+=ctx.measureText('pivot').width+FP;
|
|
1080
|
+
}
|
|
1081
|
+
var maxRow=hdrW;
|
|
1082
|
+
e.fields.forEach(function(f){
|
|
1083
|
+
var leftPad=FP+(f.pk||f.fk?16:0);
|
|
1084
|
+
var rightPad=(f.required&&!f.pk?14:0)+FP;
|
|
1085
|
+
ctx.font=(f.required&&!f.pk?'600 ':'')+'11px monospace';
|
|
1086
|
+
var nw=ctx.measureText(f.name).width;
|
|
1087
|
+
ctx.font='10px monospace';
|
|
1088
|
+
var tw=ctx.measureText(f.type).width;
|
|
1089
|
+
maxRow=Math.max(maxRow,leftPad+nw+MIN_GAP+tw+rightPad);
|
|
1090
|
+
});
|
|
1091
|
+
n.w=Math.max(NW,Math.ceil(maxRow));
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
994
1095
|
// \u2500\u2500 Layered layout (simplified Sugiyama) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
995
1096
|
var groups=[];
|
|
996
1097
|
|
|
@@ -1029,7 +1130,8 @@ function erDiagramSection(data) {
|
|
|
1029
1130
|
median(adj[b].map(function(x){return np[x]!=null?np[x]:0;}));
|
|
1030
1131
|
});
|
|
1031
1132
|
}
|
|
1032
|
-
var
|
|
1133
|
+
var maxNW=nodes.reduce(function(m,n){return Math.max(m,n.w);},NW);
|
|
1134
|
+
var colW=maxNW+COL_GAP;
|
|
1033
1135
|
var totalW=(maxL+1)*colW-COL_GAP;
|
|
1034
1136
|
var startX=Math.max(20,(W-totalW)/2);
|
|
1035
1137
|
for(var l=0;l<=maxL;l++){
|
|
@@ -1077,37 +1179,37 @@ function erDiagramSection(data) {
|
|
|
1077
1179
|
|
|
1078
1180
|
var badges=[];
|
|
1079
1181
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1182
|
+
// \u2500\u2500 SVG cardinality badges via Path2D \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1183
|
+
// SVG orientation: LEFT side touches the node, RIGHT side connects to the line.
|
|
1184
|
+
// Rotation = Math.atan2(uy,ux) aligns local +x with the outward (away-from-node) direction.
|
|
1185
|
+
var P2D={
|
|
1186
|
+
'1..1':new Path2D('M75 40C75 59.33 59.33 75 40 75C20.67 75 5 59.33 5 40C5 20.67 20.67 5 40 5C59.33 5 75 20.67 75 40ZM41 26C41 24.3431 39.6569 23 38 23C36.3431 23 35 24.3431 35 26V37H26V26C26 24.3431 24.6569 23 23 23C21.3431 23 20 24.3431 20 26V37H15C13.3431 37 12 38.3431 12 40C12 41.6569 13.3431 43 15 43H20V54C20 55.6569 21.3431 57 23 57C24.6569 57 26 55.6569 26 54V43H35V54C35 55.6569 36.3431 57 38 57C39.6569 57 41 55.6569 41 54V43H66C67.6569 43 69 41.6569 69 40C69 38.3431 67.6569 37 66 37H41V26Z'),
|
|
1187
|
+
'1..n':new Path2D('M40 75C59.33 75 75 59.33 75 40C75 20.67 59.33 5 40 5C20.67 5 5 20.67 5 40C5 59.33 20.67 75 40 75ZM44.9862 42.9025L18.6801 58.0904C17.2452 58.9188 15.4105 58.4272 14.582 56.9923C13.7536 55.5574 14.2452 53.7227 15.6801 52.8942L32.9862 42.9025H17C15.3431 42.9025 14 41.5594 14 39.9025C14 38.2457 15.3431 36.9025 17 36.9025H32.9862L15.5981 26.5986C14.1632 25.7702 13.6716 23.9354 14.5 22.5005C15.3284 21.0656 17.1632 20.574 18.5981 21.4024L44.9862 36.9025H46V26C46 24.3431 47.3431 23 49 23C50.6569 23 52 24.3431 52 26V36.9025H68C69.6569 36.9025 71 38.2457 71 39.9025C71 41.5594 69.6569 42.9025 68 42.9025H52V53C52 54.6569 50.6569 56 49 56C47.3431 56 46 54.6569 46 53V42.9025H44.9862Z'),
|
|
1188
|
+
'0..1':new Path2D('M75 40C75 59.33 59.33 75 40 75C20.67 75 5 59.33 5 40C5 20.67 20.67 5 40 5C59.33 5 75 20.67 75 40ZM23 57C24.6569 57 26 55.6569 26 54V43H33.3781C34.7102 48.1757 39.4085 52 45 52C50.5915 52 55.2898 48.1757 56.6219 43H66C67.6569 43 69 41.6569 69 40C69 38.3431 67.6569 37 66 37H56.6219C55.2898 31.8243 50.5915 28 45 28C39.4085 28 34.7102 31.8243 33.3781 37H26V26C26 24.3431 24.6569 23 23 23C21.3431 23 20 24.3431 20 26V37H15C13.3431 37 12 38.3431 12 40C12 41.6569 13.3431 43 15 43H20V54C20 55.6569 21.3431 57 23 57ZM51 40C51 41.0929 50.7078 42.1175 50.1973 43C49.1599 44.7934 47.2208 46 45 46C42.7792 46 40.8401 44.7934 39.8027 43C39.2922 42.1175 39 41.0929 39 40C39 38.9071 39.2922 37.8825 39.8027 37C40.8401 35.2066 42.7792 34 45 34C47.2208 34 49.1599 35.2066 50.1973 37C50.7078 37.8825 51 38.9071 51 40Z'),
|
|
1189
|
+
'0..n':new Path2D('M40 75C59.33 75 75 59.33 75 40C75 20.67 59.33 5 40 5C20.67 5 5 20.67 5 40C5 59.33 20.67 75 40 75ZM17 42.9025C15.3431 42.9025 14 41.5594 14 39.9025C14 38.2457 15.3431 36.9025 17 36.9025H32.9862L15.5981 26.5986C14.1632 25.7702 13.6716 23.9354 14.5 22.5005C15.3284 21.0656 17.1632 20.574 18.5981 21.4024L45.4091 36.8818L45.4036 36.9025H45.445L45.4091 36.8818C46.7819 31.7662 51.4511 28 57 28C62.579 28 67.2687 31.8072 68.6129 36.9652C69.9758 37.2481 71 38.4557 71 39.9025C71 41.331 70.0017 42.5262 68.6647 42.8286C67.3931 48.091 62.6531 52 57 52C51.3734 52 46.6513 48.1276 45.3534 42.9025H44.9862L18.6801 58.0904C17.2452 58.9188 15.4105 58.4272 14.582 56.9923C13.7536 55.5574 14.2452 53.7227 15.6801 52.8942L32.9862 42.9025H17ZM62.1397 36.9025C61.0891 35.163 59.1804 34 57 34C54.8196 34 52.9109 35.163 51.8603 36.9025C51.3142 37.8067 51 38.8667 51 40C51 41.0529 51.2712 42.0424 51.7475 42.9025C52.7704 44.7496 54.7392 46 57 46C59.2608 46 61.2296 44.7496 62.2525 42.9025C62.7288 42.0424 63 41.0529 63 40C63 38.8667 62.6858 37.8067 62.1397 36.9025Z'),
|
|
1190
|
+
'n':new Path2D('M40 75C59.33 75 75 59.33 75 40C75 20.67 59.33 5 40 5C20.67 5 5 20.67 5 40C5 59.33 20.67 75 40 75ZM17 36.9025H33.0677L15.5981 26.5986C14.1632 25.7702 13.6716 23.9354 14.5 22.5005C15.3284 21.0656 17.1632 20.574 18.5981 21.4024L44.9862 36.9025H68C69.6569 36.9025 71 38.2457 71 39.9025C71 41.5594 69.6569 42.9025 68 42.9025H44.9862L18.6801 58.0904C17.2452 58.9188 15.4105 58.4272 14.582 56.9923C13.7536 55.5574 14.2452 53.7227 15.6801 52.8942L32.9862 42.9025H17C15.3431 42.9025 14 41.5594 14 39.9025C14 38.2457 15.3431 36.9025 17 36.9025Z')
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
function pushBadge(x,y,ux,uy,car,col){
|
|
1194
|
+
badges.push({x:x,y:y,ux:ux,uy:uy,car:car,col:col});
|
|
1082
1195
|
}
|
|
1083
1196
|
|
|
1084
|
-
// Badge: solid colored circle; crow's foot (many) or double-bar (one) in dark ink inside.
|
|
1085
|
-
// ux,uy points outward from the node \u2014 badge is offset BO px into the gap.
|
|
1086
|
-
// Crow's foot tines open toward the node (-x in rotated frame), tip toward line (+x).
|
|
1087
1197
|
function drawBadge(b){
|
|
1088
|
-
var
|
|
1089
|
-
ctx.
|
|
1090
|
-
ctx.
|
|
1091
|
-
ctx.
|
|
1092
|
-
ctx.
|
|
1093
|
-
ctx.
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
ctx.moveTo(-5,4);ctx.lineTo(5,0); // lower tine \u2192 tip
|
|
1097
|
-
ctx.moveTo(-5,0);ctx.lineTo(5,0); // center line
|
|
1098
|
-
ctx.moveTo(-5,-4);ctx.lineTo(-5,4); // base bar (near node)
|
|
1099
|
-
}else{
|
|
1100
|
-
ctx.moveTo(-3,-5);ctx.lineTo(-3,5); // near bar
|
|
1101
|
-
ctx.moveTo(2,-5);ctx.lineTo(2,5); // far bar
|
|
1102
|
-
}
|
|
1103
|
-
ctx.stroke();
|
|
1198
|
+
var p=P2D[b.car]||P2D['n'],sc=BR/40;
|
|
1199
|
+
ctx.save();
|
|
1200
|
+
ctx.translate(b.x+b.ux*BO,b.y+b.uy*BO);
|
|
1201
|
+
ctx.rotate(Math.atan2(b.uy,b.ux));
|
|
1202
|
+
ctx.scale(sc,sc);
|
|
1203
|
+
ctx.translate(-40,-40);
|
|
1204
|
+
ctx.fillStyle=b.col;
|
|
1205
|
+
ctx.fill(p,'evenodd');
|
|
1104
1206
|
ctx.restore();
|
|
1105
1207
|
}
|
|
1106
1208
|
|
|
1107
1209
|
function ecol(t){return t==='one2one'?C.o2o:t==='many2many'?C.m2m:C.m2o;}
|
|
1108
1210
|
|
|
1109
1211
|
// offset: pixels to shift perpendicular to the edge direction (for parallel edges)
|
|
1110
|
-
function drawEdgeLine(a,b,col,dashed,label,
|
|
1212
|
+
function drawEdgeLine(a,b,col,dashed,label,carA,carB,off){
|
|
1111
1213
|
var p1=edgePt(a,b.x+b.w/2,b.y+b.h/2);
|
|
1112
1214
|
var p2=edgePt(b,a.x+a.w/2,a.y+a.h/2);
|
|
1113
1215
|
var dx=p2.x-p1.x,dy=p2.y-p1.y,d=Math.sqrt(dx*dx+dy*dy)||1;
|
|
@@ -1118,13 +1220,19 @@ function erDiagramSection(data) {
|
|
|
1118
1220
|
ctx.setLineDash(dashed?[5,4]:[]);
|
|
1119
1221
|
ctx.beginPath();ctx.moveTo(q1x,q1y);ctx.lineTo(q2x,q2y);ctx.stroke();
|
|
1120
1222
|
ctx.setLineDash([]);
|
|
1121
|
-
pushBadge(q1x,q1y,ux,uy,
|
|
1122
|
-
pushBadge(q2x,q2y,-ux,-uy,
|
|
1223
|
+
pushBadge(q1x,q1y,ux,uy,carA,col);
|
|
1224
|
+
pushBadge(q2x,q2y,-ux,-uy,carB,col);
|
|
1123
1225
|
if(label){
|
|
1124
1226
|
var mx=(q1x+q2x)/2,my=(q1y+q2y)/2;
|
|
1227
|
+
var la=Math.atan2(uy,ux);
|
|
1228
|
+
if(la>Math.PI/2||la<-Math.PI/2)la+=Math.PI; // keep text readable
|
|
1229
|
+
var ls=off>0?11:-11; // alternate above/below for parallel edges
|
|
1230
|
+
ctx.save();
|
|
1231
|
+
ctx.translate(mx,my);
|
|
1232
|
+
ctx.rotate(la);
|
|
1125
1233
|
ctx.fillStyle=C.mu;ctx.font='10px monospace';ctx.textAlign='center';ctx.textBaseline='middle';
|
|
1126
|
-
ctx.fillText(label,
|
|
1127
|
-
ctx.
|
|
1234
|
+
ctx.fillText(label,0,ls);
|
|
1235
|
+
ctx.restore();
|
|
1128
1236
|
}
|
|
1129
1237
|
}
|
|
1130
1238
|
|
|
@@ -1140,14 +1248,14 @@ function erDiagramSection(data) {
|
|
|
1140
1248
|
var col=ecol(r.type);
|
|
1141
1249
|
if(r.type==='many2many'&&r.through&&nIdx[r.through]!=null){
|
|
1142
1250
|
var sn=nodes[nIdx[r.source]],pn=nodes[nIdx[r.through]],tn=nodes[nIdx[r.target]];
|
|
1143
|
-
if(sn&&pn)edges.push({a:sn,b:pn,col:col,dashed:true,label:r.foreignKey||'',
|
|
1144
|
-
if(tn&&pn)edges.push({a:tn,b:pn,col:col,dashed:true,label:r.otherKey||'',
|
|
1251
|
+
if(sn&&pn)edges.push({a:sn,b:pn,col:col,dashed:true,label:r.foreignKey||'',carA:r.fkCarDirect||'1..1',carB:r.fkCarInverse||'0..n',off:0});
|
|
1252
|
+
if(tn&&pn)edges.push({a:tn,b:pn,col:col,dashed:true,label:r.otherKey||'',carA:r.otherCarDirect||'1..1',carB:r.otherCarInverse||'0..n',off:0});
|
|
1145
1253
|
}else{
|
|
1146
1254
|
var sn=nodes[nIdx[r.source]],tn=nodes[nIdx[r.target]];
|
|
1147
1255
|
if(sn&&tn&&sn!==tn){
|
|
1148
|
-
var
|
|
1149
|
-
var
|
|
1150
|
-
edges.push({a:sn,b:tn,col:col,dashed:false,label:r.label,
|
|
1256
|
+
var carA=r.carInverse||(r.type!=='one2one'?'1..n':'1..1');
|
|
1257
|
+
var carB=r.carDirect||(r.type==='many2many'?'1..n':'1..1');
|
|
1258
|
+
edges.push({a:sn,b:tn,col:col,dashed:false,label:r.label,carA:carA,carB:carB,off:0});
|
|
1151
1259
|
}
|
|
1152
1260
|
}
|
|
1153
1261
|
});
|
|
@@ -1206,12 +1314,24 @@ function erDiagramSection(data) {
|
|
|
1206
1314
|
|
|
1207
1315
|
function drawLegend(){
|
|
1208
1316
|
var cvW=cv.width/dpr,cvH=cv.height/dpr;
|
|
1209
|
-
var
|
|
1317
|
+
var yL=cvH-LGH,y1=yL+13,y2=yL+35,y3=yL+62;
|
|
1210
1318
|
|
|
1211
1319
|
ctx.fillStyle='#161b22';
|
|
1212
|
-
ctx.fillRect(0,
|
|
1320
|
+
ctx.fillRect(0,yL,cvW,LGH);
|
|
1213
1321
|
ctx.strokeStyle='#30363d';ctx.lineWidth=1;
|
|
1214
|
-
ctx.beginPath();ctx.moveTo(0,
|
|
1322
|
+
ctx.beginPath();ctx.moveTo(0,yL);ctx.lineTo(cvW,yL);ctx.stroke();
|
|
1323
|
+
|
|
1324
|
+
function legendBadge(cx,cy,car,col,angle){
|
|
1325
|
+
var p=P2D[car]||P2D['n'],sc=8/40;
|
|
1326
|
+
ctx.save();
|
|
1327
|
+
ctx.translate(cx,cy);
|
|
1328
|
+
ctx.rotate(angle);
|
|
1329
|
+
ctx.scale(sc,sc);
|
|
1330
|
+
ctx.translate(-40,-40);
|
|
1331
|
+
ctx.fillStyle=col;
|
|
1332
|
+
ctx.fill(p,'evenodd');
|
|
1333
|
+
ctx.restore();
|
|
1334
|
+
}
|
|
1215
1335
|
|
|
1216
1336
|
// Row 1: relation line types
|
|
1217
1337
|
var types=[
|
|
@@ -1230,42 +1350,43 @@ function erDiagramSection(data) {
|
|
|
1230
1350
|
x+=18+ctx.measureText(it.l).width+22;
|
|
1231
1351
|
});
|
|
1232
1352
|
|
|
1233
|
-
// Row 2:
|
|
1234
|
-
function badge2(cx,cy,many,col){
|
|
1235
|
-
ctx.fillStyle=col;ctx.strokeStyle=col;ctx.lineWidth=1;
|
|
1236
|
-
ctx.beginPath();ctx.arc(cx,cy,6,0,Math.PI*2);ctx.fill();
|
|
1237
|
-
ctx.save();ctx.translate(cx,cy);
|
|
1238
|
-
ctx.strokeStyle=C.bg;ctx.lineWidth=1.2;ctx.lineCap='round';
|
|
1239
|
-
ctx.beginPath();
|
|
1240
|
-
if(many){
|
|
1241
|
-
ctx.moveTo(-4,-3);ctx.lineTo(4,0);ctx.moveTo(-4,3);ctx.lineTo(4,0);
|
|
1242
|
-
ctx.moveTo(-4,0);ctx.lineTo(4,0);ctx.moveTo(-4,-3);ctx.lineTo(-4,3);
|
|
1243
|
-
}else{
|
|
1244
|
-
ctx.moveTo(-2,-3);ctx.lineTo(-2,3);ctx.moveTo(2,-3);ctx.lineTo(2,3);
|
|
1245
|
-
}
|
|
1246
|
-
ctx.stroke();ctx.restore();
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
ctx.font='10px monospace';ctx.textAlign='left';ctx.textBaseline='middle';
|
|
1353
|
+
// Row 2: relation type examples (pair of badges + line)
|
|
1250
1354
|
x=14;
|
|
1251
1355
|
var pairs=[
|
|
1252
|
-
{
|
|
1253
|
-
{
|
|
1254
|
-
{
|
|
1356
|
+
{carA:'1..n',carB:'1..1',col:C.m2o,l:'many2one (N:1)'},
|
|
1357
|
+
{carA:'1..1',carB:'1..1',col:C.o2o,l:'one2one (1:1)'},
|
|
1358
|
+
{carA:'1..1',carB:'0..n',col:C.m2m,l:'pivot edge (1:0..N)'}
|
|
1255
1359
|
];
|
|
1256
1360
|
pairs.forEach(function(p){
|
|
1257
1361
|
ctx.strokeStyle=p.col+'55';ctx.lineWidth=1.2;ctx.setLineDash(p.col===C.m2m?[4,3]:[]);
|
|
1258
1362
|
ctx.beginPath();ctx.moveTo(x+8,y2);ctx.lineTo(x+56,y2);ctx.stroke();
|
|
1259
1363
|
ctx.setLineDash([]);
|
|
1260
|
-
|
|
1261
|
-
|
|
1364
|
+
legendBadge(x+8,y2,p.carA,p.col,0);
|
|
1365
|
+
legendBadge(x+56,y2,p.carB,p.col,Math.PI);
|
|
1262
1366
|
ctx.fillStyle=C.mu;ctx.font='10px monospace';ctx.textAlign='left';ctx.textBaseline='middle';
|
|
1263
1367
|
ctx.fillText(p.l,x+66,y2);
|
|
1264
1368
|
x+=66+ctx.measureText(p.l).width+24;
|
|
1265
1369
|
});
|
|
1266
1370
|
|
|
1371
|
+
// Row 3: individual symbol meanings
|
|
1372
|
+
var symbols=[
|
|
1373
|
+
{car:'1..1',l:'exactly one'},
|
|
1374
|
+
{car:'0..1',l:'zero or one'},
|
|
1375
|
+
{car:'1..n',l:'one or more'},
|
|
1376
|
+
{car:'0..n',l:'zero or more'},
|
|
1377
|
+
{car:'n', l:'many (pivot)'}
|
|
1378
|
+
];
|
|
1379
|
+
x=14;
|
|
1380
|
+
ctx.font='10px monospace';ctx.textAlign='left';ctx.textBaseline='middle';
|
|
1381
|
+
symbols.forEach(function(s){
|
|
1382
|
+
legendBadge(x+8,y3,s.car,C.mu,0);
|
|
1383
|
+
ctx.fillStyle=C.mu;
|
|
1384
|
+
ctx.fillText(s.l,x+20,y3);
|
|
1385
|
+
x+=20+ctx.measureText(s.l).width+18;
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1267
1388
|
ctx.fillStyle='#3d444d';ctx.font='10px monospace';ctx.textAlign='right';ctx.textBaseline='middle';
|
|
1268
|
-
ctx.fillText('drag \xB7 scroll to zoom \xB7 dbl-click to reset',cvW-14,
|
|
1389
|
+
ctx.fillText('drag \xB7 scroll to zoom \xB7 dbl-click to reset',cvW-14,y1);
|
|
1269
1390
|
ctx.textBaseline='alphabetic';
|
|
1270
1391
|
}
|
|
1271
1392
|
|
|
@@ -1277,7 +1398,7 @@ function erDiagramSection(data) {
|
|
|
1277
1398
|
ctx.save();ctx.translate(pan.x,pan.y);ctx.scale(zm,zm);
|
|
1278
1399
|
badges=[];
|
|
1279
1400
|
var edges=collectEdges();
|
|
1280
|
-
edges.forEach(function(e){drawEdgeLine(e.a,e.b,e.col,e.dashed,e.label,e.
|
|
1401
|
+
edges.forEach(function(e){drawEdgeLine(e.a,e.b,e.col,e.dashed,e.label,e.carA,e.carB,e.off);});
|
|
1281
1402
|
nodes.forEach(function(n){drawNode(n);});
|
|
1282
1403
|
badges.forEach(function(b){drawBadge(b);});
|
|
1283
1404
|
ctx.restore();
|
|
@@ -1323,14 +1444,21 @@ function erDiagramSection(data) {
|
|
|
1323
1444
|
});
|
|
1324
1445
|
|
|
1325
1446
|
document.getElementById('er-dl').addEventListener('click',function(){
|
|
1326
|
-
var SCALE=3;
|
|
1327
|
-
var
|
|
1447
|
+
var SCALE=parseInt(document.getElementById('er-scale').value,10)||3;
|
|
1448
|
+
var pad=36;
|
|
1449
|
+
var bx0=1e9,by0=1e9,bx1=-1e9,by1=-1e9;
|
|
1450
|
+
nodes.forEach(function(n){
|
|
1451
|
+
bx0=Math.min(bx0,n.x);by0=Math.min(by0,n.y);
|
|
1452
|
+
bx1=Math.max(bx1,n.x+n.w);by1=Math.max(by1,n.y+n.h);
|
|
1453
|
+
});
|
|
1454
|
+
var cw=bx1-bx0+pad*2,ch=by1-by0+pad*2+LGH;
|
|
1328
1455
|
var oc=document.createElement('canvas');
|
|
1329
1456
|
oc.width=Math.round(cw*SCALE);oc.height=Math.round(ch*SCALE);
|
|
1330
|
-
var sav={cv:cv,ctx:ctx,dpr:dpr};
|
|
1457
|
+
var sav={cv:cv,ctx:ctx,dpr:dpr,px:pan.x,py:pan.y,zm:zm};
|
|
1331
1458
|
cv=oc;ctx=oc.getContext('2d');dpr=SCALE;
|
|
1459
|
+
pan.x=pad-bx0;pan.y=pad-by0;zm=1;
|
|
1332
1460
|
draw();
|
|
1333
|
-
cv=sav.cv;ctx=sav.ctx;dpr=sav.dpr;
|
|
1461
|
+
cv=sav.cv;ctx=sav.ctx;dpr=sav.dpr;pan.x=sav.px;pan.y=sav.py;zm=sav.zm;
|
|
1334
1462
|
var a=document.createElement('a');
|
|
1335
1463
|
a.download='er-diagram.png';a.href=oc.toDataURL('image/png');a.click();
|
|
1336
1464
|
});
|
|
@@ -1338,6 +1466,7 @@ function erDiagramSection(data) {
|
|
|
1338
1466
|
// \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1339
1467
|
requestAnimationFrame(function(){
|
|
1340
1468
|
resize();
|
|
1469
|
+
computeNodeWidths();
|
|
1341
1470
|
var r=cv.getBoundingClientRect();
|
|
1342
1471
|
computeLayout(r.width,r.height);
|
|
1343
1472
|
var h=requiredH();
|
|
@@ -1985,15 +2114,19 @@ function resolveRequestPath(dotPath, req) {
|
|
|
1985
2114
|
const [root, ...rest] = dotPath.split(".");
|
|
1986
2115
|
let value;
|
|
1987
2116
|
switch (root) {
|
|
2117
|
+
case "_body":
|
|
1988
2118
|
case "body":
|
|
1989
2119
|
value = req.body;
|
|
1990
2120
|
break;
|
|
2121
|
+
case "_params":
|
|
1991
2122
|
case "params":
|
|
1992
2123
|
value = req.params;
|
|
1993
2124
|
break;
|
|
2125
|
+
case "_query":
|
|
1994
2126
|
case "query":
|
|
1995
2127
|
value = req.query;
|
|
1996
2128
|
break;
|
|
2129
|
+
case "_headers":
|
|
1997
2130
|
case "headers":
|
|
1998
2131
|
value = req.headers;
|
|
1999
2132
|
break;
|
|
@@ -2294,6 +2427,23 @@ function buildCollectionSchema(items, fieldDefs = {}) {
|
|
|
2294
2427
|
if (def?.description) prop.description = def.description;
|
|
2295
2428
|
if (def?.enum) prop.enum = def.enum;
|
|
2296
2429
|
if (def?.default !== void 0) prop.default = def.default;
|
|
2430
|
+
if (def?.example !== void 0) prop.example = def.example;
|
|
2431
|
+
if (def?.nullable) prop.nullable = def.nullable;
|
|
2432
|
+
if (def?.minLength !== void 0) prop.minLength = def.minLength;
|
|
2433
|
+
if (def?.maxLength !== void 0) prop.maxLength = def.maxLength;
|
|
2434
|
+
if (def?.pattern) prop.pattern = def.pattern;
|
|
2435
|
+
if (def?.minimum !== void 0) prop.minimum = def.minimum;
|
|
2436
|
+
if (def?.maximum !== void 0) prop.maximum = def.maximum;
|
|
2437
|
+
if (def?.exclusiveMinimum !== void 0) prop.exclusiveMinimum = def.exclusiveMinimum;
|
|
2438
|
+
if (def?.exclusiveMaximum !== void 0) prop.exclusiveMaximum = def.exclusiveMaximum;
|
|
2439
|
+
if (def?.multipleOf !== void 0) prop.multipleOf = def.multipleOf;
|
|
2440
|
+
if (def?.items) prop.items = def.items;
|
|
2441
|
+
if (def?.minItems !== void 0) prop.minItems = def.minItems;
|
|
2442
|
+
if (def?.maxItems !== void 0) prop.maxItems = def.maxItems;
|
|
2443
|
+
if (def?.uniqueItems !== void 0) prop.uniqueItems = def.uniqueItems;
|
|
2444
|
+
if (def?.deprecated) prop.deprecated = def.deprecated;
|
|
2445
|
+
if (def?.readOnly) prop.readOnly = def.readOnly;
|
|
2446
|
+
if (def?.writeOnly) prop.writeOnly = def.writeOnly;
|
|
2297
2447
|
properties[field] = prop;
|
|
2298
2448
|
if (def?.required === true) required.push(field);
|
|
2299
2449
|
}
|
|
@@ -2797,12 +2947,12 @@ function buildOptions(opts) {
|
|
|
2797
2947
|
}
|
|
2798
2948
|
function createInMemoryStorage(data) {
|
|
2799
2949
|
const raw = data;
|
|
2800
|
-
const
|
|
2950
|
+
const RESERVED2 = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
2801
2951
|
const relations = parseRelations(raw["_rel"]);
|
|
2802
2952
|
const routes = Array.isArray(raw["_routes"]) ? raw["_routes"] : [];
|
|
2803
2953
|
const schema = parseSchema(raw["_schema"]);
|
|
2804
2954
|
const collections = Object.fromEntries(
|
|
2805
|
-
Object.entries(raw).filter(([k]) => !
|
|
2955
|
+
Object.entries(raw).filter(([k]) => !RESERVED2.has(k))
|
|
2806
2956
|
);
|
|
2807
2957
|
let snapshot = {
|
|
2808
2958
|
data: deepCopyData(collections),
|