@plasius/schema 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 result = {};
1107
- for (const key in shape) {
1108
- const def = shape[key];
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
- result[key + "Encrypted"] = encryptFn(value);
1113
- } else if (def._pii?.action === "hash") {
1114
- result[key + "Hash"] = hashFn(value);
1115
- } else {
1116
- result[key] = value;
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
- const def = shape[key];
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 output = {};
1136
- for (const key in shape) {
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") continue;
1142
- if (handling === "redact") {
1143
- output[key] = "[REDACTED]";
1144
- } else if (handling === "pseudonym") {
1145
- output[key] = pseudonymFn(value);
1146
- } else {
1147
- output[key] = value;
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
- for (const key in shape) {
1172
- const def = shape[key];
1173
- if (!def) continue;
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
- result[key + "Encrypted"] = null;
1176
- } else if (def._pii?.action === "hash") {
1177
- result[key + "Hash"] = null;
1178
- } else if (def._pii?.action === "clear") {
1179
- result[key] = null;
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 (def?.isRequired ?? false) === false;
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, _def, errors) {
1299
- const enumPath = parentKey ? `${parentKey}.${key}` : key;
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 ${enumPath} must be number`);
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
- const childValue = obj[childKey];
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(parentKey, childKey, childValue, childDef, errors);
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) validateObjectChildren(path, value, objShape, errors);
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
- const childValue = item[childKey];
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(itemParent, childKey, childValue, childDef, errors);
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(parentKey, key, value, def.itemType, errors);
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 childValue = ref[childKey];
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
- `Invalid value for field: ${path}[${idx}].${childKey}`
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, _def, errors) {
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(parentKey, key, value, def, errors);
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(parentKey, key, value, def, errors);
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
- const value = working[key];
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
- options.piiEnforcement ?? "none",
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: !!def.optional,
2205
+ optional: isOptional(def),
2206
+ immutable: !!def.isImmutable,
2207
+ system: !!def.isSystem,
1810
2208
  description: def._description ?? "",
1811
2209
  version: def._version ?? "",
1812
- enum: getEnumValues(def),
1813
- refType: def.refType ?? void 0,
1814
- pii: def._pii ?? void 0
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,