@plasius/schema 1.2.5 → 1.2.6
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 +42 -0
- package/dist/index.cjs +123 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +123 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,6 +90,7 @@ const UserFields = {
|
|
|
90
90
|
Common methods (non‑exhaustive): `.required()`, `.optional()`, `.default(v|fn)`, `.description(text)`, and type‑specific helpers like `.email()`, `.uuid()`, `.min()`, `.max()`, `.enum([...])`.
|
|
91
91
|
Defaults are applied during validation when inputs are missing/`undefined`.
|
|
92
92
|
Fields are required by default; call `.optional()` (or provide `.default()`) to allow omission.
|
|
93
|
+
Use `.internal()` or `.exposure("internal")` on fields that the server may validate/store but must not include in client-facing payloads.
|
|
93
94
|
|
|
94
95
|
### 2) Create a **versioned** schema (enforces `type` + `version`)
|
|
95
96
|
|
|
@@ -144,6 +145,47 @@ if (result.valid && result.errors.length == 0) {
|
|
|
144
145
|
- PII strict/warn enforcement runs on nested fields, preventing empty high-PII subfields from slipping through.
|
|
145
146
|
- Validation deep-clones inputs before applying defaults, so caller-provided objects/arrays aren’t mutated and non-JSON-safe values (e.g., `Date`) are preserved.
|
|
146
147
|
|
|
148
|
+
### 3b) Serialize only client-safe fields
|
|
149
|
+
|
|
150
|
+
Schema serialization is separate from validation. `serialize()` keeps only schema-known fields and drops fields marked `.internal()` unless explicitly requested.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const PersistedUserSchema = createSchema(
|
|
154
|
+
{
|
|
155
|
+
id: field.string(),
|
|
156
|
+
partitionKey: field.string().internal(),
|
|
157
|
+
email: field.email(),
|
|
158
|
+
audit: field.object({
|
|
159
|
+
createdBy: field.string().internal(),
|
|
160
|
+
createdAt: field.dateTimeISO(),
|
|
161
|
+
}),
|
|
162
|
+
},
|
|
163
|
+
"persisted-user",
|
|
164
|
+
{ version: "1.0.0", piiEnforcement: "strict" }
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const publicPayload = PersistedUserSchema.serialize({
|
|
168
|
+
type: "persisted-user",
|
|
169
|
+
version: "1.0.0",
|
|
170
|
+
id: "123",
|
|
171
|
+
partitionKey: "tenant-a",
|
|
172
|
+
email: "alice@example.com",
|
|
173
|
+
audit: {
|
|
174
|
+
createdBy: "admin-1",
|
|
175
|
+
createdAt: "2026-03-09T00:00:00.000Z",
|
|
176
|
+
},
|
|
177
|
+
_rid: "cosmos-only",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// => {
|
|
181
|
+
// type: "persisted-user",
|
|
182
|
+
// version: "1.0.0",
|
|
183
|
+
// id: "123",
|
|
184
|
+
// email: "alice@example.com",
|
|
185
|
+
// audit: { createdAt: "2026-03-09T00:00:00.000Z" }
|
|
186
|
+
// }
|
|
187
|
+
```
|
|
188
|
+
|
|
147
189
|
### 4) Version enforcement in action
|
|
148
190
|
|
|
149
191
|
If either `type` or `version` doesn’t match the schema, validation fails.
|
package/dist/index.cjs
CHANGED
|
@@ -86,6 +86,7 @@ var FieldBuilder = class _FieldBuilder {
|
|
|
86
86
|
logHandling: "plain",
|
|
87
87
|
purpose: "an ordinary value"
|
|
88
88
|
};
|
|
89
|
+
_exposure = "public";
|
|
89
90
|
enumValues;
|
|
90
91
|
immutable() {
|
|
91
92
|
this.isImmutable = true;
|
|
@@ -95,6 +96,16 @@ var FieldBuilder = class _FieldBuilder {
|
|
|
95
96
|
this.isSystem = true;
|
|
96
97
|
return this;
|
|
97
98
|
}
|
|
99
|
+
exposure(exposure) {
|
|
100
|
+
this._exposure = exposure;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
internal() {
|
|
104
|
+
return this.exposure("internal");
|
|
105
|
+
}
|
|
106
|
+
public() {
|
|
107
|
+
return this.exposure("public");
|
|
108
|
+
}
|
|
98
109
|
required() {
|
|
99
110
|
this.isRequired = true;
|
|
100
111
|
return this;
|
|
@@ -229,6 +240,7 @@ var FieldBuilder = class _FieldBuilder {
|
|
|
229
240
|
clone._description = this._description;
|
|
230
241
|
clone._version = this._version;
|
|
231
242
|
clone._pii = this._pii;
|
|
243
|
+
clone._exposure = this._exposure;
|
|
232
244
|
clone._validator = this._validator;
|
|
233
245
|
clone._default = this._default;
|
|
234
246
|
clone._upgrade = this._upgrade;
|
|
@@ -1432,6 +1444,94 @@ function deepClone(value) {
|
|
|
1432
1444
|
};
|
|
1433
1445
|
return cloneAny(value);
|
|
1434
1446
|
}
|
|
1447
|
+
function shouldIncludeField(def, options) {
|
|
1448
|
+
return options.includeInternal === true || (def?._exposure ?? "public") !== "internal";
|
|
1449
|
+
}
|
|
1450
|
+
function serializeFieldValue(def, value, options) {
|
|
1451
|
+
if (value === void 0) {
|
|
1452
|
+
return void 0;
|
|
1453
|
+
}
|
|
1454
|
+
if (value === null) {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
switch (def?.type) {
|
|
1458
|
+
case "string":
|
|
1459
|
+
case "number":
|
|
1460
|
+
case "boolean":
|
|
1461
|
+
return deepClone(value);
|
|
1462
|
+
case "ref": {
|
|
1463
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1464
|
+
return deepClone(value);
|
|
1465
|
+
}
|
|
1466
|
+
const shape = getShape(def);
|
|
1467
|
+
if (!shape || Object.keys(shape).length === 0) {
|
|
1468
|
+
return deepClone(value);
|
|
1469
|
+
}
|
|
1470
|
+
const serializedRef = {};
|
|
1471
|
+
if (typeof value.type === "string") {
|
|
1472
|
+
serializedRef.type = value.type;
|
|
1473
|
+
}
|
|
1474
|
+
if (typeof value.id === "string") {
|
|
1475
|
+
serializedRef.id = value.id;
|
|
1476
|
+
}
|
|
1477
|
+
for (const [childKey, childDef] of Object.entries(shape)) {
|
|
1478
|
+
if (!shouldIncludeField(childDef, options)) {
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
if (!Object.prototype.hasOwnProperty.call(value, childKey)) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
const serializedChild = serializeFieldValue(
|
|
1485
|
+
childDef,
|
|
1486
|
+
value[childKey],
|
|
1487
|
+
options
|
|
1488
|
+
);
|
|
1489
|
+
if (serializedChild !== void 0) {
|
|
1490
|
+
serializedRef[childKey] = serializedChild;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return serializedRef;
|
|
1494
|
+
}
|
|
1495
|
+
case "object": {
|
|
1496
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1497
|
+
return deepClone(value);
|
|
1498
|
+
}
|
|
1499
|
+
const shape = getShape(def);
|
|
1500
|
+
if (!shape || Object.keys(shape).length === 0) {
|
|
1501
|
+
return deepClone(value);
|
|
1502
|
+
}
|
|
1503
|
+
const serializedObject = {};
|
|
1504
|
+
for (const [childKey, childDef] of Object.entries(shape)) {
|
|
1505
|
+
if (!shouldIncludeField(childDef, options)) {
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
if (!Object.prototype.hasOwnProperty.call(value, childKey)) {
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
const serializedChild = serializeFieldValue(
|
|
1512
|
+
childDef,
|
|
1513
|
+
value[childKey],
|
|
1514
|
+
options
|
|
1515
|
+
);
|
|
1516
|
+
if (serializedChild !== void 0) {
|
|
1517
|
+
serializedObject[childKey] = serializedChild;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return serializedObject;
|
|
1521
|
+
}
|
|
1522
|
+
case "array": {
|
|
1523
|
+
if (!Array.isArray(value)) {
|
|
1524
|
+
return deepClone(value);
|
|
1525
|
+
}
|
|
1526
|
+
if (!def.itemType) {
|
|
1527
|
+
return deepClone(value);
|
|
1528
|
+
}
|
|
1529
|
+
return value.map((item) => serializeFieldValue(def.itemType, item, options));
|
|
1530
|
+
}
|
|
1531
|
+
default:
|
|
1532
|
+
return deepClone(value);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1435
1535
|
function cmpSemver(a, b) {
|
|
1436
1536
|
const pa = a.split(".").map((n) => parseInt(n, 10));
|
|
1437
1537
|
const pb = b.split(".").map((n) => parseInt(n, 10));
|
|
@@ -2203,6 +2303,25 @@ function createSchema(_shape, entityType, options = {
|
|
|
2203
2303
|
}
|
|
2204
2304
|
return store;
|
|
2205
2305
|
},
|
|
2306
|
+
serialize(input, options2 = {}) {
|
|
2307
|
+
if (typeof input !== "object" || input === null) {
|
|
2308
|
+
return {};
|
|
2309
|
+
}
|
|
2310
|
+
const serialized = {};
|
|
2311
|
+
for (const [key, def] of Object.entries(schema._shape)) {
|
|
2312
|
+
if (!shouldIncludeField(def, options2)) {
|
|
2313
|
+
continue;
|
|
2314
|
+
}
|
|
2315
|
+
if (!Object.prototype.hasOwnProperty.call(input, key)) {
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
2318
|
+
const value = serializeFieldValue(def, input[key], options2);
|
|
2319
|
+
if (value !== void 0) {
|
|
2320
|
+
serialized[key] = value;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
return serialized;
|
|
2324
|
+
},
|
|
2206
2325
|
/**
|
|
2207
2326
|
* Transforms an input object for persistence by applying PII protection
|
|
2208
2327
|
* according to field annotations (e.g., encryption and hashing).
|
|
@@ -2272,7 +2391,8 @@ function createSchema(_shape, entityType, options = {
|
|
|
2272
2391
|
deprecatedVersion: def.deprecatedVersion === void 0 ? null : def.deprecatedVersion,
|
|
2273
2392
|
enum: getEnumValues(def) ?? null,
|
|
2274
2393
|
refType: def.refType ?? null,
|
|
2275
|
-
pii: !def._pii || def._pii.classification === "none" ? null : def._pii
|
|
2394
|
+
pii: !def._pii || def._pii.classification === "none" ? null : def._pii,
|
|
2395
|
+
exposure: def._exposure ?? "public"
|
|
2276
2396
|
};
|
|
2277
2397
|
}
|
|
2278
2398
|
return {
|
|
@@ -2302,7 +2422,8 @@ function renderSchemaDescription(schema) {
|
|
|
2302
2422
|
optional: def.optional,
|
|
2303
2423
|
description: def.description,
|
|
2304
2424
|
deprecated: def.deprecated,
|
|
2305
|
-
pii: def.pii ? def.pii.classification : void 0
|
|
2425
|
+
pii: def.pii ? def.pii.classification : void 0,
|
|
2426
|
+
exposure: def.exposure
|
|
2306
2427
|
}))
|
|
2307
2428
|
};
|
|
2308
2429
|
}
|