@plasius/schema 1.2.3 → 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 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
  }