@plasius/schema 1.1.1 → 1.2.1
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 +32 -19
- package/dist/index.cjs +511 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +238 -2
- package/dist/index.d.ts +238 -2
- package/dist/index.js +500 -75
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -416,6 +416,7 @@ var isoCountryCodes = /* @__PURE__ */ new Set([
|
|
|
416
416
|
"PM",
|
|
417
417
|
"PN",
|
|
418
418
|
"PR",
|
|
419
|
+
"PS",
|
|
419
420
|
"PT",
|
|
420
421
|
"PW",
|
|
421
422
|
"PY",
|
|
@@ -618,6 +619,7 @@ var isoCurrencyCodes = /* @__PURE__ */ new Set([
|
|
|
618
619
|
"SEK",
|
|
619
620
|
"SGD",
|
|
620
621
|
"SHP",
|
|
622
|
+
"SLE",
|
|
621
623
|
"SLL",
|
|
622
624
|
"SOS",
|
|
623
625
|
"SRD",
|
|
@@ -1103,49 +1105,151 @@ function enforcePIIField(parentKey, key, value, def, enforcement = "none", error
|
|
|
1103
1105
|
return { shortCircuit: false };
|
|
1104
1106
|
}
|
|
1105
1107
|
function prepareForStorage(shape, input, encryptFn, hashFn) {
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
const
|
|
1109
|
-
if (!def) continue;
|
|
1110
|
-
const value = input[key];
|
|
1108
|
+
const build = (def, value, key, out, ctx = {}) => {
|
|
1109
|
+
if (!def) return;
|
|
1110
|
+
const isMissing = value === void 0 || value === null;
|
|
1111
1111
|
if (def._pii?.action === "encrypt") {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1112
|
+
if (!isMissing) out[key + "Encrypted"] = encryptFn(value);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (def._pii?.action === "hash") {
|
|
1116
|
+
if (!isMissing) out[key + "Hash"] = hashFn(value);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
if (def._pii?.action === "clear") {
|
|
1120
|
+
if (!isMissing) out[key] = null;
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (def.type === "object" && def._shape) {
|
|
1124
|
+
const obj = {};
|
|
1125
|
+
for (const [childKey, childDef] of Object.entries(def._shape)) {
|
|
1126
|
+
build(childDef, value?.[childKey], childKey, obj, { parentKey: key });
|
|
1127
|
+
}
|
|
1128
|
+
out[key] = obj;
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
if (def.type === "array" && def.itemType && Array.isArray(value)) {
|
|
1132
|
+
out[key] = value.map((item) => {
|
|
1133
|
+
const obj = {};
|
|
1134
|
+
build(def.itemType, item, key, obj, {
|
|
1135
|
+
parentKey: key,
|
|
1136
|
+
isArrayItem: true
|
|
1137
|
+
});
|
|
1138
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
1139
|
+
return obj[key];
|
|
1140
|
+
}
|
|
1141
|
+
return Object.keys(obj).length > 0 ? obj : item;
|
|
1142
|
+
});
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (def.type === "ref") {
|
|
1146
|
+
const ref = { ...value };
|
|
1147
|
+
const refShape = def._shape;
|
|
1148
|
+
if (refShape && value) {
|
|
1149
|
+
for (const [childKey, childDef] of Object.entries(refShape)) {
|
|
1150
|
+
build(childDef, value[childKey], childKey, ref, { parentKey: key });
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
out[key] = ref;
|
|
1154
|
+
return;
|
|
1117
1155
|
}
|
|
1156
|
+
out[key] = value;
|
|
1157
|
+
};
|
|
1158
|
+
const result = {};
|
|
1159
|
+
for (const key in shape) {
|
|
1160
|
+
build(shape[key], input?.[key], key, result);
|
|
1118
1161
|
}
|
|
1119
1162
|
return result;
|
|
1120
1163
|
}
|
|
1121
1164
|
function prepareForRead(shape, stored, decryptFn) {
|
|
1165
|
+
const readValue = (def, key, container, ctx = {}) => {
|
|
1166
|
+
if (!def) return container?.[key];
|
|
1167
|
+
const action = def._pii?.action;
|
|
1168
|
+
if (action === "encrypt") {
|
|
1169
|
+
const enc = container?.[key + "Encrypted"] ?? container?.[`${ctx.parentKey ?? key}Encrypted`];
|
|
1170
|
+
return enc === void 0 ? void 0 : decryptFn(enc);
|
|
1171
|
+
}
|
|
1172
|
+
if (action === "hash") {
|
|
1173
|
+
const h = container?.[key + "Hash"] ?? container?.[`${ctx.parentKey ?? key}Hash`];
|
|
1174
|
+
return h === void 0 ? void 0 : h;
|
|
1175
|
+
}
|
|
1176
|
+
if (action === "clear")
|
|
1177
|
+
return container?.hasOwnProperty(key) ? null : void 0;
|
|
1178
|
+
if (def.type === "object" && def._shape) {
|
|
1179
|
+
const obj = {};
|
|
1180
|
+
const source = container && Object.prototype.hasOwnProperty.call(container, key) ? container[key] : container ?? {};
|
|
1181
|
+
for (const [childKey, childDef] of Object.entries(def._shape)) {
|
|
1182
|
+
obj[childKey] = readValue(childDef, childKey, source);
|
|
1183
|
+
}
|
|
1184
|
+
return obj;
|
|
1185
|
+
}
|
|
1186
|
+
if (def.type === "array" && def.itemType) {
|
|
1187
|
+
const arr = container?.[key];
|
|
1188
|
+
if (!Array.isArray(arr)) return arr;
|
|
1189
|
+
return arr.map(
|
|
1190
|
+
(item) => readValue(def.itemType, key, item, { parentKey: key, isArrayItem: true })
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
if (def.type === "ref") {
|
|
1194
|
+
const ref = { ...container?.[key] ?? {} };
|
|
1195
|
+
const refShape = def._shape;
|
|
1196
|
+
if (refShape) {
|
|
1197
|
+
for (const [childKey, childDef] of Object.entries(refShape)) {
|
|
1198
|
+
ref[childKey] = readValue(childDef, childKey, ref);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return ref;
|
|
1202
|
+
}
|
|
1203
|
+
return container?.[key];
|
|
1204
|
+
};
|
|
1122
1205
|
const result = {};
|
|
1123
1206
|
for (const key in shape) {
|
|
1124
|
-
|
|
1125
|
-
if (!def) continue;
|
|
1126
|
-
if (def._pii?.action === "encrypt") {
|
|
1127
|
-
result[key] = decryptFn(stored[key + "Encrypted"]);
|
|
1128
|
-
} else {
|
|
1129
|
-
result[key] = stored[key];
|
|
1130
|
-
}
|
|
1207
|
+
result[key] = readValue(shape[key], key, stored);
|
|
1131
1208
|
}
|
|
1132
1209
|
return result;
|
|
1133
1210
|
}
|
|
1134
1211
|
function sanitizeForLog(shape, data, pseudonymFn) {
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
const def = shape[key];
|
|
1138
|
-
if (!def) continue;
|
|
1139
|
-
const value = data[key];
|
|
1212
|
+
const visit = (def, value, ctx = {}) => {
|
|
1213
|
+
if (!def) return void 0;
|
|
1140
1214
|
const handling = def._pii?.logHandling;
|
|
1141
|
-
if (handling === "omit")
|
|
1142
|
-
if (handling === "redact")
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1215
|
+
if (handling === "omit") return void 0;
|
|
1216
|
+
if (handling === "redact") return "[REDACTED]";
|
|
1217
|
+
if (handling === "pseudonym") return pseudonymFn(value);
|
|
1218
|
+
if (def.type === "object" && def._shape) {
|
|
1219
|
+
const obj = {};
|
|
1220
|
+
const src = value ?? {};
|
|
1221
|
+
for (const [k, childDef] of Object.entries(def._shape)) {
|
|
1222
|
+
const child = visit(childDef, src[k], { parentKey: k });
|
|
1223
|
+
if (child !== void 0) obj[k] = child;
|
|
1224
|
+
}
|
|
1225
|
+
return obj;
|
|
1226
|
+
}
|
|
1227
|
+
if (def.type === "array" && def.itemType) {
|
|
1228
|
+
if (!Array.isArray(value)) return void 0;
|
|
1229
|
+
const arr = value.map((v) => visit(def.itemType, v, { parentKey: ctx.parentKey })).filter((v) => v !== void 0);
|
|
1230
|
+
return arr;
|
|
1231
|
+
}
|
|
1232
|
+
if (def.type === "ref") {
|
|
1233
|
+
const ref = value ? { type: value.type, id: value.id } : {};
|
|
1234
|
+
const refShape = def._shape;
|
|
1235
|
+
const src = value ?? {};
|
|
1236
|
+
if (refShape) {
|
|
1237
|
+
for (const [k, childDef] of Object.entries(refShape)) {
|
|
1238
|
+
const child = visit(childDef, src[k]);
|
|
1239
|
+
if (child !== void 0) ref[k] = child;
|
|
1240
|
+
}
|
|
1241
|
+
} else if (value) {
|
|
1242
|
+
ref.type = value.type;
|
|
1243
|
+
ref.id = value.id;
|
|
1244
|
+
}
|
|
1245
|
+
return ref;
|
|
1148
1246
|
}
|
|
1247
|
+
return value;
|
|
1248
|
+
};
|
|
1249
|
+
const output = {};
|
|
1250
|
+
for (const key in shape) {
|
|
1251
|
+
const child = visit(shape[key], data?.[key]);
|
|
1252
|
+
if (child !== void 0) output[key] = child;
|
|
1149
1253
|
}
|
|
1150
1254
|
return output;
|
|
1151
1255
|
}
|
|
@@ -1167,23 +1271,106 @@ function getPiiAudit(shape) {
|
|
|
1167
1271
|
return piiFields;
|
|
1168
1272
|
}
|
|
1169
1273
|
function scrubPiiForDelete(shape, stored) {
|
|
1170
|
-
const result = { ...stored };
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1274
|
+
const result = Array.isArray(stored) ? [...stored] : { ...stored };
|
|
1275
|
+
const setAtPath = (target, path, val) => {
|
|
1276
|
+
let cur = target;
|
|
1277
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1278
|
+
const p = path[i];
|
|
1279
|
+
if (p === void 0) return;
|
|
1280
|
+
if (cur[p] === void 0) {
|
|
1281
|
+
const next = path[i + 1];
|
|
1282
|
+
cur[p] = typeof next === "number" ? [] : {};
|
|
1283
|
+
}
|
|
1284
|
+
cur = cur[p];
|
|
1285
|
+
}
|
|
1286
|
+
const last = path[path.length - 1];
|
|
1287
|
+
if (last === void 0) return;
|
|
1288
|
+
cur[last] = val;
|
|
1289
|
+
};
|
|
1290
|
+
const visit = (def, host, path, keyName) => {
|
|
1291
|
+
if (!def) return;
|
|
1292
|
+
const applyPath = (targetKey) => {
|
|
1293
|
+
const last = path[path.length - 1];
|
|
1294
|
+
if (last === void 0) return;
|
|
1295
|
+
if (typeof last === "number") {
|
|
1296
|
+
setAtPath(result, [...path, targetKey], null);
|
|
1297
|
+
} else {
|
|
1298
|
+
setAtPath(result, [...path.slice(0, -1), targetKey], null);
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1174
1301
|
if (def._pii?.action === "encrypt") {
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1302
|
+
const targetKey = `${keyName}Encrypted`;
|
|
1303
|
+
if (host && Object.prototype.hasOwnProperty.call(host, targetKey)) {
|
|
1304
|
+
applyPath(targetKey);
|
|
1305
|
+
}
|
|
1306
|
+
return;
|
|
1180
1307
|
}
|
|
1308
|
+
if (def._pii?.action === "hash") {
|
|
1309
|
+
const targetKey = `${keyName}Hash`;
|
|
1310
|
+
if (host && Object.prototype.hasOwnProperty.call(host, targetKey)) {
|
|
1311
|
+
applyPath(targetKey);
|
|
1312
|
+
}
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (def._pii?.action === "clear") {
|
|
1316
|
+
if (host && Object.prototype.hasOwnProperty.call(host, keyName)) {
|
|
1317
|
+
setAtPath(result, path, null);
|
|
1318
|
+
}
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
if (def.type === "object" && def._shape) {
|
|
1322
|
+
const obj = host && Object.prototype.hasOwnProperty.call(host, keyName) ? host[keyName] : host;
|
|
1323
|
+
for (const [k, childDef] of Object.entries(def._shape)) {
|
|
1324
|
+
visit(childDef, obj, [...path, k], k);
|
|
1325
|
+
}
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
if (def.type === "array" && def.itemType) {
|
|
1329
|
+
const arr = host?.[keyName];
|
|
1330
|
+
if (Array.isArray(arr)) {
|
|
1331
|
+
arr.forEach(
|
|
1332
|
+
(item, idx) => visit(def.itemType, item, [...path, idx], keyName)
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (def.type === "ref") {
|
|
1338
|
+
const refShape = def._shape;
|
|
1339
|
+
const ref = host?.[keyName];
|
|
1340
|
+
if (refShape && ref) {
|
|
1341
|
+
for (const [k, childDef] of Object.entries(refShape)) {
|
|
1342
|
+
visit(childDef, ref, [...path, k], k);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
for (const key in shape) {
|
|
1348
|
+
visit(shape[key], stored, [key], key);
|
|
1181
1349
|
}
|
|
1182
1350
|
return result;
|
|
1183
1351
|
}
|
|
1184
1352
|
|
|
1185
1353
|
// src/schema.ts
|
|
1186
1354
|
var globalSchemaRegistry = /* @__PURE__ */ new Map();
|
|
1355
|
+
function deepClone(value) {
|
|
1356
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
1357
|
+
const cloneAny = (val) => {
|
|
1358
|
+
if (val === null || typeof val !== "object") return val;
|
|
1359
|
+
if (seen.has(val)) throw new TypeError("Cannot clone circular structures");
|
|
1360
|
+
if (val instanceof Date) return new Date(val.getTime());
|
|
1361
|
+
if (Array.isArray(val)) {
|
|
1362
|
+
const arr = [];
|
|
1363
|
+
seen.set(val, arr);
|
|
1364
|
+
val.forEach((item) => arr.push(cloneAny(item)));
|
|
1365
|
+
return arr;
|
|
1366
|
+
}
|
|
1367
|
+
const out = {};
|
|
1368
|
+
seen.set(val, out);
|
|
1369
|
+
for (const [k, v] of Object.entries(val)) out[k] = cloneAny(v);
|
|
1370
|
+
return out;
|
|
1371
|
+
};
|
|
1372
|
+
return cloneAny(value);
|
|
1373
|
+
}
|
|
1187
1374
|
function cmpSemver(a, b) {
|
|
1188
1375
|
const pa = a.split(".").map((n) => parseInt(n, 10));
|
|
1189
1376
|
const pb = b.split(".").map((n) => parseInt(n, 10));
|
|
@@ -1242,8 +1429,17 @@ function getEnumValues(def) {
|
|
|
1242
1429
|
);
|
|
1243
1430
|
return ok ? src : void 0;
|
|
1244
1431
|
}
|
|
1432
|
+
function applyDefault(def, current) {
|
|
1433
|
+
if ((current === void 0 || current === null) && typeof def?.getDefault === "function") {
|
|
1434
|
+
const next = def.getDefault();
|
|
1435
|
+
if (next !== void 0) {
|
|
1436
|
+
return { value: next, applied: true };
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return { value: current, applied: false };
|
|
1440
|
+
}
|
|
1245
1441
|
function isOptional(def) {
|
|
1246
|
-
return
|
|
1442
|
+
return def?.isRequired === false;
|
|
1247
1443
|
}
|
|
1248
1444
|
function getValidator(def) {
|
|
1249
1445
|
return def?._validator ?? void 0;
|
|
@@ -1295,10 +1491,18 @@ function validateStringField(parentKey, key, value, def, errors) {
|
|
|
1295
1491
|
}
|
|
1296
1492
|
}
|
|
1297
1493
|
}
|
|
1298
|
-
function validateNumberField(parentKey, key, value,
|
|
1299
|
-
const
|
|
1494
|
+
function validateNumberField(parentKey, key, value, def, errors) {
|
|
1495
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1300
1496
|
if (typeof value !== "number") {
|
|
1301
|
-
errors.push(`Field ${
|
|
1497
|
+
errors.push(`Field ${path} must be number`);
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
const enumValues = getEnumValues(def);
|
|
1501
|
+
if (Array.isArray(enumValues)) {
|
|
1502
|
+
const enumError = validateEnum(path, value, enumValues);
|
|
1503
|
+
if (enumError) {
|
|
1504
|
+
errors.push(enumError);
|
|
1505
|
+
}
|
|
1302
1506
|
}
|
|
1303
1507
|
}
|
|
1304
1508
|
function validateBooleanField(parentKey, key, value, _def, errors) {
|
|
@@ -1307,9 +1511,15 @@ function validateBooleanField(parentKey, key, value, _def, errors) {
|
|
|
1307
1511
|
errors.push(`Field ${path} must be boolean`);
|
|
1308
1512
|
}
|
|
1309
1513
|
}
|
|
1310
|
-
function validateObjectChildren(parentKey, obj, shape, errors) {
|
|
1514
|
+
function validateObjectChildren(parentKey, obj, shape, errors, existing, piiEnforcement, logger) {
|
|
1311
1515
|
for (const [childKey, childDef] of Object.entries(shape)) {
|
|
1312
|
-
|
|
1516
|
+
let childValue = obj[childKey];
|
|
1517
|
+
const existingChild = existing && typeof existing === "object" ? existing[childKey] : void 0;
|
|
1518
|
+
const { value: withDefault, applied } = applyDefault(childDef, childValue);
|
|
1519
|
+
if (applied) {
|
|
1520
|
+
obj[childKey] = withDefault;
|
|
1521
|
+
childValue = withDefault;
|
|
1522
|
+
}
|
|
1313
1523
|
const { missing } = checkMissingRequired(
|
|
1314
1524
|
parentKey,
|
|
1315
1525
|
childKey,
|
|
@@ -1318,6 +1528,25 @@ function validateObjectChildren(parentKey, obj, shape, errors) {
|
|
|
1318
1528
|
errors
|
|
1319
1529
|
);
|
|
1320
1530
|
if (missing) continue;
|
|
1531
|
+
const { immutableViolation } = checkImmutable(
|
|
1532
|
+
parentKey,
|
|
1533
|
+
childKey,
|
|
1534
|
+
childValue,
|
|
1535
|
+
childDef,
|
|
1536
|
+
existing,
|
|
1537
|
+
errors
|
|
1538
|
+
);
|
|
1539
|
+
if (immutableViolation) continue;
|
|
1540
|
+
const { shortCircuit } = enforcePIIField(
|
|
1541
|
+
parentKey,
|
|
1542
|
+
childKey,
|
|
1543
|
+
childValue,
|
|
1544
|
+
childDef,
|
|
1545
|
+
piiEnforcement,
|
|
1546
|
+
errors,
|
|
1547
|
+
logger
|
|
1548
|
+
);
|
|
1549
|
+
if (shortCircuit) continue;
|
|
1321
1550
|
const { invalid } = runCustomValidator(
|
|
1322
1551
|
parentKey,
|
|
1323
1552
|
childKey,
|
|
@@ -1326,17 +1555,27 @@ function validateObjectChildren(parentKey, obj, shape, errors) {
|
|
|
1326
1555
|
errors
|
|
1327
1556
|
);
|
|
1328
1557
|
if (invalid) continue;
|
|
1329
|
-
validateByType(
|
|
1558
|
+
validateByType(
|
|
1559
|
+
parentKey,
|
|
1560
|
+
childKey,
|
|
1561
|
+
childValue,
|
|
1562
|
+
childDef,
|
|
1563
|
+
errors,
|
|
1564
|
+
existingChild,
|
|
1565
|
+
piiEnforcement,
|
|
1566
|
+
logger
|
|
1567
|
+
);
|
|
1330
1568
|
}
|
|
1331
1569
|
}
|
|
1332
|
-
function validateObjectField(parentKey, key, value, def, errors) {
|
|
1570
|
+
function validateObjectField(parentKey, key, value, def, errors, existing, piiEnforcement, logger) {
|
|
1333
1571
|
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1334
1572
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1335
1573
|
errors.push(`Field ${path} must be object`);
|
|
1336
1574
|
return;
|
|
1337
1575
|
}
|
|
1338
1576
|
const objShape = getShape(def);
|
|
1339
|
-
if (objShape)
|
|
1577
|
+
if (objShape)
|
|
1578
|
+
validateObjectChildren(path, value, objShape, errors, existing, piiEnforcement, logger);
|
|
1340
1579
|
}
|
|
1341
1580
|
function validateArrayOfStrings(parentKey, key, arr, itemDef, errors) {
|
|
1342
1581
|
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
@@ -1351,6 +1590,14 @@ function validateArrayOfStrings(parentKey, key, arr, itemDef, errors) {
|
|
|
1351
1590
|
errors.push(enumError);
|
|
1352
1591
|
}
|
|
1353
1592
|
}
|
|
1593
|
+
const validator = getValidator(itemDef);
|
|
1594
|
+
if (validator) {
|
|
1595
|
+
arr.forEach((v, idx) => {
|
|
1596
|
+
if (!validator(v)) {
|
|
1597
|
+
errors.push(`Invalid value for field: ${path}[${idx}]`);
|
|
1598
|
+
}
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1354
1601
|
}
|
|
1355
1602
|
function validateArrayOfNumbers(parentKey, key, arr, itemDef, errors) {
|
|
1356
1603
|
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
@@ -1364,14 +1611,30 @@ function validateArrayOfNumbers(parentKey, key, arr, itemDef, errors) {
|
|
|
1364
1611
|
errors.push(enumError);
|
|
1365
1612
|
}
|
|
1366
1613
|
}
|
|
1614
|
+
const validator = getValidator(itemDef);
|
|
1615
|
+
if (validator) {
|
|
1616
|
+
arr.forEach((v, idx) => {
|
|
1617
|
+
if (!validator(v)) {
|
|
1618
|
+
errors.push(`Invalid value for field: ${path}[${idx}]`);
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1367
1622
|
}
|
|
1368
1623
|
function validateArrayOfBooleans(parentKey, key, arr, _itemDef, errors) {
|
|
1369
1624
|
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1370
1625
|
if (!arr.every((v) => typeof v === "boolean")) {
|
|
1371
1626
|
errors.push(`Field ${path} must be boolean[]`);
|
|
1372
1627
|
}
|
|
1628
|
+
const validator = getValidator(_itemDef);
|
|
1629
|
+
if (validator) {
|
|
1630
|
+
arr.forEach((v, idx) => {
|
|
1631
|
+
if (!validator(v)) {
|
|
1632
|
+
errors.push(`Invalid value for field: ${path}[${idx}]`);
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1373
1636
|
}
|
|
1374
|
-
function validateArrayOfObjects(parentKey, key, arr, itemDef, errors) {
|
|
1637
|
+
function validateArrayOfObjects(parentKey, key, arr, itemDef, errors, existing, piiEnforcement, logger) {
|
|
1375
1638
|
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1376
1639
|
if (!Array.isArray(arr) || !arr.every((v) => typeof v === "object" && v !== null && !Array.isArray(v))) {
|
|
1377
1640
|
errors.push(`Field ${path} must be object[]`);
|
|
@@ -1381,8 +1644,18 @@ function validateArrayOfObjects(parentKey, key, arr, itemDef, errors) {
|
|
|
1381
1644
|
if (!itemShape) return;
|
|
1382
1645
|
arr.forEach((item, idx) => {
|
|
1383
1646
|
const itemParent = `${path}[${idx}]`;
|
|
1647
|
+
const existingItem = Array.isArray(existing) ? existing[idx] : void 0;
|
|
1384
1648
|
for (const [childKey, childDef] of Object.entries(itemShape)) {
|
|
1385
|
-
|
|
1649
|
+
let childValue = item[childKey];
|
|
1650
|
+
const existingChild = existingItem && typeof existingItem === "object" ? existingItem[childKey] : void 0;
|
|
1651
|
+
const { value: withDefault, applied } = applyDefault(
|
|
1652
|
+
childDef,
|
|
1653
|
+
childValue
|
|
1654
|
+
);
|
|
1655
|
+
if (applied) {
|
|
1656
|
+
item[childKey] = withDefault;
|
|
1657
|
+
childValue = withDefault;
|
|
1658
|
+
}
|
|
1386
1659
|
const { missing } = checkMissingRequired(
|
|
1387
1660
|
itemParent,
|
|
1388
1661
|
childKey,
|
|
@@ -1391,6 +1664,25 @@ function validateArrayOfObjects(parentKey, key, arr, itemDef, errors) {
|
|
|
1391
1664
|
errors
|
|
1392
1665
|
);
|
|
1393
1666
|
if (missing) continue;
|
|
1667
|
+
const { immutableViolation } = checkImmutable(
|
|
1668
|
+
itemParent,
|
|
1669
|
+
childKey,
|
|
1670
|
+
childValue,
|
|
1671
|
+
childDef,
|
|
1672
|
+
existingItem,
|
|
1673
|
+
errors
|
|
1674
|
+
);
|
|
1675
|
+
if (immutableViolation) continue;
|
|
1676
|
+
const { shortCircuit } = enforcePIIField(
|
|
1677
|
+
itemParent,
|
|
1678
|
+
childKey,
|
|
1679
|
+
childValue,
|
|
1680
|
+
childDef,
|
|
1681
|
+
piiEnforcement,
|
|
1682
|
+
errors,
|
|
1683
|
+
logger
|
|
1684
|
+
);
|
|
1685
|
+
if (shortCircuit) continue;
|
|
1394
1686
|
const { invalid } = runCustomValidator(
|
|
1395
1687
|
itemParent,
|
|
1396
1688
|
childKey,
|
|
@@ -1399,11 +1691,20 @@ function validateArrayOfObjects(parentKey, key, arr, itemDef, errors) {
|
|
|
1399
1691
|
errors
|
|
1400
1692
|
);
|
|
1401
1693
|
if (invalid) continue;
|
|
1402
|
-
validateByType(
|
|
1694
|
+
validateByType(
|
|
1695
|
+
itemParent,
|
|
1696
|
+
childKey,
|
|
1697
|
+
childValue,
|
|
1698
|
+
childDef,
|
|
1699
|
+
errors,
|
|
1700
|
+
existingChild,
|
|
1701
|
+
piiEnforcement,
|
|
1702
|
+
logger
|
|
1703
|
+
);
|
|
1403
1704
|
}
|
|
1404
1705
|
});
|
|
1405
1706
|
}
|
|
1406
|
-
function validateArrayField(parentKey, key, value, def, errors) {
|
|
1707
|
+
function validateArrayField(parentKey, key, value, def, errors, existing, piiEnforcement, logger) {
|
|
1407
1708
|
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1408
1709
|
if (!Array.isArray(value)) {
|
|
1409
1710
|
errors.push(`Field ${key} must be an array`);
|
|
@@ -1417,7 +1718,16 @@ function validateArrayField(parentKey, key, value, def, errors) {
|
|
|
1417
1718
|
if (itemType === "boolean")
|
|
1418
1719
|
return validateArrayOfBooleans(parentKey, key, value, def.itemType, errors);
|
|
1419
1720
|
if (itemType === "object")
|
|
1420
|
-
return validateArrayOfObjects(
|
|
1721
|
+
return validateArrayOfObjects(
|
|
1722
|
+
parentKey,
|
|
1723
|
+
key,
|
|
1724
|
+
value,
|
|
1725
|
+
def.itemType,
|
|
1726
|
+
errors,
|
|
1727
|
+
existing,
|
|
1728
|
+
piiEnforcement,
|
|
1729
|
+
logger
|
|
1730
|
+
);
|
|
1421
1731
|
if (itemType === "ref") {
|
|
1422
1732
|
const expectedType = def.itemType.refType;
|
|
1423
1733
|
value.forEach((ref, idx) => {
|
|
@@ -1429,24 +1739,63 @@ function validateArrayField(parentKey, key, value, def, errors) {
|
|
|
1429
1739
|
});
|
|
1430
1740
|
const refShape = getShape(def.itemType);
|
|
1431
1741
|
if (refShape) {
|
|
1742
|
+
const existingRefs = Array.isArray(existing) ? existing : [];
|
|
1432
1743
|
value.forEach((ref, idx) => {
|
|
1433
1744
|
if (ref && typeof ref === "object" && ref !== null) {
|
|
1745
|
+
const existingRef = existingRefs[idx];
|
|
1434
1746
|
for (const [childKey, childDef] of Object.entries(refShape)) {
|
|
1435
|
-
const
|
|
1747
|
+
const childPath = `${path}[${idx}].${childKey}`;
|
|
1748
|
+
let childValue = ref[childKey];
|
|
1749
|
+
const existingChild = existingRef && typeof existingRef === "object" ? existingRef[childKey] : void 0;
|
|
1750
|
+
const { value: withDefault, applied } = applyDefault(
|
|
1751
|
+
childDef,
|
|
1752
|
+
childValue
|
|
1753
|
+
);
|
|
1754
|
+
if (applied) {
|
|
1755
|
+
ref[childKey] = withDefault;
|
|
1756
|
+
childValue = withDefault;
|
|
1757
|
+
}
|
|
1436
1758
|
if ((childValue === void 0 || childValue === null) && !isOptional(childDef)) {
|
|
1437
|
-
errors.push(
|
|
1438
|
-
`Missing required field: ${path}[${idx}].${childKey}`
|
|
1439
|
-
);
|
|
1759
|
+
errors.push(`Missing required field: ${childPath}`);
|
|
1440
1760
|
continue;
|
|
1441
1761
|
}
|
|
1762
|
+
const { immutableViolation } = checkImmutable(
|
|
1763
|
+
`${path}[${idx}]`,
|
|
1764
|
+
childKey,
|
|
1765
|
+
childValue,
|
|
1766
|
+
childDef,
|
|
1767
|
+
existingRef,
|
|
1768
|
+
errors
|
|
1769
|
+
);
|
|
1770
|
+
if (immutableViolation) continue;
|
|
1771
|
+
const { shortCircuit } = enforcePIIField(
|
|
1772
|
+
`${path}[${idx}]`,
|
|
1773
|
+
childKey,
|
|
1774
|
+
childValue,
|
|
1775
|
+
childDef,
|
|
1776
|
+
piiEnforcement,
|
|
1777
|
+
errors,
|
|
1778
|
+
logger
|
|
1779
|
+
);
|
|
1780
|
+
if (shortCircuit) continue;
|
|
1442
1781
|
const childValidator = getValidator(childDef);
|
|
1443
1782
|
if (childValidator && childValue !== void 0 && childValue !== null) {
|
|
1444
1783
|
const valid = childValidator(childValue);
|
|
1445
|
-
if (!valid)
|
|
1446
|
-
errors.push(
|
|
1447
|
-
|
|
1448
|
-
|
|
1784
|
+
if (!valid) {
|
|
1785
|
+
errors.push(`Invalid value for field: ${childPath}`);
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1449
1788
|
}
|
|
1789
|
+
validateByType(
|
|
1790
|
+
`${path}[${idx}]`,
|
|
1791
|
+
childKey,
|
|
1792
|
+
childValue,
|
|
1793
|
+
childDef,
|
|
1794
|
+
errors,
|
|
1795
|
+
existingChild,
|
|
1796
|
+
piiEnforcement,
|
|
1797
|
+
logger
|
|
1798
|
+
);
|
|
1450
1799
|
}
|
|
1451
1800
|
}
|
|
1452
1801
|
});
|
|
@@ -1455,13 +1804,20 @@ function validateArrayField(parentKey, key, value, def, errors) {
|
|
|
1455
1804
|
}
|
|
1456
1805
|
errors.push(`Field ${path} has unsupported array item type`);
|
|
1457
1806
|
}
|
|
1458
|
-
function validateRefField(parentKey, key, value,
|
|
1807
|
+
function validateRefField(parentKey, key, value, def, errors, _existing, _piiEnforcement, _logger) {
|
|
1808
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1459
1809
|
if (typeof value !== "object" || value === null || typeof value.type !== "string" || typeof value.id !== "string") {
|
|
1460
|
-
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1461
1810
|
errors.push(`Field ${path} must be { type: string; id: string }`);
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
const expectedType = def.refType;
|
|
1814
|
+
if (expectedType && value.type !== expectedType) {
|
|
1815
|
+
errors.push(
|
|
1816
|
+
`Field ${path} must reference type: ${expectedType} (got ${value.type})`
|
|
1817
|
+
);
|
|
1462
1818
|
}
|
|
1463
1819
|
}
|
|
1464
|
-
function validateByType(parentKey, key, value, def, errors) {
|
|
1820
|
+
function validateByType(parentKey, key, value, def, errors, existing, piiEnforcement, logger) {
|
|
1465
1821
|
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1466
1822
|
switch (def.type) {
|
|
1467
1823
|
case "string":
|
|
@@ -1471,11 +1827,29 @@ function validateByType(parentKey, key, value, def, errors) {
|
|
|
1471
1827
|
case "boolean":
|
|
1472
1828
|
return validateBooleanField(parentKey, key, value, def, errors);
|
|
1473
1829
|
case "object":
|
|
1474
|
-
return validateObjectField(
|
|
1830
|
+
return validateObjectField(
|
|
1831
|
+
parentKey,
|
|
1832
|
+
key,
|
|
1833
|
+
value,
|
|
1834
|
+
def,
|
|
1835
|
+
errors,
|
|
1836
|
+
existing,
|
|
1837
|
+
piiEnforcement,
|
|
1838
|
+
logger
|
|
1839
|
+
);
|
|
1475
1840
|
case "array":
|
|
1476
|
-
return validateArrayField(
|
|
1841
|
+
return validateArrayField(
|
|
1842
|
+
parentKey,
|
|
1843
|
+
key,
|
|
1844
|
+
value,
|
|
1845
|
+
def,
|
|
1846
|
+
errors,
|
|
1847
|
+
existing,
|
|
1848
|
+
piiEnforcement,
|
|
1849
|
+
logger
|
|
1850
|
+
);
|
|
1477
1851
|
case "ref":
|
|
1478
|
-
return validateRefField(parentKey, key, value, def, errors);
|
|
1852
|
+
return validateRefField(parentKey, key, value, def, errors, existing, piiEnforcement, logger);
|
|
1479
1853
|
default:
|
|
1480
1854
|
errors.push(`Unknown type for field ${path}: ${def.type}`);
|
|
1481
1855
|
}
|
|
@@ -1506,10 +1880,11 @@ function createSchema(_shape, entityType, options = {
|
|
|
1506
1880
|
validate(input, existing) {
|
|
1507
1881
|
const errors = [];
|
|
1508
1882
|
const result = {};
|
|
1883
|
+
const piiMode = options.piiEnforcement ?? "none";
|
|
1509
1884
|
if (typeof input !== "object" || input === null) {
|
|
1510
1885
|
return { valid: false, errors: ["Input must be an object"] };
|
|
1511
1886
|
}
|
|
1512
|
-
const working = { ...input };
|
|
1887
|
+
const working = typeof input === "object" && input !== null ? deepClone(input) : { ...input };
|
|
1513
1888
|
if (working.type == null) working.type = entityType;
|
|
1514
1889
|
if (working.version == null) working.version = version;
|
|
1515
1890
|
const fromVersion = String(working.version ?? "0.0.0");
|
|
@@ -1533,11 +1908,17 @@ function createSchema(_shape, entityType, options = {
|
|
|
1533
1908
|
}
|
|
1534
1909
|
for (const key in schema._shape) {
|
|
1535
1910
|
const def = schema._shape[key];
|
|
1536
|
-
|
|
1911
|
+
let value = working[key];
|
|
1912
|
+
const existingField = existing && typeof existing === "object" ? existing[key] : void 0;
|
|
1537
1913
|
if (!def) {
|
|
1538
1914
|
errors.push(`Field definition missing for: ${key}`);
|
|
1539
1915
|
continue;
|
|
1540
1916
|
}
|
|
1917
|
+
const { value: withDefault, applied } = applyDefault(def, value);
|
|
1918
|
+
if (applied) {
|
|
1919
|
+
working[key] = withDefault;
|
|
1920
|
+
value = withDefault;
|
|
1921
|
+
}
|
|
1541
1922
|
const { missing } = checkMissingRequired("", key, value, def, errors);
|
|
1542
1923
|
if (missing) continue;
|
|
1543
1924
|
const { immutableViolation } = checkImmutable(
|
|
@@ -1554,7 +1935,7 @@ function createSchema(_shape, entityType, options = {
|
|
|
1554
1935
|
key,
|
|
1555
1936
|
value,
|
|
1556
1937
|
def,
|
|
1557
|
-
|
|
1938
|
+
piiMode,
|
|
1558
1939
|
errors,
|
|
1559
1940
|
console
|
|
1560
1941
|
);
|
|
@@ -1569,7 +1950,7 @@ function createSchema(_shape, entityType, options = {
|
|
|
1569
1950
|
localErrors
|
|
1570
1951
|
);
|
|
1571
1952
|
if (!invalid) {
|
|
1572
|
-
validateByType("", key, val, def, localErrors);
|
|
1953
|
+
validateByType("", key, val, def, localErrors, existingField, piiMode, console);
|
|
1573
1954
|
}
|
|
1574
1955
|
return localErrors;
|
|
1575
1956
|
};
|
|
@@ -1691,13 +2072,20 @@ function createSchema(_shape, entityType, options = {
|
|
|
1691
2072
|
log?.(`Skipping field ${key} (not in onlyFields)`);
|
|
1692
2073
|
continue;
|
|
1693
2074
|
}
|
|
1694
|
-
const refType = def.refType;
|
|
1695
2075
|
const autoValidate = def.autoValidate !== false;
|
|
1696
2076
|
const refPolicy = def.refPolicy ?? "eager";
|
|
1697
2077
|
const value = entity[key];
|
|
1698
2078
|
if (!value) continue;
|
|
1699
2079
|
if (def.type === "ref") {
|
|
2080
|
+
const refType = def.refType;
|
|
2081
|
+
if (!refType)
|
|
2082
|
+
throw new Error(`Missing refType for reference field ${key}`);
|
|
1700
2083
|
const ref = value;
|
|
2084
|
+
if (ref.type && ref.type !== refType) {
|
|
2085
|
+
throw new Error(
|
|
2086
|
+
`Reference type mismatch for field ${key}: expected ${refType}, got ${ref.type}`
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
1701
2089
|
const target = await options2.resolveEntity(refType, ref.id);
|
|
1702
2090
|
if (!target)
|
|
1703
2091
|
throw new Error(
|
|
@@ -1714,8 +2102,16 @@ function createSchema(_shape, entityType, options = {
|
|
|
1714
2102
|
}
|
|
1715
2103
|
}
|
|
1716
2104
|
} else if (def.type === "array" && def.itemType?.type === "ref") {
|
|
2105
|
+
const refType = def.itemType.refType;
|
|
2106
|
+
if (!refType)
|
|
2107
|
+
throw new Error(`Missing refType for reference array field ${key}`);
|
|
1717
2108
|
const refs = value;
|
|
1718
2109
|
for (const ref of refs) {
|
|
2110
|
+
if (ref.type && ref.type !== refType) {
|
|
2111
|
+
throw new Error(
|
|
2112
|
+
`Reference type mismatch for field ${key}: expected ${refType}, got ${ref.type}`
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
1719
2115
|
const target = await options2.resolveEntity(refType, ref.id);
|
|
1720
2116
|
if (!target)
|
|
1721
2117
|
throw new Error(
|
|
@@ -1806,12 +2202,16 @@ function createSchema(_shape, entityType, options = {
|
|
|
1806
2202
|
for (const [key, def] of Object.entries(schema._shape)) {
|
|
1807
2203
|
description[key] = {
|
|
1808
2204
|
type: def.type,
|
|
1809
|
-
optional:
|
|
2205
|
+
optional: isOptional(def),
|
|
2206
|
+
immutable: !!def.isImmutable,
|
|
2207
|
+
system: !!def.isSystem,
|
|
1810
2208
|
description: def._description ?? "",
|
|
1811
2209
|
version: def._version ?? "",
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2210
|
+
deprecated: def.deprecated ?? false,
|
|
2211
|
+
deprecatedVersion: def.deprecatedVersion === void 0 ? null : def.deprecatedVersion,
|
|
2212
|
+
enum: getEnumValues(def) ?? null,
|
|
2213
|
+
refType: def.refType ?? null,
|
|
2214
|
+
pii: !def._pii || def._pii.classification === "none" ? null : def._pii
|
|
1815
2215
|
};
|
|
1816
2216
|
}
|
|
1817
2217
|
return {
|
|
@@ -1831,6 +2231,20 @@ function getSchemaForType(type) {
|
|
|
1831
2231
|
function getAllSchemas() {
|
|
1832
2232
|
return Array.from(globalSchemaRegistry.values());
|
|
1833
2233
|
}
|
|
2234
|
+
function renderSchemaDescription(schema) {
|
|
2235
|
+
const meta = schema.describe();
|
|
2236
|
+
return {
|
|
2237
|
+
title: `${meta.entityType} (v${meta.version})`,
|
|
2238
|
+
fields: Object.entries(meta.shape).map(([name, def]) => ({
|
|
2239
|
+
name,
|
|
2240
|
+
type: def.type,
|
|
2241
|
+
optional: def.optional,
|
|
2242
|
+
description: def.description,
|
|
2243
|
+
deprecated: def.deprecated,
|
|
2244
|
+
pii: def.pii ? def.pii.classification : void 0
|
|
2245
|
+
}))
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
1834
2248
|
|
|
1835
2249
|
// src/components.ts
|
|
1836
2250
|
var componentSchemaRegistry = /* @__PURE__ */ new Map();
|
|
@@ -1855,6 +2269,7 @@ function getAllComponentSchemas() {
|
|
|
1855
2269
|
}
|
|
1856
2270
|
export {
|
|
1857
2271
|
FieldBuilder,
|
|
2272
|
+
IsoLanguageCode,
|
|
1858
2273
|
createComponentSchema,
|
|
1859
2274
|
createSchema,
|
|
1860
2275
|
field,
|
|
@@ -1862,13 +2277,23 @@ export {
|
|
|
1862
2277
|
getAllSchemas,
|
|
1863
2278
|
getComponentSchema,
|
|
1864
2279
|
getSchemaForType,
|
|
2280
|
+
isExtensionSingleton,
|
|
2281
|
+
isExtensionSubtag,
|
|
2282
|
+
isIsoLanguageCode,
|
|
2283
|
+
isPrivateUseSingleton,
|
|
2284
|
+
isPrivateUseSubtag,
|
|
2285
|
+
isRegionSubtag,
|
|
2286
|
+
isScriptSubtag,
|
|
2287
|
+
isVariantSubtag,
|
|
1865
2288
|
isoCountryCodes,
|
|
1866
2289
|
isoCurrencyCodes,
|
|
1867
2290
|
registerComponentSchema,
|
|
2291
|
+
renderSchemaDescription,
|
|
1868
2292
|
validateCountryCode,
|
|
1869
2293
|
validateCurrencyCode,
|
|
1870
2294
|
validateDateTimeISO,
|
|
1871
2295
|
validateEmail,
|
|
2296
|
+
validateLanguage,
|
|
1872
2297
|
validateName,
|
|
1873
2298
|
validatePercentage,
|
|
1874
2299
|
validatePhone,
|