@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/cli/index.mjs
CHANGED
|
@@ -690,6 +690,7 @@ function parseSchema(raw) {
|
|
|
690
690
|
}
|
|
691
691
|
return result;
|
|
692
692
|
}
|
|
693
|
+
var VALID_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean", "object", "array"]);
|
|
693
694
|
function normaliseFieldDef(value) {
|
|
694
695
|
if (value === "required") return { required: true };
|
|
695
696
|
if (value === "optional") return { required: false };
|
|
@@ -697,15 +698,60 @@ function normaliseFieldDef(value) {
|
|
|
697
698
|
const v = value;
|
|
698
699
|
const def = {};
|
|
699
700
|
if (v["_required"] === true || v["_required"] === false) def.required = v["_required"];
|
|
700
|
-
if (typeof v["_type"] === "string" &&
|
|
701
|
+
if (typeof v["_type"] === "string" && VALID_TYPES.has(v["_type"]))
|
|
701
702
|
def.type = v["_type"];
|
|
702
703
|
if (typeof v["_format"] === "string") def.format = v["_format"];
|
|
703
704
|
if (Array.isArray(v["_enum"])) def.enum = v["_enum"];
|
|
704
705
|
if (typeof v["_description"] === "string") def.description = v["_description"];
|
|
705
706
|
if (v["_default"] !== void 0) def.default = v["_default"];
|
|
707
|
+
if (v["_example"] !== void 0) def.example = v["_example"];
|
|
708
|
+
if (v["_nullable"] === true || v["_nullable"] === false) def.nullable = v["_nullable"];
|
|
709
|
+
if (typeof v["_minLength"] === "number") def.minLength = v["_minLength"];
|
|
710
|
+
if (typeof v["_maxLength"] === "number") def.maxLength = v["_maxLength"];
|
|
711
|
+
if (typeof v["_pattern"] === "string") def.pattern = v["_pattern"];
|
|
712
|
+
if (typeof v["_minimum"] === "number") def.minimum = v["_minimum"];
|
|
713
|
+
if (typeof v["_maximum"] === "number") def.maximum = v["_maximum"];
|
|
714
|
+
if (typeof v["_exclusiveMinimum"] === "number") def.exclusiveMinimum = v["_exclusiveMinimum"];
|
|
715
|
+
if (typeof v["_exclusiveMaximum"] === "number") def.exclusiveMaximum = v["_exclusiveMaximum"];
|
|
716
|
+
if (typeof v["_multipleOf"] === "number") def.multipleOf = v["_multipleOf"];
|
|
717
|
+
if (typeof v["_minItems"] === "number") def.minItems = v["_minItems"];
|
|
718
|
+
if (typeof v["_maxItems"] === "number") def.maxItems = v["_maxItems"];
|
|
719
|
+
if (v["_uniqueItems"] === true || v["_uniqueItems"] === false)
|
|
720
|
+
def.uniqueItems = v["_uniqueItems"];
|
|
721
|
+
if (v["_items"] && typeof v["_items"] === "object" && !Array.isArray(v["_items"])) {
|
|
722
|
+
const items = v["_items"];
|
|
723
|
+
const itemsDef = {};
|
|
724
|
+
if (typeof items["_type"] === "string" && VALID_TYPES.has(items["_type"]))
|
|
725
|
+
itemsDef.type = items["_type"];
|
|
726
|
+
if (typeof items["_format"] === "string") itemsDef.format = items["_format"];
|
|
727
|
+
if (Array.isArray(items["_enum"])) itemsDef.enum = items["_enum"];
|
|
728
|
+
def.items = itemsDef;
|
|
729
|
+
}
|
|
730
|
+
if (v["_deprecated"] === true || v["_deprecated"] === false)
|
|
731
|
+
def.deprecated = v["_deprecated"];
|
|
732
|
+
if (v["_readOnly"] === true || v["_readOnly"] === false) def.readOnly = v["_readOnly"];
|
|
733
|
+
if (v["_writeOnly"] === true || v["_writeOnly"] === false)
|
|
734
|
+
def.writeOnly = v["_writeOnly"];
|
|
706
735
|
return def;
|
|
707
736
|
}
|
|
708
737
|
|
|
738
|
+
// src/storage/parseData.ts
|
|
739
|
+
var RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema", "_data"]);
|
|
740
|
+
function parseData(raw) {
|
|
741
|
+
const block = raw["_data"];
|
|
742
|
+
const fromBlock = block && typeof block === "object" && !Array.isArray(block) ? Object.fromEntries(
|
|
743
|
+
Object.entries(block).filter(([, v]) => Array.isArray(v))
|
|
744
|
+
) : {};
|
|
745
|
+
const fromRoot = Object.fromEntries(
|
|
746
|
+
Object.entries(raw).filter(([key, val]) => !RESERVED.has(key) && Array.isArray(val))
|
|
747
|
+
);
|
|
748
|
+
return { ...fromBlock, ...fromRoot };
|
|
749
|
+
}
|
|
750
|
+
function hasDataBlock(raw) {
|
|
751
|
+
const d = raw["_data"];
|
|
752
|
+
return d !== null && d !== void 0 && typeof d === "object" && !Array.isArray(d);
|
|
753
|
+
}
|
|
754
|
+
|
|
709
755
|
// src/utils/deepCopy.ts
|
|
710
756
|
function deepCopyData(source) {
|
|
711
757
|
return Object.fromEntries(
|
|
@@ -771,13 +817,11 @@ function serializeScenario(s) {
|
|
|
771
817
|
function createYrestStorage(filePath) {
|
|
772
818
|
const absPath = resolve2(filePath);
|
|
773
819
|
const raw = parse(readFileSync(absPath, "utf8")) ?? {};
|
|
774
|
-
const RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
775
820
|
const relations = parseRelations(raw["_rel"]);
|
|
776
821
|
const routes = parseRoutes(raw["_routes"]);
|
|
777
822
|
const schema = parseSchema(raw["_schema"]);
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
);
|
|
823
|
+
const dataBlock = hasDataBlock(raw);
|
|
824
|
+
const data = parseData(raw);
|
|
781
825
|
let snapshot = {
|
|
782
826
|
data: deepCopyData(data),
|
|
783
827
|
relations: { ...relations },
|
|
@@ -806,7 +850,11 @@ function createYrestStorage(filePath) {
|
|
|
806
850
|
const payload = {};
|
|
807
851
|
if (Object.keys(relations).length > 0) payload._rel = serializeRelations(relations);
|
|
808
852
|
if (routes.length > 0) payload._routes = serializeRoutes(routes);
|
|
809
|
-
|
|
853
|
+
if (dataBlock) {
|
|
854
|
+
payload._data = data;
|
|
855
|
+
} else {
|
|
856
|
+
Object.assign(payload, data);
|
|
857
|
+
}
|
|
810
858
|
const tmp = resolve2(dirname(absPath), `.yrest-${randomUUID()}.tmp`);
|
|
811
859
|
writeFileSync2(tmp, stringify(payload), "utf8");
|
|
812
860
|
renameSync(tmp, absPath);
|
|
@@ -814,9 +862,7 @@ function createYrestStorage(filePath) {
|
|
|
814
862
|
reload() {
|
|
815
863
|
const fresh = parse(readFileSync(absPath, "utf8")) ?? {};
|
|
816
864
|
const freshRelations = parseRelations(fresh["_rel"]);
|
|
817
|
-
const freshData =
|
|
818
|
-
Object.entries(fresh).filter(([key]) => !RESERVED.has(key))
|
|
819
|
-
);
|
|
865
|
+
const freshData = parseData(fresh);
|
|
820
866
|
for (const key of Object.keys(data)) delete data[key];
|
|
821
867
|
Object.assign(data, freshData);
|
|
822
868
|
for (const key of Object.keys(relations)) delete relations[key];
|
|
@@ -1196,9 +1242,15 @@ function generateERData(storage) {
|
|
|
1196
1242
|
const relations = storage.getRelations();
|
|
1197
1243
|
const schema = storage.getSchema();
|
|
1198
1244
|
const pivotTables = /* @__PURE__ */ new Set();
|
|
1245
|
+
const pivotFKs = /* @__PURE__ */ new Map();
|
|
1199
1246
|
for (const fields of Object.values(relations)) {
|
|
1200
1247
|
for (const def of Object.values(fields)) {
|
|
1201
|
-
if (def.type === "many2many")
|
|
1248
|
+
if (def.type === "many2many") {
|
|
1249
|
+
pivotTables.add(def.through);
|
|
1250
|
+
if (!pivotFKs.has(def.through)) pivotFKs.set(def.through, /* @__PURE__ */ new Set());
|
|
1251
|
+
pivotFKs.get(def.through).add(def.foreignKey);
|
|
1252
|
+
pivotFKs.get(def.through).add(def.otherKey);
|
|
1253
|
+
}
|
|
1202
1254
|
}
|
|
1203
1255
|
}
|
|
1204
1256
|
const fkFields = /* @__PURE__ */ new Map();
|
|
@@ -1234,6 +1286,9 @@ function generateERData(storage) {
|
|
|
1234
1286
|
for (const [source, fields] of Object.entries(relations)) {
|
|
1235
1287
|
for (const [key, def] of Object.entries(fields)) {
|
|
1236
1288
|
if (def.type === "many2many") {
|
|
1289
|
+
const throughRels = relations[def.through] ?? {};
|
|
1290
|
+
const fkRel = throughRels[def.foreignKey];
|
|
1291
|
+
const otherRel = throughRels[def.otherKey];
|
|
1237
1292
|
erRelations.push({
|
|
1238
1293
|
source,
|
|
1239
1294
|
target: def.target,
|
|
@@ -1241,9 +1296,14 @@ function generateERData(storage) {
|
|
|
1241
1296
|
label: def.through,
|
|
1242
1297
|
through: def.through,
|
|
1243
1298
|
foreignKey: def.foreignKey,
|
|
1244
|
-
otherKey: def.otherKey
|
|
1299
|
+
otherKey: def.otherKey,
|
|
1300
|
+
fkCarDirect: fkRel?.carDirect,
|
|
1301
|
+
fkCarInverse: fkRel?.carInverse,
|
|
1302
|
+
otherCarDirect: otherRel?.carDirect,
|
|
1303
|
+
otherCarInverse: otherRel?.carInverse
|
|
1245
1304
|
});
|
|
1246
1305
|
} else {
|
|
1306
|
+
if (pivotFKs.get(source)?.has(key)) continue;
|
|
1247
1307
|
erRelations.push({
|
|
1248
1308
|
source,
|
|
1249
1309
|
target: def.target,
|
|
@@ -1269,7 +1329,15 @@ function erDiagramSection(data) {
|
|
|
1269
1329
|
|
|
1270
1330
|
<div class="card" style="padding:0;overflow:hidden;position:relative;">
|
|
1271
1331
|
<canvas id="er-canvas" style="width:100%;display:block;cursor:grab;"></canvas>
|
|
1272
|
-
<
|
|
1332
|
+
<div style="position:absolute;top:10px;right:14px;display:flex;gap:6px;align-items:center;">
|
|
1333
|
+
<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;">
|
|
1334
|
+
<option value="1">1\xD7</option>
|
|
1335
|
+
<option value="2">2\xD7</option>
|
|
1336
|
+
<option value="3" selected>3\xD7</option>
|
|
1337
|
+
<option value="4">4\xD7</option>
|
|
1338
|
+
</select>
|
|
1339
|
+
<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>
|
|
1340
|
+
</div>
|
|
1273
1341
|
</div>
|
|
1274
1342
|
|
|
1275
1343
|
<script>${/*js*/
|
|
@@ -1279,7 +1347,7 @@ function erDiagramSection(data) {
|
|
|
1279
1347
|
var D=${json};
|
|
1280
1348
|
|
|
1281
1349
|
// \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
|
|
1282
|
-
var NW=178,HH=34,FH=22,FP=10,ROW_GAP=44,COL_GAP=100,BR=
|
|
1350
|
+
var NW=178,HH=34,FH=22,FP=10,ROW_GAP=44,COL_GAP=100,BR=12,BO=16,LGH=84,MIN_GAP=10;
|
|
1283
1351
|
|
|
1284
1352
|
var C={bg:'#0d1117',nb:'#161b22',nh:'#1c2128',pb:'#0d1117',ph:'#161b22',
|
|
1285
1353
|
bo:'#30363d',bs:'#58a6ff',tx:'#e6edf3',mu:'#7d8590',
|
|
@@ -1319,6 +1387,30 @@ function erDiagramSection(data) {
|
|
|
1319
1387
|
}
|
|
1320
1388
|
});
|
|
1321
1389
|
|
|
1390
|
+
// \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
|
|
1391
|
+
function computeNodeWidths(){
|
|
1392
|
+
nodes.forEach(function(n){
|
|
1393
|
+
var e=n.e;
|
|
1394
|
+
ctx.font='600 12px -apple-system,BlinkMacSystemFont,sans-serif';
|
|
1395
|
+
var hdrW=FP+ctx.measureText(e.name).width+FP;
|
|
1396
|
+
if(e.isPivot){
|
|
1397
|
+
ctx.font='9px monospace';
|
|
1398
|
+
hdrW+=ctx.measureText('pivot').width+FP;
|
|
1399
|
+
}
|
|
1400
|
+
var maxRow=hdrW;
|
|
1401
|
+
e.fields.forEach(function(f){
|
|
1402
|
+
var leftPad=FP+(f.pk||f.fk?16:0);
|
|
1403
|
+
var rightPad=(f.required&&!f.pk?14:0)+FP;
|
|
1404
|
+
ctx.font=(f.required&&!f.pk?'600 ':'')+'11px monospace';
|
|
1405
|
+
var nw=ctx.measureText(f.name).width;
|
|
1406
|
+
ctx.font='10px monospace';
|
|
1407
|
+
var tw=ctx.measureText(f.type).width;
|
|
1408
|
+
maxRow=Math.max(maxRow,leftPad+nw+MIN_GAP+tw+rightPad);
|
|
1409
|
+
});
|
|
1410
|
+
n.w=Math.max(NW,Math.ceil(maxRow));
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1322
1414
|
// \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
|
|
1323
1415
|
var groups=[];
|
|
1324
1416
|
|
|
@@ -1357,7 +1449,8 @@ function erDiagramSection(data) {
|
|
|
1357
1449
|
median(adj[b].map(function(x){return np[x]!=null?np[x]:0;}));
|
|
1358
1450
|
});
|
|
1359
1451
|
}
|
|
1360
|
-
var
|
|
1452
|
+
var maxNW=nodes.reduce(function(m,n){return Math.max(m,n.w);},NW);
|
|
1453
|
+
var colW=maxNW+COL_GAP;
|
|
1361
1454
|
var totalW=(maxL+1)*colW-COL_GAP;
|
|
1362
1455
|
var startX=Math.max(20,(W-totalW)/2);
|
|
1363
1456
|
for(var l=0;l<=maxL;l++){
|
|
@@ -1405,37 +1498,37 @@ function erDiagramSection(data) {
|
|
|
1405
1498
|
|
|
1406
1499
|
var badges=[];
|
|
1407
1500
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1501
|
+
// \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
|
|
1502
|
+
// SVG orientation: LEFT side touches the node, RIGHT side connects to the line.
|
|
1503
|
+
// Rotation = Math.atan2(uy,ux) aligns local +x with the outward (away-from-node) direction.
|
|
1504
|
+
var P2D={
|
|
1505
|
+
'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'),
|
|
1506
|
+
'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'),
|
|
1507
|
+
'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'),
|
|
1508
|
+
'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'),
|
|
1509
|
+
'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')
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
function pushBadge(x,y,ux,uy,car,col){
|
|
1513
|
+
badges.push({x:x,y:y,ux:ux,uy:uy,car:car,col:col});
|
|
1410
1514
|
}
|
|
1411
1515
|
|
|
1412
|
-
// Badge: solid colored circle; crow's foot (many) or double-bar (one) in dark ink inside.
|
|
1413
|
-
// ux,uy points outward from the node \u2014 badge is offset BO px into the gap.
|
|
1414
|
-
// Crow's foot tines open toward the node (-x in rotated frame), tip toward line (+x).
|
|
1415
1516
|
function drawBadge(b){
|
|
1416
|
-
var
|
|
1417
|
-
ctx.
|
|
1418
|
-
ctx.
|
|
1419
|
-
ctx.
|
|
1420
|
-
ctx.
|
|
1421
|
-
ctx.
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
ctx.moveTo(-5,4);ctx.lineTo(5,0); // lower tine \u2192 tip
|
|
1425
|
-
ctx.moveTo(-5,0);ctx.lineTo(5,0); // center line
|
|
1426
|
-
ctx.moveTo(-5,-4);ctx.lineTo(-5,4); // base bar (near node)
|
|
1427
|
-
}else{
|
|
1428
|
-
ctx.moveTo(-3,-5);ctx.lineTo(-3,5); // near bar
|
|
1429
|
-
ctx.moveTo(2,-5);ctx.lineTo(2,5); // far bar
|
|
1430
|
-
}
|
|
1431
|
-
ctx.stroke();
|
|
1517
|
+
var p=P2D[b.car]||P2D['n'],sc=BR/40;
|
|
1518
|
+
ctx.save();
|
|
1519
|
+
ctx.translate(b.x+b.ux*BO,b.y+b.uy*BO);
|
|
1520
|
+
ctx.rotate(Math.atan2(b.uy,b.ux));
|
|
1521
|
+
ctx.scale(sc,sc);
|
|
1522
|
+
ctx.translate(-40,-40);
|
|
1523
|
+
ctx.fillStyle=b.col;
|
|
1524
|
+
ctx.fill(p,'evenodd');
|
|
1432
1525
|
ctx.restore();
|
|
1433
1526
|
}
|
|
1434
1527
|
|
|
1435
1528
|
function ecol(t){return t==='one2one'?C.o2o:t==='many2many'?C.m2m:C.m2o;}
|
|
1436
1529
|
|
|
1437
1530
|
// offset: pixels to shift perpendicular to the edge direction (for parallel edges)
|
|
1438
|
-
function drawEdgeLine(a,b,col,dashed,label,
|
|
1531
|
+
function drawEdgeLine(a,b,col,dashed,label,carA,carB,off){
|
|
1439
1532
|
var p1=edgePt(a,b.x+b.w/2,b.y+b.h/2);
|
|
1440
1533
|
var p2=edgePt(b,a.x+a.w/2,a.y+a.h/2);
|
|
1441
1534
|
var dx=p2.x-p1.x,dy=p2.y-p1.y,d=Math.sqrt(dx*dx+dy*dy)||1;
|
|
@@ -1446,13 +1539,19 @@ function erDiagramSection(data) {
|
|
|
1446
1539
|
ctx.setLineDash(dashed?[5,4]:[]);
|
|
1447
1540
|
ctx.beginPath();ctx.moveTo(q1x,q1y);ctx.lineTo(q2x,q2y);ctx.stroke();
|
|
1448
1541
|
ctx.setLineDash([]);
|
|
1449
|
-
pushBadge(q1x,q1y,ux,uy,
|
|
1450
|
-
pushBadge(q2x,q2y,-ux,-uy,
|
|
1542
|
+
pushBadge(q1x,q1y,ux,uy,carA,col);
|
|
1543
|
+
pushBadge(q2x,q2y,-ux,-uy,carB,col);
|
|
1451
1544
|
if(label){
|
|
1452
1545
|
var mx=(q1x+q2x)/2,my=(q1y+q2y)/2;
|
|
1546
|
+
var la=Math.atan2(uy,ux);
|
|
1547
|
+
if(la>Math.PI/2||la<-Math.PI/2)la+=Math.PI; // keep text readable
|
|
1548
|
+
var ls=off>0?11:-11; // alternate above/below for parallel edges
|
|
1549
|
+
ctx.save();
|
|
1550
|
+
ctx.translate(mx,my);
|
|
1551
|
+
ctx.rotate(la);
|
|
1453
1552
|
ctx.fillStyle=C.mu;ctx.font='10px monospace';ctx.textAlign='center';ctx.textBaseline='middle';
|
|
1454
|
-
ctx.fillText(label,
|
|
1455
|
-
ctx.
|
|
1553
|
+
ctx.fillText(label,0,ls);
|
|
1554
|
+
ctx.restore();
|
|
1456
1555
|
}
|
|
1457
1556
|
}
|
|
1458
1557
|
|
|
@@ -1468,14 +1567,14 @@ function erDiagramSection(data) {
|
|
|
1468
1567
|
var col=ecol(r.type);
|
|
1469
1568
|
if(r.type==='many2many'&&r.through&&nIdx[r.through]!=null){
|
|
1470
1569
|
var sn=nodes[nIdx[r.source]],pn=nodes[nIdx[r.through]],tn=nodes[nIdx[r.target]];
|
|
1471
|
-
if(sn&&pn)edges.push({a:sn,b:pn,col:col,dashed:true,label:r.foreignKey||'',
|
|
1472
|
-
if(tn&&pn)edges.push({a:tn,b:pn,col:col,dashed:true,label:r.otherKey||'',
|
|
1570
|
+
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});
|
|
1571
|
+
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});
|
|
1473
1572
|
}else{
|
|
1474
1573
|
var sn=nodes[nIdx[r.source]],tn=nodes[nIdx[r.target]];
|
|
1475
1574
|
if(sn&&tn&&sn!==tn){
|
|
1476
|
-
var
|
|
1477
|
-
var
|
|
1478
|
-
edges.push({a:sn,b:tn,col:col,dashed:false,label:r.label,
|
|
1575
|
+
var carA=r.carInverse||(r.type!=='one2one'?'1..n':'1..1');
|
|
1576
|
+
var carB=r.carDirect||(r.type==='many2many'?'1..n':'1..1');
|
|
1577
|
+
edges.push({a:sn,b:tn,col:col,dashed:false,label:r.label,carA:carA,carB:carB,off:0});
|
|
1479
1578
|
}
|
|
1480
1579
|
}
|
|
1481
1580
|
});
|
|
@@ -1534,12 +1633,24 @@ function erDiagramSection(data) {
|
|
|
1534
1633
|
|
|
1535
1634
|
function drawLegend(){
|
|
1536
1635
|
var cvW=cv.width/dpr,cvH=cv.height/dpr;
|
|
1537
|
-
var
|
|
1636
|
+
var yL=cvH-LGH,y1=yL+13,y2=yL+35,y3=yL+62;
|
|
1538
1637
|
|
|
1539
1638
|
ctx.fillStyle='#161b22';
|
|
1540
|
-
ctx.fillRect(0,
|
|
1639
|
+
ctx.fillRect(0,yL,cvW,LGH);
|
|
1541
1640
|
ctx.strokeStyle='#30363d';ctx.lineWidth=1;
|
|
1542
|
-
ctx.beginPath();ctx.moveTo(0,
|
|
1641
|
+
ctx.beginPath();ctx.moveTo(0,yL);ctx.lineTo(cvW,yL);ctx.stroke();
|
|
1642
|
+
|
|
1643
|
+
function legendBadge(cx,cy,car,col,angle){
|
|
1644
|
+
var p=P2D[car]||P2D['n'],sc=8/40;
|
|
1645
|
+
ctx.save();
|
|
1646
|
+
ctx.translate(cx,cy);
|
|
1647
|
+
ctx.rotate(angle);
|
|
1648
|
+
ctx.scale(sc,sc);
|
|
1649
|
+
ctx.translate(-40,-40);
|
|
1650
|
+
ctx.fillStyle=col;
|
|
1651
|
+
ctx.fill(p,'evenodd');
|
|
1652
|
+
ctx.restore();
|
|
1653
|
+
}
|
|
1543
1654
|
|
|
1544
1655
|
// Row 1: relation line types
|
|
1545
1656
|
var types=[
|
|
@@ -1558,42 +1669,43 @@ function erDiagramSection(data) {
|
|
|
1558
1669
|
x+=18+ctx.measureText(it.l).width+22;
|
|
1559
1670
|
});
|
|
1560
1671
|
|
|
1561
|
-
// Row 2:
|
|
1562
|
-
function badge2(cx,cy,many,col){
|
|
1563
|
-
ctx.fillStyle=col;ctx.strokeStyle=col;ctx.lineWidth=1;
|
|
1564
|
-
ctx.beginPath();ctx.arc(cx,cy,6,0,Math.PI*2);ctx.fill();
|
|
1565
|
-
ctx.save();ctx.translate(cx,cy);
|
|
1566
|
-
ctx.strokeStyle=C.bg;ctx.lineWidth=1.2;ctx.lineCap='round';
|
|
1567
|
-
ctx.beginPath();
|
|
1568
|
-
if(many){
|
|
1569
|
-
ctx.moveTo(-4,-3);ctx.lineTo(4,0);ctx.moveTo(-4,3);ctx.lineTo(4,0);
|
|
1570
|
-
ctx.moveTo(-4,0);ctx.lineTo(4,0);ctx.moveTo(-4,-3);ctx.lineTo(-4,3);
|
|
1571
|
-
}else{
|
|
1572
|
-
ctx.moveTo(-2,-3);ctx.lineTo(-2,3);ctx.moveTo(2,-3);ctx.lineTo(2,3);
|
|
1573
|
-
}
|
|
1574
|
-
ctx.stroke();ctx.restore();
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
ctx.font='10px monospace';ctx.textAlign='left';ctx.textBaseline='middle';
|
|
1672
|
+
// Row 2: relation type examples (pair of badges + line)
|
|
1578
1673
|
x=14;
|
|
1579
1674
|
var pairs=[
|
|
1580
|
-
{
|
|
1581
|
-
{
|
|
1582
|
-
{
|
|
1675
|
+
{carA:'1..n',carB:'1..1',col:C.m2o,l:'many2one (N:1)'},
|
|
1676
|
+
{carA:'1..1',carB:'1..1',col:C.o2o,l:'one2one (1:1)'},
|
|
1677
|
+
{carA:'1..1',carB:'0..n',col:C.m2m,l:'pivot edge (1:0..N)'}
|
|
1583
1678
|
];
|
|
1584
1679
|
pairs.forEach(function(p){
|
|
1585
1680
|
ctx.strokeStyle=p.col+'55';ctx.lineWidth=1.2;ctx.setLineDash(p.col===C.m2m?[4,3]:[]);
|
|
1586
1681
|
ctx.beginPath();ctx.moveTo(x+8,y2);ctx.lineTo(x+56,y2);ctx.stroke();
|
|
1587
1682
|
ctx.setLineDash([]);
|
|
1588
|
-
|
|
1589
|
-
|
|
1683
|
+
legendBadge(x+8,y2,p.carA,p.col,0);
|
|
1684
|
+
legendBadge(x+56,y2,p.carB,p.col,Math.PI);
|
|
1590
1685
|
ctx.fillStyle=C.mu;ctx.font='10px monospace';ctx.textAlign='left';ctx.textBaseline='middle';
|
|
1591
1686
|
ctx.fillText(p.l,x+66,y2);
|
|
1592
1687
|
x+=66+ctx.measureText(p.l).width+24;
|
|
1593
1688
|
});
|
|
1594
1689
|
|
|
1690
|
+
// Row 3: individual symbol meanings
|
|
1691
|
+
var symbols=[
|
|
1692
|
+
{car:'1..1',l:'exactly one'},
|
|
1693
|
+
{car:'0..1',l:'zero or one'},
|
|
1694
|
+
{car:'1..n',l:'one or more'},
|
|
1695
|
+
{car:'0..n',l:'zero or more'},
|
|
1696
|
+
{car:'n', l:'many (pivot)'}
|
|
1697
|
+
];
|
|
1698
|
+
x=14;
|
|
1699
|
+
ctx.font='10px monospace';ctx.textAlign='left';ctx.textBaseline='middle';
|
|
1700
|
+
symbols.forEach(function(s){
|
|
1701
|
+
legendBadge(x+8,y3,s.car,C.mu,0);
|
|
1702
|
+
ctx.fillStyle=C.mu;
|
|
1703
|
+
ctx.fillText(s.l,x+20,y3);
|
|
1704
|
+
x+=20+ctx.measureText(s.l).width+18;
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1595
1707
|
ctx.fillStyle='#3d444d';ctx.font='10px monospace';ctx.textAlign='right';ctx.textBaseline='middle';
|
|
1596
|
-
ctx.fillText('drag \xB7 scroll to zoom \xB7 dbl-click to reset',cvW-14,
|
|
1708
|
+
ctx.fillText('drag \xB7 scroll to zoom \xB7 dbl-click to reset',cvW-14,y1);
|
|
1597
1709
|
ctx.textBaseline='alphabetic';
|
|
1598
1710
|
}
|
|
1599
1711
|
|
|
@@ -1605,7 +1717,7 @@ function erDiagramSection(data) {
|
|
|
1605
1717
|
ctx.save();ctx.translate(pan.x,pan.y);ctx.scale(zm,zm);
|
|
1606
1718
|
badges=[];
|
|
1607
1719
|
var edges=collectEdges();
|
|
1608
|
-
edges.forEach(function(e){drawEdgeLine(e.a,e.b,e.col,e.dashed,e.label,e.
|
|
1720
|
+
edges.forEach(function(e){drawEdgeLine(e.a,e.b,e.col,e.dashed,e.label,e.carA,e.carB,e.off);});
|
|
1609
1721
|
nodes.forEach(function(n){drawNode(n);});
|
|
1610
1722
|
badges.forEach(function(b){drawBadge(b);});
|
|
1611
1723
|
ctx.restore();
|
|
@@ -1651,14 +1763,21 @@ function erDiagramSection(data) {
|
|
|
1651
1763
|
});
|
|
1652
1764
|
|
|
1653
1765
|
document.getElementById('er-dl').addEventListener('click',function(){
|
|
1654
|
-
var SCALE=3;
|
|
1655
|
-
var
|
|
1766
|
+
var SCALE=parseInt(document.getElementById('er-scale').value,10)||3;
|
|
1767
|
+
var pad=36;
|
|
1768
|
+
var bx0=1e9,by0=1e9,bx1=-1e9,by1=-1e9;
|
|
1769
|
+
nodes.forEach(function(n){
|
|
1770
|
+
bx0=Math.min(bx0,n.x);by0=Math.min(by0,n.y);
|
|
1771
|
+
bx1=Math.max(bx1,n.x+n.w);by1=Math.max(by1,n.y+n.h);
|
|
1772
|
+
});
|
|
1773
|
+
var cw=bx1-bx0+pad*2,ch=by1-by0+pad*2+LGH;
|
|
1656
1774
|
var oc=document.createElement('canvas');
|
|
1657
1775
|
oc.width=Math.round(cw*SCALE);oc.height=Math.round(ch*SCALE);
|
|
1658
|
-
var sav={cv:cv,ctx:ctx,dpr:dpr};
|
|
1776
|
+
var sav={cv:cv,ctx:ctx,dpr:dpr,px:pan.x,py:pan.y,zm:zm};
|
|
1659
1777
|
cv=oc;ctx=oc.getContext('2d');dpr=SCALE;
|
|
1778
|
+
pan.x=pad-bx0;pan.y=pad-by0;zm=1;
|
|
1660
1779
|
draw();
|
|
1661
|
-
cv=sav.cv;ctx=sav.ctx;dpr=sav.dpr;
|
|
1780
|
+
cv=sav.cv;ctx=sav.ctx;dpr=sav.dpr;pan.x=sav.px;pan.y=sav.py;zm=sav.zm;
|
|
1662
1781
|
var a=document.createElement('a');
|
|
1663
1782
|
a.download='er-diagram.png';a.href=oc.toDataURL('image/png');a.click();
|
|
1664
1783
|
});
|
|
@@ -1666,6 +1785,7 @@ function erDiagramSection(data) {
|
|
|
1666
1785
|
// \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
|
|
1667
1786
|
requestAnimationFrame(function(){
|
|
1668
1787
|
resize();
|
|
1788
|
+
computeNodeWidths();
|
|
1669
1789
|
var r=cv.getBoundingClientRect();
|
|
1670
1790
|
computeLayout(r.width,r.height);
|
|
1671
1791
|
var h=requiredH();
|
|
@@ -2302,15 +2422,19 @@ function resolveRequestPath(dotPath, req) {
|
|
|
2302
2422
|
const [root, ...rest] = dotPath.split(".");
|
|
2303
2423
|
let value;
|
|
2304
2424
|
switch (root) {
|
|
2425
|
+
case "_body":
|
|
2305
2426
|
case "body":
|
|
2306
2427
|
value = req.body;
|
|
2307
2428
|
break;
|
|
2429
|
+
case "_params":
|
|
2308
2430
|
case "params":
|
|
2309
2431
|
value = req.params;
|
|
2310
2432
|
break;
|
|
2433
|
+
case "_query":
|
|
2311
2434
|
case "query":
|
|
2312
2435
|
value = req.query;
|
|
2313
2436
|
break;
|
|
2437
|
+
case "_headers":
|
|
2314
2438
|
case "headers":
|
|
2315
2439
|
value = req.headers;
|
|
2316
2440
|
break;
|
|
@@ -2602,6 +2726,23 @@ function buildCollectionSchema(items, fieldDefs = {}) {
|
|
|
2602
2726
|
if (def?.description) prop.description = def.description;
|
|
2603
2727
|
if (def?.enum) prop.enum = def.enum;
|
|
2604
2728
|
if (def?.default !== void 0) prop.default = def.default;
|
|
2729
|
+
if (def?.example !== void 0) prop.example = def.example;
|
|
2730
|
+
if (def?.nullable) prop.nullable = def.nullable;
|
|
2731
|
+
if (def?.minLength !== void 0) prop.minLength = def.minLength;
|
|
2732
|
+
if (def?.maxLength !== void 0) prop.maxLength = def.maxLength;
|
|
2733
|
+
if (def?.pattern) prop.pattern = def.pattern;
|
|
2734
|
+
if (def?.minimum !== void 0) prop.minimum = def.minimum;
|
|
2735
|
+
if (def?.maximum !== void 0) prop.maximum = def.maximum;
|
|
2736
|
+
if (def?.exclusiveMinimum !== void 0) prop.exclusiveMinimum = def.exclusiveMinimum;
|
|
2737
|
+
if (def?.exclusiveMaximum !== void 0) prop.exclusiveMaximum = def.exclusiveMaximum;
|
|
2738
|
+
if (def?.multipleOf !== void 0) prop.multipleOf = def.multipleOf;
|
|
2739
|
+
if (def?.items) prop.items = def.items;
|
|
2740
|
+
if (def?.minItems !== void 0) prop.minItems = def.minItems;
|
|
2741
|
+
if (def?.maxItems !== void 0) prop.maxItems = def.maxItems;
|
|
2742
|
+
if (def?.uniqueItems !== void 0) prop.uniqueItems = def.uniqueItems;
|
|
2743
|
+
if (def?.deprecated) prop.deprecated = def.deprecated;
|
|
2744
|
+
if (def?.readOnly) prop.readOnly = def.readOnly;
|
|
2745
|
+
if (def?.writeOnly) prop.writeOnly = def.writeOnly;
|
|
2605
2746
|
properties[field] = prop;
|
|
2606
2747
|
if (def?.required === true) required.push(field);
|
|
2607
2748
|
}
|
|
@@ -3144,6 +3285,14 @@ async function loadHandlers(filePath) {
|
|
|
3144
3285
|
}
|
|
3145
3286
|
|
|
3146
3287
|
// src/cli/commands/serve.ts
|
|
3288
|
+
function buildServeOptions(file, fileConfig, cliOverrides, fileArgIsExplicit = false) {
|
|
3289
|
+
return yrestOptionsSchema.parse({
|
|
3290
|
+
file,
|
|
3291
|
+
...fileConfig,
|
|
3292
|
+
...fileArgIsExplicit ? { file } : {},
|
|
3293
|
+
...cliOverrides
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
3147
3296
|
function registerServe(program2) {
|
|
3148
3297
|
program2.command("serve").description("Start the mock server using a YAML file as database").argument("[file]", "Path to the YAML database file", "db.yml").option("-p, --port <number>", "Port to listen on", "3070").option("-H, --host <host>", "Host to bind", "localhost").option("-b, --base <path>", "Base path prefix for all routes", "").option("-w, --watch", "Reload db.yml automatically when it changes on disk").option("-r, --readonly", "Reject all write operations (POST, PUT, PATCH, DELETE) with 405").option(
|
|
3149
3298
|
"-d, --delay <ms>",
|
|
@@ -3167,13 +3316,7 @@ function registerServe(program2) {
|
|
|
3167
3316
|
const cliOverrides = Object.fromEntries(
|
|
3168
3317
|
Object.entries(flags).filter(([key]) => cmd.getOptionValueSource(key) === "cli")
|
|
3169
3318
|
);
|
|
3170
|
-
const
|
|
3171
|
-
file,
|
|
3172
|
-
...fileConfig,
|
|
3173
|
-
...cmd.args.length > 0 ? { file } : {},
|
|
3174
|
-
...cliOverrides
|
|
3175
|
-
};
|
|
3176
|
-
const options = yrestOptionsSchema.parse(merged);
|
|
3319
|
+
const options = buildServeOptions(file, fileConfig, cliOverrides, cmd.args.length > 0);
|
|
3177
3320
|
let storage;
|
|
3178
3321
|
try {
|
|
3179
3322
|
storage = createYrestStorage(options.file);
|
package/dist/index.d.mts
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.
|