@plasius/schema 1.1.0 → 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/README.md +31 -18
- package/dist/index.cjs +2370 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +627 -0
- package/dist/index.d.ts +627 -0
- package/dist/index.js +2308 -0
- package/dist/index.js.map +1 -0
- package/package.json +18 -6
- package/.eslintrc.cjs +0 -7
- package/.github/workflows/cd.yml +0 -236
- package/.github/workflows/ci.yml +0 -16
- package/.nvmrc +0 -1
- package/.vscode/launch.json +0 -15
- package/CHANGELOG.md +0 -120
- package/CODE_OF_CONDUCT.md +0 -79
- package/CONTRIBUTING.md +0 -201
- package/CONTRIBUTORS.md +0 -27
- package/SECURITY.md +0 -17
- package/docs/adrs/adr-0001: schema.md +0 -45
- package/docs/adrs/adr-template.md +0 -67
- package/legal/CLA-REGISTRY.csv +0 -2
- package/legal/CLA.md +0 -22
- package/legal/CORPORATE_CLA.md +0 -57
- package/legal/INDIVIDUAL_CLA.md +0 -91
- package/sbom.cdx.json +0 -66
- package/src/components.ts +0 -39
- package/src/field.builder.ts +0 -239
- package/src/field.ts +0 -153
- package/src/index.ts +0 -7
- package/src/infer.ts +0 -34
- package/src/pii.ts +0 -165
- package/src/schema.ts +0 -893
- package/src/types.ts +0 -156
- package/src/validation/countryCode.ISO3166.ts +0 -256
- package/src/validation/currencyCode.ISO4217.ts +0 -191
- package/src/validation/dateTime.ISO8601.ts +0 -60
- package/src/validation/email.RFC5322.ts +0 -9
- package/src/validation/generalText.OWASP.ts +0 -39
- package/src/validation/index.ts +0 -13
- package/src/validation/languageCode.BCP47.ts +0 -299
- package/src/validation/name.OWASP.ts +0 -25
- package/src/validation/percentage.ISO80000-1.ts +0 -8
- package/src/validation/phone.E.164.ts +0 -9
- package/src/validation/richtext.OWASP.ts +0 -34
- package/src/validation/url.WHATWG.ts +0 -16
- package/src/validation/user.MS-GOOGLE-APPLE.ts +0 -31
- package/src/validation/uuid.RFC4122.ts +0 -10
- package/src/validation/version.SEMVER2.0.0.ts +0 -10
- package/tests/field.builder.test.ts +0 -81
- package/tests/fields.test.ts +0 -213
- package/tests/pii.test.ts +0 -139
- package/tests/schema.test.ts +0 -501
- package/tests/test-utils.ts +0 -97
- package/tests/validate.test.ts +0 -97
- package/tests/validation.test.ts +0 -98
- package/tsconfig.build.json +0 -19
- package/tsconfig.json +0 -7
- package/tsup.config.ts +0 -10
- package/vitest.config.js +0 -20
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2370 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
FieldBuilder: () => FieldBuilder,
|
|
24
|
+
IsoLanguageCode: () => IsoLanguageCode,
|
|
25
|
+
createComponentSchema: () => createComponentSchema,
|
|
26
|
+
createSchema: () => createSchema,
|
|
27
|
+
field: () => field,
|
|
28
|
+
getAllComponentSchemas: () => getAllComponentSchemas,
|
|
29
|
+
getAllSchemas: () => getAllSchemas,
|
|
30
|
+
getComponentSchema: () => getComponentSchema,
|
|
31
|
+
getSchemaForType: () => getSchemaForType,
|
|
32
|
+
isExtensionSingleton: () => isExtensionSingleton,
|
|
33
|
+
isExtensionSubtag: () => isExtensionSubtag,
|
|
34
|
+
isIsoLanguageCode: () => isIsoLanguageCode,
|
|
35
|
+
isPrivateUseSingleton: () => isPrivateUseSingleton,
|
|
36
|
+
isPrivateUseSubtag: () => isPrivateUseSubtag,
|
|
37
|
+
isRegionSubtag: () => isRegionSubtag,
|
|
38
|
+
isScriptSubtag: () => isScriptSubtag,
|
|
39
|
+
isVariantSubtag: () => isVariantSubtag,
|
|
40
|
+
isoCountryCodes: () => isoCountryCodes,
|
|
41
|
+
isoCurrencyCodes: () => isoCurrencyCodes,
|
|
42
|
+
registerComponentSchema: () => registerComponentSchema,
|
|
43
|
+
renderSchemaDescription: () => renderSchemaDescription,
|
|
44
|
+
validateCountryCode: () => validateCountryCode,
|
|
45
|
+
validateCurrencyCode: () => validateCurrencyCode,
|
|
46
|
+
validateDateTimeISO: () => validateDateTimeISO,
|
|
47
|
+
validateEmail: () => validateEmail,
|
|
48
|
+
validateLanguage: () => validateLanguage,
|
|
49
|
+
validateName: () => validateName,
|
|
50
|
+
validatePercentage: () => validatePercentage,
|
|
51
|
+
validatePhone: () => validatePhone,
|
|
52
|
+
validateRichText: () => validateRichText,
|
|
53
|
+
validateSafeText: () => validateSafeText,
|
|
54
|
+
validateSemVer: () => validateSemVer,
|
|
55
|
+
validateUUID: () => validateUUID,
|
|
56
|
+
validateUrl: () => validateUrl,
|
|
57
|
+
validateUserId: () => validateUserId,
|
|
58
|
+
validateUserIdArray: () => validateUserIdArray
|
|
59
|
+
});
|
|
60
|
+
module.exports = __toCommonJS(index_exports);
|
|
61
|
+
|
|
62
|
+
// src/field.builder.ts
|
|
63
|
+
var FieldBuilder = class _FieldBuilder {
|
|
64
|
+
constructor(type, options = {}) {
|
|
65
|
+
this.type = type;
|
|
66
|
+
this._shape = options.shape;
|
|
67
|
+
this.itemType = options.itemType;
|
|
68
|
+
this.refType = options.refType;
|
|
69
|
+
}
|
|
70
|
+
_type;
|
|
71
|
+
_storageType;
|
|
72
|
+
isSystem = false;
|
|
73
|
+
isImmutable = false;
|
|
74
|
+
isRequired = true;
|
|
75
|
+
_validator;
|
|
76
|
+
_description = "";
|
|
77
|
+
_version = "1.0.0";
|
|
78
|
+
_default;
|
|
79
|
+
_upgrade;
|
|
80
|
+
_shape;
|
|
81
|
+
itemType;
|
|
82
|
+
refType;
|
|
83
|
+
_pii = {
|
|
84
|
+
classification: "none",
|
|
85
|
+
action: "none",
|
|
86
|
+
logHandling: "plain",
|
|
87
|
+
purpose: "an ordinary value"
|
|
88
|
+
};
|
|
89
|
+
enumValues;
|
|
90
|
+
immutable() {
|
|
91
|
+
this.isImmutable = true;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
system() {
|
|
95
|
+
this.isSystem = true;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
required() {
|
|
99
|
+
this.isRequired = true;
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
optional() {
|
|
103
|
+
this.isRequired = false;
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
validator(fn) {
|
|
107
|
+
this._validator = fn;
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
description(desc) {
|
|
111
|
+
this._description = desc;
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
default(value) {
|
|
115
|
+
this._default = value;
|
|
116
|
+
this.isRequired = false;
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Configure an upgrader used when validating older entities against a newer schema.
|
|
121
|
+
* The upgrader receives the current field value and version context, and should
|
|
122
|
+
* return { ok: true, value } with the upgraded value, or { ok: false, error }.
|
|
123
|
+
*/
|
|
124
|
+
upgrade(fn) {
|
|
125
|
+
this._upgrade = fn;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
getDefault() {
|
|
129
|
+
const v = this._default;
|
|
130
|
+
return typeof v === "function" ? v() : v;
|
|
131
|
+
}
|
|
132
|
+
version(ver) {
|
|
133
|
+
this._version = ver;
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
/// PID informs the schema PII handling of the manner in
|
|
137
|
+
/// which to handle data relating to this field.
|
|
138
|
+
PID(pii) {
|
|
139
|
+
this._pii = pii;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
min(min) {
|
|
143
|
+
if (this.type === "number") {
|
|
144
|
+
const prevValidator = this._validator;
|
|
145
|
+
this._validator = (value) => {
|
|
146
|
+
const valid = typeof value === "number" && value >= min;
|
|
147
|
+
return prevValidator ? prevValidator(value) && valid : valid;
|
|
148
|
+
};
|
|
149
|
+
} else if (this.type === "string") {
|
|
150
|
+
const prevValidator = this._validator;
|
|
151
|
+
this._validator = (value) => {
|
|
152
|
+
const valid = typeof value === "string" && value.length >= min;
|
|
153
|
+
return prevValidator ? prevValidator(value) && valid : valid;
|
|
154
|
+
};
|
|
155
|
+
} else if (this.type === "array") {
|
|
156
|
+
const prevValidator = this._validator;
|
|
157
|
+
this._validator = (value) => {
|
|
158
|
+
const valid = Array.isArray(value) && value.length >= min;
|
|
159
|
+
return prevValidator ? prevValidator(value) && valid : valid;
|
|
160
|
+
};
|
|
161
|
+
} else {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"Min is only supported on number, string, or array fields."
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
max(max) {
|
|
169
|
+
if (this.type === "number") {
|
|
170
|
+
const prevValidator = this._validator;
|
|
171
|
+
this._validator = (value) => {
|
|
172
|
+
const valid = typeof value === "number" && value <= max;
|
|
173
|
+
return prevValidator ? prevValidator(value) && valid : valid;
|
|
174
|
+
};
|
|
175
|
+
} else if (this.type === "string") {
|
|
176
|
+
const prevValidator = this._validator;
|
|
177
|
+
this._validator = (value) => {
|
|
178
|
+
const valid = typeof value === "string" && value.length <= max;
|
|
179
|
+
return prevValidator ? prevValidator(value) && valid : valid;
|
|
180
|
+
};
|
|
181
|
+
} else if (this.type === "array") {
|
|
182
|
+
const prevValidator = this._validator;
|
|
183
|
+
this._validator = (value) => {
|
|
184
|
+
const valid = Array.isArray(value) && value.length <= max;
|
|
185
|
+
return prevValidator ? prevValidator(value) && valid : valid;
|
|
186
|
+
};
|
|
187
|
+
} else {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"Max is only supported on number, string, or array fields."
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
pattern(regex) {
|
|
195
|
+
if (this.type !== "string") {
|
|
196
|
+
throw new Error("Pattern is only supported on string fields.");
|
|
197
|
+
}
|
|
198
|
+
const prevValidator = this._validator;
|
|
199
|
+
this._validator = (value) => {
|
|
200
|
+
const valid = typeof value === "string" && regex.test(value);
|
|
201
|
+
return prevValidator ? prevValidator(value) && valid : valid;
|
|
202
|
+
};
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
enum(values) {
|
|
206
|
+
if (this.type !== "string" && this.type !== "number" && !(this.type === "array" && (this.itemType?.type === "string" || this.itemType?.type === "number"))) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
"Enums are only supported on string or number fields or arrays of strings or numbers."
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
this.enumValues = values;
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Create a shallow clone with a different external type parameter.
|
|
216
|
+
* Note: shape and itemType are passed by reference (shallow). If you need
|
|
217
|
+
* deep isolation of nested FieldBuilders, clone them explicitly.
|
|
218
|
+
*/
|
|
219
|
+
as() {
|
|
220
|
+
const clone = new _FieldBuilder(this.type, {
|
|
221
|
+
shape: this._shape,
|
|
222
|
+
itemType: this.itemType,
|
|
223
|
+
refType: this.refType
|
|
224
|
+
});
|
|
225
|
+
clone.enumValues = this.enumValues;
|
|
226
|
+
clone.isImmutable = this.isImmutable;
|
|
227
|
+
clone.isSystem = this.isSystem;
|
|
228
|
+
clone.isRequired = this.isRequired;
|
|
229
|
+
clone._description = this._description;
|
|
230
|
+
clone._version = this._version;
|
|
231
|
+
clone._pii = this._pii;
|
|
232
|
+
clone._validator = this._validator;
|
|
233
|
+
clone._default = this._default;
|
|
234
|
+
clone._upgrade = this._upgrade;
|
|
235
|
+
return clone;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// src/validation/email.RFC5322.ts
|
|
240
|
+
var validateEmail = (value) => {
|
|
241
|
+
if (typeof value !== "string") return false;
|
|
242
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
243
|
+
return emailRegex.test(value);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// src/validation/phone.E.164.ts
|
|
247
|
+
var validatePhone = (value) => {
|
|
248
|
+
if (typeof value !== "string") return false;
|
|
249
|
+
const phoneRegex = /^\+[1-9]\d{1,14}$/;
|
|
250
|
+
return phoneRegex.test(value);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// src/validation/url.WHATWG.ts
|
|
254
|
+
var validateUrl = (value) => {
|
|
255
|
+
if (typeof value !== "string") return false;
|
|
256
|
+
try {
|
|
257
|
+
const url = new URL(value);
|
|
258
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
259
|
+
return true;
|
|
260
|
+
} catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// src/validation/uuid.RFC4122.ts
|
|
266
|
+
var validateUUID = (value) => {
|
|
267
|
+
if (typeof value !== "string") return false;
|
|
268
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
269
|
+
return uuidRegex.test(value);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/validation/dateTime.ISO8601.ts
|
|
273
|
+
var validateDateTimeISO = (value, options) => {
|
|
274
|
+
const mode = options?.mode ?? "datetime";
|
|
275
|
+
if (typeof value !== "string") return false;
|
|
276
|
+
if (mode === "datetime") {
|
|
277
|
+
const isoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
278
|
+
if (!isoDateTimeRegex.test(value)) return false;
|
|
279
|
+
const date = new Date(value);
|
|
280
|
+
if (Number.isNaN(date.getTime())) return false;
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
if (mode === "date") {
|
|
284
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
285
|
+
if (!dateRegex.test(value)) return false;
|
|
286
|
+
const date = new Date(value);
|
|
287
|
+
if (isNaN(date.getTime())) return false;
|
|
288
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
289
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() + 1 === month && date.getUTCDate() === day;
|
|
290
|
+
}
|
|
291
|
+
if (mode === "time") {
|
|
292
|
+
const timeRegex = /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?Z?$/;
|
|
293
|
+
return timeRegex.test(value);
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/validation/countryCode.ISO3166.ts
|
|
299
|
+
var isoCountryCodes = /* @__PURE__ */ new Set([
|
|
300
|
+
"AD",
|
|
301
|
+
"AE",
|
|
302
|
+
"AF",
|
|
303
|
+
"AG",
|
|
304
|
+
"AI",
|
|
305
|
+
"AL",
|
|
306
|
+
"AM",
|
|
307
|
+
"AO",
|
|
308
|
+
"AQ",
|
|
309
|
+
"AR",
|
|
310
|
+
"AS",
|
|
311
|
+
"AT",
|
|
312
|
+
"AU",
|
|
313
|
+
"AW",
|
|
314
|
+
"AX",
|
|
315
|
+
"AZ",
|
|
316
|
+
"BA",
|
|
317
|
+
"BB",
|
|
318
|
+
"BD",
|
|
319
|
+
"BE",
|
|
320
|
+
"BF",
|
|
321
|
+
"BG",
|
|
322
|
+
"BH",
|
|
323
|
+
"BI",
|
|
324
|
+
"BJ",
|
|
325
|
+
"BL",
|
|
326
|
+
"BM",
|
|
327
|
+
"BN",
|
|
328
|
+
"BO",
|
|
329
|
+
"BQ",
|
|
330
|
+
"BR",
|
|
331
|
+
"BS",
|
|
332
|
+
"BT",
|
|
333
|
+
"BV",
|
|
334
|
+
"BW",
|
|
335
|
+
"BY",
|
|
336
|
+
"BZ",
|
|
337
|
+
"CA",
|
|
338
|
+
"CC",
|
|
339
|
+
"CD",
|
|
340
|
+
"CF",
|
|
341
|
+
"CG",
|
|
342
|
+
"CH",
|
|
343
|
+
"CI",
|
|
344
|
+
"CK",
|
|
345
|
+
"CL",
|
|
346
|
+
"CM",
|
|
347
|
+
"CN",
|
|
348
|
+
"CO",
|
|
349
|
+
"CR",
|
|
350
|
+
"CU",
|
|
351
|
+
"CV",
|
|
352
|
+
"CW",
|
|
353
|
+
"CX",
|
|
354
|
+
"CY",
|
|
355
|
+
"CZ",
|
|
356
|
+
"DE",
|
|
357
|
+
"DJ",
|
|
358
|
+
"DK",
|
|
359
|
+
"DM",
|
|
360
|
+
"DO",
|
|
361
|
+
"DZ",
|
|
362
|
+
"EC",
|
|
363
|
+
"EE",
|
|
364
|
+
"EG",
|
|
365
|
+
"EH",
|
|
366
|
+
"ER",
|
|
367
|
+
"ES",
|
|
368
|
+
"ET",
|
|
369
|
+
"FI",
|
|
370
|
+
"FJ",
|
|
371
|
+
"FM",
|
|
372
|
+
"FO",
|
|
373
|
+
"FR",
|
|
374
|
+
"GA",
|
|
375
|
+
"GB",
|
|
376
|
+
"GD",
|
|
377
|
+
"GE",
|
|
378
|
+
"GF",
|
|
379
|
+
"GG",
|
|
380
|
+
"GH",
|
|
381
|
+
"GI",
|
|
382
|
+
"GL",
|
|
383
|
+
"GM",
|
|
384
|
+
"GN",
|
|
385
|
+
"GP",
|
|
386
|
+
"GQ",
|
|
387
|
+
"GR",
|
|
388
|
+
"GT",
|
|
389
|
+
"GU",
|
|
390
|
+
"GW",
|
|
391
|
+
"GY",
|
|
392
|
+
"HK",
|
|
393
|
+
"HM",
|
|
394
|
+
"HN",
|
|
395
|
+
"HR",
|
|
396
|
+
"HT",
|
|
397
|
+
"HU",
|
|
398
|
+
"ID",
|
|
399
|
+
"IE",
|
|
400
|
+
"IL",
|
|
401
|
+
"IM",
|
|
402
|
+
"IN",
|
|
403
|
+
"IO",
|
|
404
|
+
"IQ",
|
|
405
|
+
"IR",
|
|
406
|
+
"IS",
|
|
407
|
+
"IT",
|
|
408
|
+
"JE",
|
|
409
|
+
"JM",
|
|
410
|
+
"JO",
|
|
411
|
+
"JP",
|
|
412
|
+
"KE",
|
|
413
|
+
"KG",
|
|
414
|
+
"KH",
|
|
415
|
+
"KI",
|
|
416
|
+
"KM",
|
|
417
|
+
"KN",
|
|
418
|
+
"KP",
|
|
419
|
+
"KR",
|
|
420
|
+
"KW",
|
|
421
|
+
"KY",
|
|
422
|
+
"KZ",
|
|
423
|
+
"LA",
|
|
424
|
+
"LB",
|
|
425
|
+
"LC",
|
|
426
|
+
"LI",
|
|
427
|
+
"LK",
|
|
428
|
+
"LR",
|
|
429
|
+
"LS",
|
|
430
|
+
"LT",
|
|
431
|
+
"LU",
|
|
432
|
+
"LV",
|
|
433
|
+
"LY",
|
|
434
|
+
"MA",
|
|
435
|
+
"MC",
|
|
436
|
+
"MD",
|
|
437
|
+
"ME",
|
|
438
|
+
"MF",
|
|
439
|
+
"MG",
|
|
440
|
+
"MH",
|
|
441
|
+
"MK",
|
|
442
|
+
"ML",
|
|
443
|
+
"MM",
|
|
444
|
+
"MN",
|
|
445
|
+
"MO",
|
|
446
|
+
"MP",
|
|
447
|
+
"MQ",
|
|
448
|
+
"MR",
|
|
449
|
+
"MS",
|
|
450
|
+
"MT",
|
|
451
|
+
"MU",
|
|
452
|
+
"MV",
|
|
453
|
+
"MW",
|
|
454
|
+
"MX",
|
|
455
|
+
"MY",
|
|
456
|
+
"MZ",
|
|
457
|
+
"NA",
|
|
458
|
+
"NC",
|
|
459
|
+
"NE",
|
|
460
|
+
"NF",
|
|
461
|
+
"NG",
|
|
462
|
+
"NI",
|
|
463
|
+
"NL",
|
|
464
|
+
"NO",
|
|
465
|
+
"NP",
|
|
466
|
+
"NR",
|
|
467
|
+
"NU",
|
|
468
|
+
"NZ",
|
|
469
|
+
"OM",
|
|
470
|
+
"PA",
|
|
471
|
+
"PE",
|
|
472
|
+
"PF",
|
|
473
|
+
"PG",
|
|
474
|
+
"PH",
|
|
475
|
+
"PK",
|
|
476
|
+
"PL",
|
|
477
|
+
"PM",
|
|
478
|
+
"PN",
|
|
479
|
+
"PR",
|
|
480
|
+
"PS",
|
|
481
|
+
"PT",
|
|
482
|
+
"PW",
|
|
483
|
+
"PY",
|
|
484
|
+
"QA",
|
|
485
|
+
"RE",
|
|
486
|
+
"RO",
|
|
487
|
+
"RS",
|
|
488
|
+
"RU",
|
|
489
|
+
"RW",
|
|
490
|
+
"SA",
|
|
491
|
+
"SB",
|
|
492
|
+
"SC",
|
|
493
|
+
"SD",
|
|
494
|
+
"SE",
|
|
495
|
+
"SG",
|
|
496
|
+
"SH",
|
|
497
|
+
"SI",
|
|
498
|
+
"SJ",
|
|
499
|
+
"SK",
|
|
500
|
+
"SL",
|
|
501
|
+
"SM",
|
|
502
|
+
"SN",
|
|
503
|
+
"SO",
|
|
504
|
+
"SR",
|
|
505
|
+
"SS",
|
|
506
|
+
"ST",
|
|
507
|
+
"SV",
|
|
508
|
+
"SX",
|
|
509
|
+
"SY",
|
|
510
|
+
"SZ",
|
|
511
|
+
"TC",
|
|
512
|
+
"TD",
|
|
513
|
+
"TF",
|
|
514
|
+
"TG",
|
|
515
|
+
"TH",
|
|
516
|
+
"TJ",
|
|
517
|
+
"TK",
|
|
518
|
+
"TL",
|
|
519
|
+
"TM",
|
|
520
|
+
"TN",
|
|
521
|
+
"TO",
|
|
522
|
+
"TR",
|
|
523
|
+
"TT",
|
|
524
|
+
"TV",
|
|
525
|
+
"TZ",
|
|
526
|
+
"UA",
|
|
527
|
+
"UG",
|
|
528
|
+
"UM",
|
|
529
|
+
"US",
|
|
530
|
+
"UY",
|
|
531
|
+
"UZ",
|
|
532
|
+
"VA",
|
|
533
|
+
"VC",
|
|
534
|
+
"VE",
|
|
535
|
+
"VG",
|
|
536
|
+
"VI",
|
|
537
|
+
"VN",
|
|
538
|
+
"VU",
|
|
539
|
+
"WF",
|
|
540
|
+
"WS",
|
|
541
|
+
"YE",
|
|
542
|
+
"YT",
|
|
543
|
+
"ZA",
|
|
544
|
+
"ZM",
|
|
545
|
+
"ZW"
|
|
546
|
+
]);
|
|
547
|
+
var validateCountryCode = (value) => {
|
|
548
|
+
if (typeof value !== "string") return false;
|
|
549
|
+
return isoCountryCodes.has(value.toUpperCase());
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// src/validation/currencyCode.ISO4217.ts
|
|
553
|
+
var isoCurrencyCodes = /* @__PURE__ */ new Set([
|
|
554
|
+
"AED",
|
|
555
|
+
"AFN",
|
|
556
|
+
"ALL",
|
|
557
|
+
"AMD",
|
|
558
|
+
"ANG",
|
|
559
|
+
"AOA",
|
|
560
|
+
"ARS",
|
|
561
|
+
"AUD",
|
|
562
|
+
"AWG",
|
|
563
|
+
"AZN",
|
|
564
|
+
"BAM",
|
|
565
|
+
"BBD",
|
|
566
|
+
"BDT",
|
|
567
|
+
"BGN",
|
|
568
|
+
"BHD",
|
|
569
|
+
"BIF",
|
|
570
|
+
"BMD",
|
|
571
|
+
"BND",
|
|
572
|
+
"BOB",
|
|
573
|
+
"BOV",
|
|
574
|
+
"BRL",
|
|
575
|
+
"BSD",
|
|
576
|
+
"BTN",
|
|
577
|
+
"BWP",
|
|
578
|
+
"BYN",
|
|
579
|
+
"BZD",
|
|
580
|
+
"CAD",
|
|
581
|
+
"CDF",
|
|
582
|
+
"CHE",
|
|
583
|
+
"CHF",
|
|
584
|
+
"CHW",
|
|
585
|
+
"CLF",
|
|
586
|
+
"CLP",
|
|
587
|
+
"CNY",
|
|
588
|
+
"COP",
|
|
589
|
+
"COU",
|
|
590
|
+
"CRC",
|
|
591
|
+
"CUC",
|
|
592
|
+
"CUP",
|
|
593
|
+
"CVE",
|
|
594
|
+
"CZK",
|
|
595
|
+
"DJF",
|
|
596
|
+
"DKK",
|
|
597
|
+
"DOP",
|
|
598
|
+
"DZD",
|
|
599
|
+
"EGP",
|
|
600
|
+
"ERN",
|
|
601
|
+
"ETB",
|
|
602
|
+
"EUR",
|
|
603
|
+
"FJD",
|
|
604
|
+
"FKP",
|
|
605
|
+
"GBP",
|
|
606
|
+
"GEL",
|
|
607
|
+
"GHS",
|
|
608
|
+
"GIP",
|
|
609
|
+
"GMD",
|
|
610
|
+
"GNF",
|
|
611
|
+
"GTQ",
|
|
612
|
+
"GYD",
|
|
613
|
+
"HKD",
|
|
614
|
+
"HNL",
|
|
615
|
+
"HRK",
|
|
616
|
+
"HTG",
|
|
617
|
+
"HUF",
|
|
618
|
+
"IDR",
|
|
619
|
+
"ILS",
|
|
620
|
+
"INR",
|
|
621
|
+
"IQD",
|
|
622
|
+
"IRR",
|
|
623
|
+
"ISK",
|
|
624
|
+
"JMD",
|
|
625
|
+
"JOD",
|
|
626
|
+
"JPY",
|
|
627
|
+
"KES",
|
|
628
|
+
"KGS",
|
|
629
|
+
"KHR",
|
|
630
|
+
"KMF",
|
|
631
|
+
"KPW",
|
|
632
|
+
"KRW",
|
|
633
|
+
"KWD",
|
|
634
|
+
"KYD",
|
|
635
|
+
"KZT",
|
|
636
|
+
"LAK",
|
|
637
|
+
"LBP",
|
|
638
|
+
"LKR",
|
|
639
|
+
"LRD",
|
|
640
|
+
"LSL",
|
|
641
|
+
"LYD",
|
|
642
|
+
"MAD",
|
|
643
|
+
"MDL",
|
|
644
|
+
"MGA",
|
|
645
|
+
"MKD",
|
|
646
|
+
"MMK",
|
|
647
|
+
"MNT",
|
|
648
|
+
"MOP",
|
|
649
|
+
"MRU",
|
|
650
|
+
"MUR",
|
|
651
|
+
"MVR",
|
|
652
|
+
"MWK",
|
|
653
|
+
"MXN",
|
|
654
|
+
"MXV",
|
|
655
|
+
"MYR",
|
|
656
|
+
"MZN",
|
|
657
|
+
"NAD",
|
|
658
|
+
"NGN",
|
|
659
|
+
"NIO",
|
|
660
|
+
"NOK",
|
|
661
|
+
"NPR",
|
|
662
|
+
"NZD",
|
|
663
|
+
"OMR",
|
|
664
|
+
"PAB",
|
|
665
|
+
"PEN",
|
|
666
|
+
"PGK",
|
|
667
|
+
"PHP",
|
|
668
|
+
"PKR",
|
|
669
|
+
"PLN",
|
|
670
|
+
"PYG",
|
|
671
|
+
"QAR",
|
|
672
|
+
"RON",
|
|
673
|
+
"RSD",
|
|
674
|
+
"RUB",
|
|
675
|
+
"RWF",
|
|
676
|
+
"SAR",
|
|
677
|
+
"SBD",
|
|
678
|
+
"SCR",
|
|
679
|
+
"SDG",
|
|
680
|
+
"SEK",
|
|
681
|
+
"SGD",
|
|
682
|
+
"SHP",
|
|
683
|
+
"SLE",
|
|
684
|
+
"SLL",
|
|
685
|
+
"SOS",
|
|
686
|
+
"SRD",
|
|
687
|
+
"SSP",
|
|
688
|
+
"STN",
|
|
689
|
+
"SVC",
|
|
690
|
+
"SYP",
|
|
691
|
+
"SZL",
|
|
692
|
+
"THB",
|
|
693
|
+
"TJS",
|
|
694
|
+
"TMT",
|
|
695
|
+
"TND",
|
|
696
|
+
"TOP",
|
|
697
|
+
"TRY",
|
|
698
|
+
"TTD",
|
|
699
|
+
"TWD",
|
|
700
|
+
"TZS",
|
|
701
|
+
"UAH",
|
|
702
|
+
"UGX",
|
|
703
|
+
"USD",
|
|
704
|
+
"USN",
|
|
705
|
+
"UYI",
|
|
706
|
+
"UYU",
|
|
707
|
+
"UYW",
|
|
708
|
+
"UZS",
|
|
709
|
+
"VES",
|
|
710
|
+
"VND",
|
|
711
|
+
"VUV",
|
|
712
|
+
"WST",
|
|
713
|
+
"XAF",
|
|
714
|
+
"XAG",
|
|
715
|
+
"XAU",
|
|
716
|
+
"XBA",
|
|
717
|
+
"XBB",
|
|
718
|
+
"XBC",
|
|
719
|
+
"XBD",
|
|
720
|
+
"XCD",
|
|
721
|
+
"XDR",
|
|
722
|
+
"XOF",
|
|
723
|
+
"XPD",
|
|
724
|
+
"XPF",
|
|
725
|
+
"XPT",
|
|
726
|
+
"XSU",
|
|
727
|
+
"XTS",
|
|
728
|
+
"XUA",
|
|
729
|
+
"XXX",
|
|
730
|
+
"YER",
|
|
731
|
+
"ZAR",
|
|
732
|
+
"ZMW",
|
|
733
|
+
"ZWL"
|
|
734
|
+
]);
|
|
735
|
+
var validateCurrencyCode = (value) => {
|
|
736
|
+
if (typeof value !== "string") return false;
|
|
737
|
+
return isoCurrencyCodes.has(value.toUpperCase());
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// src/validation/generalText.OWASP.ts
|
|
741
|
+
function validateSafeText(value) {
|
|
742
|
+
if (typeof value !== "string") return false;
|
|
743
|
+
const trimmed = value.trim();
|
|
744
|
+
if (trimmed.length === 0) return false;
|
|
745
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
746
|
+
const code = trimmed.codePointAt(i);
|
|
747
|
+
if (code !== void 0 && (code >= 0 && code <= 31 || code === 127)) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (/['"<>\\{}();]/.test(trimmed)) return false;
|
|
752
|
+
if (/(--|\b(SELECT|UPDATE|DELETE|INSERT|DROP|ALTER|EXEC|UNION|GRANT|REVOKE)\b|\/\*|\*\/|@@)/i.test(
|
|
753
|
+
trimmed
|
|
754
|
+
))
|
|
755
|
+
return false;
|
|
756
|
+
if (trimmed.includes("\0")) return false;
|
|
757
|
+
if (trimmed.length > 1024) return false;
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/validation/version.SEMVER2.0.0.ts
|
|
762
|
+
function validateSemVer(value) {
|
|
763
|
+
if (typeof value !== "string") return false;
|
|
764
|
+
return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/.test(
|
|
765
|
+
value
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/validation/percentage.ISO80000-1.ts
|
|
770
|
+
function validatePercentage(value) {
|
|
771
|
+
if (typeof value !== "number") return false;
|
|
772
|
+
return value >= 0 && value <= 100;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/validation/richtext.OWASP.ts
|
|
776
|
+
function validateRichText(value) {
|
|
777
|
+
if (typeof value !== "string") return false;
|
|
778
|
+
const trimmed = value.trim();
|
|
779
|
+
if (trimmed.length === 0) return true;
|
|
780
|
+
if (/<(script|iframe|object|embed|style|link|meta|base|form|input|button|textarea|select)\b/i.test(
|
|
781
|
+
trimmed
|
|
782
|
+
)) {
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
if (/javascript:/i.test(trimmed)) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
if (/on\w+=["']?/i.test(trimmed)) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
if (trimmed.length > 1e4) return false;
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/validation/name.OWASP.ts
|
|
796
|
+
function validateName(value) {
|
|
797
|
+
if (typeof value !== "string") return false;
|
|
798
|
+
const trimmed = value.trim();
|
|
799
|
+
if (trimmed.length === 0) return false;
|
|
800
|
+
if (trimmed.length > 256) return false;
|
|
801
|
+
for (const ch of trimmed) {
|
|
802
|
+
const cp = ch.codePointAt(0);
|
|
803
|
+
if (cp >= 0 && cp <= 31 || cp === 127) return false;
|
|
804
|
+
}
|
|
805
|
+
const namePattern = /^[\p{L}\p{M}'\- ]+$/u;
|
|
806
|
+
if (!namePattern.test(trimmed)) return false;
|
|
807
|
+
return true;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/validation/user.MS-GOOGLE-APPLE.ts
|
|
811
|
+
function validateUserId(value) {
|
|
812
|
+
if (typeof value !== "string") return false;
|
|
813
|
+
const trimmed = value.trim();
|
|
814
|
+
if (trimmed.length === 0) return false;
|
|
815
|
+
const googlePattern = /^\d{21,22}$/;
|
|
816
|
+
const microsoftPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
817
|
+
const applePattern = /^[\w\-.]{6,255}$/;
|
|
818
|
+
return googlePattern.test(trimmed) || microsoftPattern.test(trimmed) || applePattern.test(trimmed);
|
|
819
|
+
}
|
|
820
|
+
function validateUserIdArray(value) {
|
|
821
|
+
if (!Array.isArray(value)) return false;
|
|
822
|
+
return value.every(validateUserId);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/validation/languageCode.BCP47.ts
|
|
826
|
+
var IsoLanguageCode = /* @__PURE__ */ ((IsoLanguageCode2) => {
|
|
827
|
+
IsoLanguageCode2["Afar"] = "aa";
|
|
828
|
+
IsoLanguageCode2["Abkhazian"] = "ab";
|
|
829
|
+
IsoLanguageCode2["Afrikaans"] = "af";
|
|
830
|
+
IsoLanguageCode2["Akan"] = "ak";
|
|
831
|
+
IsoLanguageCode2["Albanian"] = "sq";
|
|
832
|
+
IsoLanguageCode2["Amharic"] = "am";
|
|
833
|
+
IsoLanguageCode2["Arabic"] = "ar";
|
|
834
|
+
IsoLanguageCode2["Aragonese"] = "an";
|
|
835
|
+
IsoLanguageCode2["Armenian"] = "hy";
|
|
836
|
+
IsoLanguageCode2["Assamese"] = "as";
|
|
837
|
+
IsoLanguageCode2["Avaric"] = "av";
|
|
838
|
+
IsoLanguageCode2["Aymara"] = "ay";
|
|
839
|
+
IsoLanguageCode2["Azerbaijani"] = "az";
|
|
840
|
+
IsoLanguageCode2["Bashkir"] = "ba";
|
|
841
|
+
IsoLanguageCode2["Bambara"] = "bm";
|
|
842
|
+
IsoLanguageCode2["Basque"] = "eu";
|
|
843
|
+
IsoLanguageCode2["Belarusian"] = "be";
|
|
844
|
+
IsoLanguageCode2["Bengali"] = "bn";
|
|
845
|
+
IsoLanguageCode2["Bislama"] = "bi";
|
|
846
|
+
IsoLanguageCode2["Bosnian"] = "bs";
|
|
847
|
+
IsoLanguageCode2["Breton"] = "br";
|
|
848
|
+
IsoLanguageCode2["Bulgarian"] = "bg";
|
|
849
|
+
IsoLanguageCode2["Burmese"] = "my";
|
|
850
|
+
IsoLanguageCode2["Catalan"] = "ca";
|
|
851
|
+
IsoLanguageCode2["Chamorro"] = "ch";
|
|
852
|
+
IsoLanguageCode2["Chechen"] = "ce";
|
|
853
|
+
IsoLanguageCode2["Chinese"] = "zh";
|
|
854
|
+
IsoLanguageCode2["ChurchSlavic"] = "cu";
|
|
855
|
+
IsoLanguageCode2["Chuvash"] = "cv";
|
|
856
|
+
IsoLanguageCode2["Cornish"] = "kw";
|
|
857
|
+
IsoLanguageCode2["Corsican"] = "co";
|
|
858
|
+
IsoLanguageCode2["Cree"] = "cr";
|
|
859
|
+
IsoLanguageCode2["Croatian"] = "hr";
|
|
860
|
+
IsoLanguageCode2["Czech"] = "cs";
|
|
861
|
+
IsoLanguageCode2["Danish"] = "da";
|
|
862
|
+
IsoLanguageCode2["Divehi"] = "dv";
|
|
863
|
+
IsoLanguageCode2["Dutch"] = "nl";
|
|
864
|
+
IsoLanguageCode2["Dzongkha"] = "dz";
|
|
865
|
+
IsoLanguageCode2["English"] = "en";
|
|
866
|
+
IsoLanguageCode2["Esperanto"] = "eo";
|
|
867
|
+
IsoLanguageCode2["Estonian"] = "et";
|
|
868
|
+
IsoLanguageCode2["Ewe"] = "ee";
|
|
869
|
+
IsoLanguageCode2["Faroese"] = "fo";
|
|
870
|
+
IsoLanguageCode2["Fijian"] = "fj";
|
|
871
|
+
IsoLanguageCode2["Finnish"] = "fi";
|
|
872
|
+
IsoLanguageCode2["French"] = "fr";
|
|
873
|
+
IsoLanguageCode2["WesternFrisian"] = "fy";
|
|
874
|
+
IsoLanguageCode2["Fulah"] = "ff";
|
|
875
|
+
IsoLanguageCode2["Gaelic"] = "gd";
|
|
876
|
+
IsoLanguageCode2["Galician"] = "gl";
|
|
877
|
+
IsoLanguageCode2["Ganda"] = "lg";
|
|
878
|
+
IsoLanguageCode2["Georgian"] = "ka";
|
|
879
|
+
IsoLanguageCode2["German"] = "de";
|
|
880
|
+
IsoLanguageCode2["Greek"] = "el";
|
|
881
|
+
IsoLanguageCode2["Kalaallisut"] = "kl";
|
|
882
|
+
IsoLanguageCode2["Guarani"] = "gn";
|
|
883
|
+
IsoLanguageCode2["Gujarati"] = "gu";
|
|
884
|
+
IsoLanguageCode2["Haitian"] = "ht";
|
|
885
|
+
IsoLanguageCode2["Hausa"] = "ha";
|
|
886
|
+
IsoLanguageCode2["Hebrew"] = "he";
|
|
887
|
+
IsoLanguageCode2["Herero"] = "hz";
|
|
888
|
+
IsoLanguageCode2["Hindi"] = "hi";
|
|
889
|
+
IsoLanguageCode2["HiriMotu"] = "ho";
|
|
890
|
+
IsoLanguageCode2["Hungarian"] = "hu";
|
|
891
|
+
IsoLanguageCode2["Icelandic"] = "is";
|
|
892
|
+
IsoLanguageCode2["Ido"] = "io";
|
|
893
|
+
IsoLanguageCode2["Igbo"] = "ig";
|
|
894
|
+
IsoLanguageCode2["Indonesian"] = "id";
|
|
895
|
+
IsoLanguageCode2["Interlingua"] = "ia";
|
|
896
|
+
IsoLanguageCode2["Interlingue"] = "ie";
|
|
897
|
+
IsoLanguageCode2["Inuktitut"] = "iu";
|
|
898
|
+
IsoLanguageCode2["Inupiaq"] = "ik";
|
|
899
|
+
IsoLanguageCode2["Irish"] = "ga";
|
|
900
|
+
IsoLanguageCode2["Italian"] = "it";
|
|
901
|
+
IsoLanguageCode2["Japanese"] = "ja";
|
|
902
|
+
IsoLanguageCode2["Javanese"] = "jv";
|
|
903
|
+
IsoLanguageCode2["Kannada"] = "kn";
|
|
904
|
+
IsoLanguageCode2["Kanuri"] = "kr";
|
|
905
|
+
IsoLanguageCode2["Kashmiri"] = "ks";
|
|
906
|
+
IsoLanguageCode2["Kazakh"] = "kk";
|
|
907
|
+
IsoLanguageCode2["CentralKhmer"] = "km";
|
|
908
|
+
IsoLanguageCode2["Kikuyu"] = "ki";
|
|
909
|
+
IsoLanguageCode2["Kinyarwanda"] = "rw";
|
|
910
|
+
IsoLanguageCode2["Kyrgyz"] = "ky";
|
|
911
|
+
IsoLanguageCode2["Komi"] = "kv";
|
|
912
|
+
IsoLanguageCode2["Kongo"] = "kg";
|
|
913
|
+
IsoLanguageCode2["Korean"] = "ko";
|
|
914
|
+
IsoLanguageCode2["Kuanyama"] = "kj";
|
|
915
|
+
IsoLanguageCode2["Kurdish"] = "ku";
|
|
916
|
+
IsoLanguageCode2["Lao"] = "lo";
|
|
917
|
+
IsoLanguageCode2["Latin"] = "la";
|
|
918
|
+
IsoLanguageCode2["Latvian"] = "lv";
|
|
919
|
+
IsoLanguageCode2["Limburgan"] = "li";
|
|
920
|
+
IsoLanguageCode2["Lingala"] = "ln";
|
|
921
|
+
IsoLanguageCode2["Lithuanian"] = "lt";
|
|
922
|
+
IsoLanguageCode2["LubaKatanga"] = "lu";
|
|
923
|
+
IsoLanguageCode2["Luxembourgish"] = "lb";
|
|
924
|
+
IsoLanguageCode2["Macedonian"] = "mk";
|
|
925
|
+
IsoLanguageCode2["Malagasy"] = "mg";
|
|
926
|
+
IsoLanguageCode2["Malay"] = "ms";
|
|
927
|
+
IsoLanguageCode2["Malayalam"] = "ml";
|
|
928
|
+
IsoLanguageCode2["Maltese"] = "mt";
|
|
929
|
+
IsoLanguageCode2["Manx"] = "gv";
|
|
930
|
+
IsoLanguageCode2["Maori"] = "mi";
|
|
931
|
+
IsoLanguageCode2["Marathi"] = "mr";
|
|
932
|
+
IsoLanguageCode2["Marshallese"] = "mh";
|
|
933
|
+
IsoLanguageCode2["Mongolian"] = "mn";
|
|
934
|
+
IsoLanguageCode2["Nauru"] = "na";
|
|
935
|
+
IsoLanguageCode2["Navajo"] = "nv";
|
|
936
|
+
IsoLanguageCode2["NorthNdebele"] = "nd";
|
|
937
|
+
IsoLanguageCode2["SouthNdebele"] = "nr";
|
|
938
|
+
IsoLanguageCode2["Ndonga"] = "ng";
|
|
939
|
+
IsoLanguageCode2["Nepali"] = "ne";
|
|
940
|
+
IsoLanguageCode2["Norwegian"] = "no";
|
|
941
|
+
IsoLanguageCode2["NorwegianBokmal"] = "nb";
|
|
942
|
+
IsoLanguageCode2["NorwegianNynorsk"] = "nn";
|
|
943
|
+
IsoLanguageCode2["SichuanYi"] = "ii";
|
|
944
|
+
IsoLanguageCode2["Occitan"] = "oc";
|
|
945
|
+
IsoLanguageCode2["Ojibwa"] = "oj";
|
|
946
|
+
IsoLanguageCode2["Oriya"] = "or";
|
|
947
|
+
IsoLanguageCode2["Oromo"] = "om";
|
|
948
|
+
IsoLanguageCode2["Ossetian"] = "os";
|
|
949
|
+
IsoLanguageCode2["Pali"] = "pi";
|
|
950
|
+
IsoLanguageCode2["Pashto"] = "ps";
|
|
951
|
+
IsoLanguageCode2["Persian"] = "fa";
|
|
952
|
+
IsoLanguageCode2["Polish"] = "pl";
|
|
953
|
+
IsoLanguageCode2["Portuguese"] = "pt";
|
|
954
|
+
IsoLanguageCode2["Punjabi"] = "pa";
|
|
955
|
+
IsoLanguageCode2["Quechua"] = "qu";
|
|
956
|
+
IsoLanguageCode2["Romansh"] = "rm";
|
|
957
|
+
IsoLanguageCode2["Romanian"] = "ro";
|
|
958
|
+
IsoLanguageCode2["Rundi"] = "rn";
|
|
959
|
+
IsoLanguageCode2["Russian"] = "ru";
|
|
960
|
+
IsoLanguageCode2["Samoan"] = "sm";
|
|
961
|
+
IsoLanguageCode2["Sango"] = "sg";
|
|
962
|
+
IsoLanguageCode2["Sanskrit"] = "sa";
|
|
963
|
+
IsoLanguageCode2["Sardinian"] = "sc";
|
|
964
|
+
IsoLanguageCode2["Serbian"] = "sr";
|
|
965
|
+
IsoLanguageCode2["Shona"] = "sn";
|
|
966
|
+
IsoLanguageCode2["Sindhi"] = "sd";
|
|
967
|
+
IsoLanguageCode2["Sinhala"] = "si";
|
|
968
|
+
IsoLanguageCode2["Slovak"] = "sk";
|
|
969
|
+
IsoLanguageCode2["Slovenian"] = "sl";
|
|
970
|
+
IsoLanguageCode2["Somali"] = "so";
|
|
971
|
+
IsoLanguageCode2["SouthernSotho"] = "st";
|
|
972
|
+
IsoLanguageCode2["Spanish"] = "es";
|
|
973
|
+
IsoLanguageCode2["Sundanese"] = "su";
|
|
974
|
+
IsoLanguageCode2["Swahili"] = "sw";
|
|
975
|
+
IsoLanguageCode2["Swati"] = "ss";
|
|
976
|
+
IsoLanguageCode2["Swedish"] = "sv";
|
|
977
|
+
IsoLanguageCode2["Tagalog"] = "tl";
|
|
978
|
+
IsoLanguageCode2["Tahitian"] = "ty";
|
|
979
|
+
IsoLanguageCode2["Tajik"] = "tg";
|
|
980
|
+
IsoLanguageCode2["Tamil"] = "ta";
|
|
981
|
+
IsoLanguageCode2["Tatar"] = "tt";
|
|
982
|
+
IsoLanguageCode2["Telugu"] = "te";
|
|
983
|
+
IsoLanguageCode2["Thai"] = "th";
|
|
984
|
+
IsoLanguageCode2["Tibetan"] = "bo";
|
|
985
|
+
IsoLanguageCode2["Tigrinya"] = "ti";
|
|
986
|
+
IsoLanguageCode2["Tonga"] = "to";
|
|
987
|
+
IsoLanguageCode2["Tsonga"] = "ts";
|
|
988
|
+
IsoLanguageCode2["Tswana"] = "tn";
|
|
989
|
+
IsoLanguageCode2["Turkish"] = "tr";
|
|
990
|
+
IsoLanguageCode2["Turkmen"] = "tk";
|
|
991
|
+
IsoLanguageCode2["Twi"] = "tw";
|
|
992
|
+
IsoLanguageCode2["Uighur"] = "ug";
|
|
993
|
+
IsoLanguageCode2["Ukrainian"] = "uk";
|
|
994
|
+
IsoLanguageCode2["Urdu"] = "ur";
|
|
995
|
+
IsoLanguageCode2["Uzbek"] = "uz";
|
|
996
|
+
IsoLanguageCode2["Venda"] = "ve";
|
|
997
|
+
IsoLanguageCode2["Vietnamese"] = "vi";
|
|
998
|
+
IsoLanguageCode2["Volapuk"] = "vo";
|
|
999
|
+
IsoLanguageCode2["Walloon"] = "wa";
|
|
1000
|
+
IsoLanguageCode2["Welsh"] = "cy";
|
|
1001
|
+
IsoLanguageCode2["Wolof"] = "wo";
|
|
1002
|
+
IsoLanguageCode2["Xhosa"] = "xh";
|
|
1003
|
+
IsoLanguageCode2["Yiddish"] = "yi";
|
|
1004
|
+
IsoLanguageCode2["Yoruba"] = "yo";
|
|
1005
|
+
IsoLanguageCode2["Zhuang"] = "za";
|
|
1006
|
+
IsoLanguageCode2["Zulu"] = "zu";
|
|
1007
|
+
return IsoLanguageCode2;
|
|
1008
|
+
})(IsoLanguageCode || {});
|
|
1009
|
+
var ISO_LANGUAGE_SET = new Set(
|
|
1010
|
+
Object.values(IsoLanguageCode)
|
|
1011
|
+
);
|
|
1012
|
+
function isIsoLanguageCode(value) {
|
|
1013
|
+
return typeof value === "string" && ISO_LANGUAGE_SET.has(value.toLowerCase());
|
|
1014
|
+
}
|
|
1015
|
+
function isRegionSubtag(value) {
|
|
1016
|
+
return /^[A-Z]{2}$/.test(value) || /^\d{3}$/.test(value);
|
|
1017
|
+
}
|
|
1018
|
+
function isScriptSubtag(value) {
|
|
1019
|
+
return /^[A-Z][a-z]{3}$/.test(value);
|
|
1020
|
+
}
|
|
1021
|
+
function isVariantSubtag(value) {
|
|
1022
|
+
return /^([0-9][A-Za-z0-9]{3}|[A-Za-z0-9]{5,8})$/.test(value);
|
|
1023
|
+
}
|
|
1024
|
+
function isExtensionSingleton(value) {
|
|
1025
|
+
return /^[0-9A-WY-Za-wy-z]$/.test(value);
|
|
1026
|
+
}
|
|
1027
|
+
function isExtensionSubtag(value) {
|
|
1028
|
+
return /^[A-Za-z0-9]{2,8}$/.test(value);
|
|
1029
|
+
}
|
|
1030
|
+
function isPrivateUseSingleton(value) {
|
|
1031
|
+
return value.toLowerCase() === "x";
|
|
1032
|
+
}
|
|
1033
|
+
function isPrivateUseSubtag(value) {
|
|
1034
|
+
return /^[A-Za-z0-9]{1,8}$/.test(value);
|
|
1035
|
+
}
|
|
1036
|
+
function validateLanguage(value) {
|
|
1037
|
+
if (typeof value !== "string" || value.length === 0) return false;
|
|
1038
|
+
const parts = value.split("-");
|
|
1039
|
+
let i = 0;
|
|
1040
|
+
const lang = parts[i];
|
|
1041
|
+
if (!lang || !isIsoLanguageCode(lang)) return false;
|
|
1042
|
+
i += 1;
|
|
1043
|
+
if (i < parts.length && isScriptSubtag(parts[i])) {
|
|
1044
|
+
i += 1;
|
|
1045
|
+
}
|
|
1046
|
+
if (i < parts.length && isRegionSubtag(parts[i].toUpperCase())) {
|
|
1047
|
+
i += 1;
|
|
1048
|
+
}
|
|
1049
|
+
while (i < parts.length && isVariantSubtag(parts[i])) {
|
|
1050
|
+
i += 1;
|
|
1051
|
+
}
|
|
1052
|
+
while (i < parts.length && isExtensionSingleton(parts[i])) {
|
|
1053
|
+
i += 1;
|
|
1054
|
+
if (!(i < parts.length && isExtensionSubtag(parts[i]))) return false;
|
|
1055
|
+
while (i < parts.length && isExtensionSubtag(parts[i])) {
|
|
1056
|
+
i += 1;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (i < parts.length && isPrivateUseSingleton(parts[i])) {
|
|
1060
|
+
i += 1;
|
|
1061
|
+
if (!(i < parts.length && isPrivateUseSubtag(parts[i]))) return false;
|
|
1062
|
+
while (i < parts.length && isPrivateUseSubtag(parts[i])) {
|
|
1063
|
+
i += 1;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return i === parts.length;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/field.ts
|
|
1070
|
+
var field = {
|
|
1071
|
+
string: () => new FieldBuilder("string"),
|
|
1072
|
+
number: () => new FieldBuilder("number"),
|
|
1073
|
+
boolean: () => new FieldBuilder("boolean"),
|
|
1074
|
+
object: (fields) => new FieldBuilder("object", { shape: fields }),
|
|
1075
|
+
array: (itemType) => new FieldBuilder("array", { itemType }),
|
|
1076
|
+
ref: (refType) => new FieldBuilder("ref", { refType }),
|
|
1077
|
+
email: () => new FieldBuilder("string").validator(validateEmail).PID({
|
|
1078
|
+
classification: "high",
|
|
1079
|
+
action: "encrypt",
|
|
1080
|
+
logHandling: "redact",
|
|
1081
|
+
purpose: "an email address"
|
|
1082
|
+
}).description("An email address"),
|
|
1083
|
+
phone: () => new FieldBuilder("string").validator(validatePhone).PID({
|
|
1084
|
+
classification: "high",
|
|
1085
|
+
action: "encrypt",
|
|
1086
|
+
logHandling: "redact",
|
|
1087
|
+
purpose: "a phone number"
|
|
1088
|
+
}).description("A phone number"),
|
|
1089
|
+
url: () => new FieldBuilder("string").validator(validateUrl).PID({
|
|
1090
|
+
classification: "low",
|
|
1091
|
+
action: "hash",
|
|
1092
|
+
logHandling: "pseudonym",
|
|
1093
|
+
purpose: "a URL"
|
|
1094
|
+
}).description("A URL"),
|
|
1095
|
+
uuid: () => new FieldBuilder("string").PID({
|
|
1096
|
+
classification: "low",
|
|
1097
|
+
action: "hash",
|
|
1098
|
+
logHandling: "pseudonym",
|
|
1099
|
+
purpose: "a UUID"
|
|
1100
|
+
}).validator(validateUUID).description("A UUID"),
|
|
1101
|
+
dateTimeISO: () => new FieldBuilder("string").PID({
|
|
1102
|
+
classification: "none",
|
|
1103
|
+
action: "none",
|
|
1104
|
+
logHandling: "plain",
|
|
1105
|
+
purpose: "a date string"
|
|
1106
|
+
}).validator(validateDateTimeISO).description("A date string in ISO 8601 format"),
|
|
1107
|
+
dateISO: () => new FieldBuilder("string").PID({
|
|
1108
|
+
classification: "none",
|
|
1109
|
+
action: "none",
|
|
1110
|
+
logHandling: "plain",
|
|
1111
|
+
purpose: "a date string"
|
|
1112
|
+
}).validator((s) => validateDateTimeISO(s, { mode: "date" })).description("A date string in ISO 8601 format (date only)"),
|
|
1113
|
+
timeISO: () => new FieldBuilder("string").PID({
|
|
1114
|
+
classification: "none",
|
|
1115
|
+
action: "none",
|
|
1116
|
+
logHandling: "plain",
|
|
1117
|
+
purpose: "a time string"
|
|
1118
|
+
}).validator((s) => validateDateTimeISO(s, { mode: "time" })).description("A time string in ISO 8601 format (time only)"),
|
|
1119
|
+
richText: () => new FieldBuilder("string").PID({
|
|
1120
|
+
classification: "low",
|
|
1121
|
+
action: "clear",
|
|
1122
|
+
logHandling: "omit",
|
|
1123
|
+
purpose: "rich text content"
|
|
1124
|
+
}).validator(validateRichText).description("Rich text content, may include basic HTML formatting"),
|
|
1125
|
+
generalText: () => new FieldBuilder("string").PID({
|
|
1126
|
+
classification: "none",
|
|
1127
|
+
action: "none",
|
|
1128
|
+
logHandling: "plain",
|
|
1129
|
+
purpose: "Plain text content"
|
|
1130
|
+
}).validator(validateSafeText).description("Standard text content, no HTML allowed"),
|
|
1131
|
+
latitude: () => new FieldBuilder("number").PID({
|
|
1132
|
+
classification: "low",
|
|
1133
|
+
action: "clear",
|
|
1134
|
+
logHandling: "omit",
|
|
1135
|
+
purpose: "Latitude in decimal degrees, WGS 84 (ISO 6709)"
|
|
1136
|
+
}).min(-90).max(90).description("Latitude in decimal degrees, WGS 84 (ISO 6709)"),
|
|
1137
|
+
longitude: () => new FieldBuilder("number").PID({
|
|
1138
|
+
classification: "low",
|
|
1139
|
+
action: "clear",
|
|
1140
|
+
logHandling: "omit",
|
|
1141
|
+
purpose: "Longitude in decimal degrees, WGS 84 (ISO 6709)"
|
|
1142
|
+
}).min(-180).max(180).description("Longitude in decimal degrees, WGS 84 (ISO 6709)"),
|
|
1143
|
+
version: () => new FieldBuilder("string").validator(validateSemVer).description("A semantic version string, e.g. '1.0.0'"),
|
|
1144
|
+
countryCode: () => new FieldBuilder("string").validator(validateCountryCode).description("An ISO 3166 country code, e.g. 'US', 'GB', 'FR'"),
|
|
1145
|
+
languageCode: () => new FieldBuilder("string").validator(validateLanguage).description(
|
|
1146
|
+
"An BCP 47 structured language code, primarily ISO 639-1 and optionally with ISO 3166-1 alpha-2 country code, e.g. 'en', 'en-US', 'fr', 'fr-FR'"
|
|
1147
|
+
)
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// src/pii.ts
|
|
1151
|
+
function enforcePIIField(parentKey, key, value, def, enforcement = "none", errors, logger) {
|
|
1152
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1153
|
+
if (def?._pii?.classification === "high" && (def?.isRequired ?? true)) {
|
|
1154
|
+
const missing = value === void 0 || value === null || value === "";
|
|
1155
|
+
if (missing) {
|
|
1156
|
+
const msg = `High PII field must not be empty: ${path}`;
|
|
1157
|
+
if (enforcement === "strict") {
|
|
1158
|
+
errors?.push(msg);
|
|
1159
|
+
return { shortCircuit: true };
|
|
1160
|
+
}
|
|
1161
|
+
if (enforcement === "warn") {
|
|
1162
|
+
logger?.warn?.(`WARN (PII Enforcement): ${msg}`);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return { shortCircuit: false };
|
|
1167
|
+
}
|
|
1168
|
+
function prepareForStorage(shape, input, encryptFn, hashFn) {
|
|
1169
|
+
const build = (def, value, key, out, ctx = {}) => {
|
|
1170
|
+
if (!def) return;
|
|
1171
|
+
const isMissing = value === void 0 || value === null;
|
|
1172
|
+
if (def._pii?.action === "encrypt") {
|
|
1173
|
+
if (!isMissing) out[key + "Encrypted"] = encryptFn(value);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (def._pii?.action === "hash") {
|
|
1177
|
+
if (!isMissing) out[key + "Hash"] = hashFn(value);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if (def._pii?.action === "clear") {
|
|
1181
|
+
if (!isMissing) out[key] = null;
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (def.type === "object" && def._shape) {
|
|
1185
|
+
const obj = {};
|
|
1186
|
+
for (const [childKey, childDef] of Object.entries(def._shape)) {
|
|
1187
|
+
build(childDef, value?.[childKey], childKey, obj, { parentKey: key });
|
|
1188
|
+
}
|
|
1189
|
+
out[key] = obj;
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
if (def.type === "array" && def.itemType && Array.isArray(value)) {
|
|
1193
|
+
out[key] = value.map((item) => {
|
|
1194
|
+
const obj = {};
|
|
1195
|
+
build(def.itemType, item, key, obj, {
|
|
1196
|
+
parentKey: key,
|
|
1197
|
+
isArrayItem: true
|
|
1198
|
+
});
|
|
1199
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
1200
|
+
return obj[key];
|
|
1201
|
+
}
|
|
1202
|
+
return Object.keys(obj).length > 0 ? obj : item;
|
|
1203
|
+
});
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
if (def.type === "ref") {
|
|
1207
|
+
const ref = { ...value };
|
|
1208
|
+
const refShape = def._shape;
|
|
1209
|
+
if (refShape && value) {
|
|
1210
|
+
for (const [childKey, childDef] of Object.entries(refShape)) {
|
|
1211
|
+
build(childDef, value[childKey], childKey, ref, { parentKey: key });
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
out[key] = ref;
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
out[key] = value;
|
|
1218
|
+
};
|
|
1219
|
+
const result = {};
|
|
1220
|
+
for (const key in shape) {
|
|
1221
|
+
build(shape[key], input?.[key], key, result);
|
|
1222
|
+
}
|
|
1223
|
+
return result;
|
|
1224
|
+
}
|
|
1225
|
+
function prepareForRead(shape, stored, decryptFn) {
|
|
1226
|
+
const readValue = (def, key, container, ctx = {}) => {
|
|
1227
|
+
if (!def) return container?.[key];
|
|
1228
|
+
const action = def._pii?.action;
|
|
1229
|
+
if (action === "encrypt") {
|
|
1230
|
+
const enc = container?.[key + "Encrypted"] ?? container?.[`${ctx.parentKey ?? key}Encrypted`];
|
|
1231
|
+
return enc === void 0 ? void 0 : decryptFn(enc);
|
|
1232
|
+
}
|
|
1233
|
+
if (action === "hash") {
|
|
1234
|
+
const h = container?.[key + "Hash"] ?? container?.[`${ctx.parentKey ?? key}Hash`];
|
|
1235
|
+
return h === void 0 ? void 0 : h;
|
|
1236
|
+
}
|
|
1237
|
+
if (action === "clear")
|
|
1238
|
+
return container?.hasOwnProperty(key) ? null : void 0;
|
|
1239
|
+
if (def.type === "object" && def._shape) {
|
|
1240
|
+
const obj = {};
|
|
1241
|
+
const source = container && Object.prototype.hasOwnProperty.call(container, key) ? container[key] : container ?? {};
|
|
1242
|
+
for (const [childKey, childDef] of Object.entries(def._shape)) {
|
|
1243
|
+
obj[childKey] = readValue(childDef, childKey, source);
|
|
1244
|
+
}
|
|
1245
|
+
return obj;
|
|
1246
|
+
}
|
|
1247
|
+
if (def.type === "array" && def.itemType) {
|
|
1248
|
+
const arr = container?.[key];
|
|
1249
|
+
if (!Array.isArray(arr)) return arr;
|
|
1250
|
+
return arr.map(
|
|
1251
|
+
(item) => readValue(def.itemType, key, item, { parentKey: key, isArrayItem: true })
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
if (def.type === "ref") {
|
|
1255
|
+
const ref = { ...container?.[key] ?? {} };
|
|
1256
|
+
const refShape = def._shape;
|
|
1257
|
+
if (refShape) {
|
|
1258
|
+
for (const [childKey, childDef] of Object.entries(refShape)) {
|
|
1259
|
+
ref[childKey] = readValue(childDef, childKey, ref);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return ref;
|
|
1263
|
+
}
|
|
1264
|
+
return container?.[key];
|
|
1265
|
+
};
|
|
1266
|
+
const result = {};
|
|
1267
|
+
for (const key in shape) {
|
|
1268
|
+
result[key] = readValue(shape[key], key, stored);
|
|
1269
|
+
}
|
|
1270
|
+
return result;
|
|
1271
|
+
}
|
|
1272
|
+
function sanitizeForLog(shape, data, pseudonymFn) {
|
|
1273
|
+
const visit = (def, value, ctx = {}) => {
|
|
1274
|
+
if (!def) return void 0;
|
|
1275
|
+
const handling = def._pii?.logHandling;
|
|
1276
|
+
if (handling === "omit") return void 0;
|
|
1277
|
+
if (handling === "redact") return "[REDACTED]";
|
|
1278
|
+
if (handling === "pseudonym") return pseudonymFn(value);
|
|
1279
|
+
if (def.type === "object" && def._shape) {
|
|
1280
|
+
const obj = {};
|
|
1281
|
+
const src = value ?? {};
|
|
1282
|
+
for (const [k, childDef] of Object.entries(def._shape)) {
|
|
1283
|
+
const child = visit(childDef, src[k], { parentKey: k });
|
|
1284
|
+
if (child !== void 0) obj[k] = child;
|
|
1285
|
+
}
|
|
1286
|
+
return obj;
|
|
1287
|
+
}
|
|
1288
|
+
if (def.type === "array" && def.itemType) {
|
|
1289
|
+
if (!Array.isArray(value)) return void 0;
|
|
1290
|
+
const arr = value.map((v) => visit(def.itemType, v, { parentKey: ctx.parentKey })).filter((v) => v !== void 0);
|
|
1291
|
+
return arr;
|
|
1292
|
+
}
|
|
1293
|
+
if (def.type === "ref") {
|
|
1294
|
+
const ref = value ? { type: value.type, id: value.id } : {};
|
|
1295
|
+
const refShape = def._shape;
|
|
1296
|
+
const src = value ?? {};
|
|
1297
|
+
if (refShape) {
|
|
1298
|
+
for (const [k, childDef] of Object.entries(refShape)) {
|
|
1299
|
+
const child = visit(childDef, src[k]);
|
|
1300
|
+
if (child !== void 0) ref[k] = child;
|
|
1301
|
+
}
|
|
1302
|
+
} else if (value) {
|
|
1303
|
+
ref.type = value.type;
|
|
1304
|
+
ref.id = value.id;
|
|
1305
|
+
}
|
|
1306
|
+
return ref;
|
|
1307
|
+
}
|
|
1308
|
+
return value;
|
|
1309
|
+
};
|
|
1310
|
+
const output = {};
|
|
1311
|
+
for (const key in shape) {
|
|
1312
|
+
const child = visit(shape[key], data?.[key]);
|
|
1313
|
+
if (child !== void 0) output[key] = child;
|
|
1314
|
+
}
|
|
1315
|
+
return output;
|
|
1316
|
+
}
|
|
1317
|
+
function getPiiAudit(shape) {
|
|
1318
|
+
const piiFields = [];
|
|
1319
|
+
for (const key in shape) {
|
|
1320
|
+
const def = shape[key];
|
|
1321
|
+
if (!def) continue;
|
|
1322
|
+
if (def._pii && def._pii.classification !== "none") {
|
|
1323
|
+
piiFields.push({
|
|
1324
|
+
field: key,
|
|
1325
|
+
classification: def._pii.classification,
|
|
1326
|
+
action: def._pii.action,
|
|
1327
|
+
logHandling: def._pii.logHandling,
|
|
1328
|
+
purpose: def._pii.purpose
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
return piiFields;
|
|
1333
|
+
}
|
|
1334
|
+
function scrubPiiForDelete(shape, stored) {
|
|
1335
|
+
const result = Array.isArray(stored) ? [...stored] : { ...stored };
|
|
1336
|
+
const setAtPath = (target, path, val) => {
|
|
1337
|
+
let cur = target;
|
|
1338
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1339
|
+
const p = path[i];
|
|
1340
|
+
if (p === void 0) return;
|
|
1341
|
+
if (cur[p] === void 0) {
|
|
1342
|
+
const next = path[i + 1];
|
|
1343
|
+
cur[p] = typeof next === "number" ? [] : {};
|
|
1344
|
+
}
|
|
1345
|
+
cur = cur[p];
|
|
1346
|
+
}
|
|
1347
|
+
const last = path[path.length - 1];
|
|
1348
|
+
if (last === void 0) return;
|
|
1349
|
+
cur[last] = val;
|
|
1350
|
+
};
|
|
1351
|
+
const visit = (def, host, path, keyName) => {
|
|
1352
|
+
if (!def) return;
|
|
1353
|
+
const applyPath = (targetKey) => {
|
|
1354
|
+
const last = path[path.length - 1];
|
|
1355
|
+
if (last === void 0) return;
|
|
1356
|
+
if (typeof last === "number") {
|
|
1357
|
+
setAtPath(result, [...path, targetKey], null);
|
|
1358
|
+
} else {
|
|
1359
|
+
setAtPath(result, [...path.slice(0, -1), targetKey], null);
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
if (def._pii?.action === "encrypt") {
|
|
1363
|
+
const targetKey = `${keyName}Encrypted`;
|
|
1364
|
+
if (host && Object.prototype.hasOwnProperty.call(host, targetKey)) {
|
|
1365
|
+
applyPath(targetKey);
|
|
1366
|
+
}
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
if (def._pii?.action === "hash") {
|
|
1370
|
+
const targetKey = `${keyName}Hash`;
|
|
1371
|
+
if (host && Object.prototype.hasOwnProperty.call(host, targetKey)) {
|
|
1372
|
+
applyPath(targetKey);
|
|
1373
|
+
}
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
if (def._pii?.action === "clear") {
|
|
1377
|
+
if (host && Object.prototype.hasOwnProperty.call(host, keyName)) {
|
|
1378
|
+
setAtPath(result, path, null);
|
|
1379
|
+
}
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
if (def.type === "object" && def._shape) {
|
|
1383
|
+
const obj = host && Object.prototype.hasOwnProperty.call(host, keyName) ? host[keyName] : host;
|
|
1384
|
+
for (const [k, childDef] of Object.entries(def._shape)) {
|
|
1385
|
+
visit(childDef, obj, [...path, k], k);
|
|
1386
|
+
}
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
if (def.type === "array" && def.itemType) {
|
|
1390
|
+
const arr = host?.[keyName];
|
|
1391
|
+
if (Array.isArray(arr)) {
|
|
1392
|
+
arr.forEach(
|
|
1393
|
+
(item, idx) => visit(def.itemType, item, [...path, idx], keyName)
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (def.type === "ref") {
|
|
1399
|
+
const refShape = def._shape;
|
|
1400
|
+
const ref = host?.[keyName];
|
|
1401
|
+
if (refShape && ref) {
|
|
1402
|
+
for (const [k, childDef] of Object.entries(refShape)) {
|
|
1403
|
+
visit(childDef, ref, [...path, k], k);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
for (const key in shape) {
|
|
1409
|
+
visit(shape[key], stored, [key], key);
|
|
1410
|
+
}
|
|
1411
|
+
return result;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// src/schema.ts
|
|
1415
|
+
var globalSchemaRegistry = /* @__PURE__ */ new Map();
|
|
1416
|
+
function deepClone(value) {
|
|
1417
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
1418
|
+
const cloneAny = (val) => {
|
|
1419
|
+
if (val === null || typeof val !== "object") return val;
|
|
1420
|
+
if (seen.has(val)) throw new TypeError("Cannot clone circular structures");
|
|
1421
|
+
if (val instanceof Date) return new Date(val.getTime());
|
|
1422
|
+
if (Array.isArray(val)) {
|
|
1423
|
+
const arr = [];
|
|
1424
|
+
seen.set(val, arr);
|
|
1425
|
+
val.forEach((item) => arr.push(cloneAny(item)));
|
|
1426
|
+
return arr;
|
|
1427
|
+
}
|
|
1428
|
+
const out = {};
|
|
1429
|
+
seen.set(val, out);
|
|
1430
|
+
for (const [k, v] of Object.entries(val)) out[k] = cloneAny(v);
|
|
1431
|
+
return out;
|
|
1432
|
+
};
|
|
1433
|
+
return cloneAny(value);
|
|
1434
|
+
}
|
|
1435
|
+
function cmpSemver(a, b) {
|
|
1436
|
+
const pa = a.split(".").map((n) => parseInt(n, 10));
|
|
1437
|
+
const pb = b.split(".").map((n) => parseInt(n, 10));
|
|
1438
|
+
for (let i = 0; i < 3; i++) {
|
|
1439
|
+
const ai = pa[i] ?? 0;
|
|
1440
|
+
const bi = pb[i] ?? 0;
|
|
1441
|
+
if (ai > bi) return 1;
|
|
1442
|
+
if (ai < bi) return -1;
|
|
1443
|
+
}
|
|
1444
|
+
return 0;
|
|
1445
|
+
}
|
|
1446
|
+
function applySchemaUpgrade(spec, input, ctx) {
|
|
1447
|
+
if (typeof spec === "function") {
|
|
1448
|
+
return spec(input, ctx);
|
|
1449
|
+
}
|
|
1450
|
+
const steps = [...spec].sort((s1, s2) => cmpSemver(s1.to, s2.to));
|
|
1451
|
+
let working = { ...input };
|
|
1452
|
+
let fromVersion = ctx.from;
|
|
1453
|
+
for (const step of steps) {
|
|
1454
|
+
if (cmpSemver(fromVersion, step.to) < 0 && cmpSemver(step.to, ctx.to) <= 0) {
|
|
1455
|
+
ctx.log?.(`Upgrading entity from v${fromVersion} \u2192 v${step.to}`);
|
|
1456
|
+
const res = step.run(working, { ...ctx, from: fromVersion, to: step.to });
|
|
1457
|
+
if (!res || res.ok !== true || !res.value) {
|
|
1458
|
+
return {
|
|
1459
|
+
ok: false,
|
|
1460
|
+
errors: res?.errors?.length ? res.errors : [`Failed to upgrade entity from v${fromVersion} to v${step.to}`]
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
working = res.value;
|
|
1464
|
+
fromVersion = step.to;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return { ok: true, value: working };
|
|
1468
|
+
}
|
|
1469
|
+
function validateEnum(parentKey, value, enumValues) {
|
|
1470
|
+
if (!enumValues) return;
|
|
1471
|
+
const values = Array.isArray(enumValues) ? enumValues : Array.from(enumValues);
|
|
1472
|
+
if (Array.isArray(value)) {
|
|
1473
|
+
const invalid = value.filter((v) => !values.includes(v));
|
|
1474
|
+
if (invalid.length > 0) {
|
|
1475
|
+
return `Field ${parentKey} contains invalid enum values: ${invalid.join(
|
|
1476
|
+
", "
|
|
1477
|
+
)}`;
|
|
1478
|
+
}
|
|
1479
|
+
} else {
|
|
1480
|
+
if (!values.includes(value)) {
|
|
1481
|
+
return `Field ${parentKey} must be one of: ${values.join(", ")}`;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
function getEnumValues(def) {
|
|
1486
|
+
const src = Array.isArray(def?.enum) ? def.enum : Array.isArray(def?.enumValues) ? def.enumValues : Array.isArray(def?._enum) ? def._enum : Array.isArray(def?._enumValues) ? def._enumValues : def?._enumSet instanceof Set ? Array.from(def._enumSet) : void 0;
|
|
1487
|
+
if (!src) return void 0;
|
|
1488
|
+
const ok = src.every(
|
|
1489
|
+
(v) => typeof v === "string" || typeof v === "number"
|
|
1490
|
+
);
|
|
1491
|
+
return ok ? src : void 0;
|
|
1492
|
+
}
|
|
1493
|
+
function applyDefault(def, current) {
|
|
1494
|
+
if ((current === void 0 || current === null) && typeof def?.getDefault === "function") {
|
|
1495
|
+
const next = def.getDefault();
|
|
1496
|
+
if (next !== void 0) {
|
|
1497
|
+
return { value: next, applied: true };
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return { value: current, applied: false };
|
|
1501
|
+
}
|
|
1502
|
+
function isOptional(def) {
|
|
1503
|
+
return def?.isRequired === false;
|
|
1504
|
+
}
|
|
1505
|
+
function getValidator(def) {
|
|
1506
|
+
return def?._validator ?? void 0;
|
|
1507
|
+
}
|
|
1508
|
+
function getShape(def) {
|
|
1509
|
+
return def?._shape ?? def?.shape ?? void 0;
|
|
1510
|
+
}
|
|
1511
|
+
function checkMissingRequired(parentKey, key, value, def, errors) {
|
|
1512
|
+
if (value === void 0 || value === null) {
|
|
1513
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1514
|
+
if (!isOptional(def)) {
|
|
1515
|
+
errors.push(`Missing required field: ${path}`);
|
|
1516
|
+
}
|
|
1517
|
+
return { missing: true };
|
|
1518
|
+
}
|
|
1519
|
+
return { missing: false };
|
|
1520
|
+
}
|
|
1521
|
+
function checkImmutable(parentKey, key, value, def, existing, errors) {
|
|
1522
|
+
if (def.isImmutable && existing && existing[key] !== void 0 && value !== existing[key]) {
|
|
1523
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1524
|
+
errors.push(`Field is immutable: ${path}`);
|
|
1525
|
+
return { immutableViolation: true };
|
|
1526
|
+
}
|
|
1527
|
+
return { immutableViolation: false };
|
|
1528
|
+
}
|
|
1529
|
+
function runCustomValidator(parentKey, key, value, def, errors) {
|
|
1530
|
+
const validator = getValidator(def);
|
|
1531
|
+
if (validator && value !== void 0 && value !== null) {
|
|
1532
|
+
const valid = validator(value);
|
|
1533
|
+
if (!valid) {
|
|
1534
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1535
|
+
errors.push(`Invalid value for field: ${path}`);
|
|
1536
|
+
return { invalid: true };
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return { invalid: false };
|
|
1540
|
+
}
|
|
1541
|
+
function validateStringField(parentKey, key, value, def, errors) {
|
|
1542
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1543
|
+
if (typeof value !== "string") {
|
|
1544
|
+
errors.push(`Field ${path} must be string`);
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const enumValues = getEnumValues(def);
|
|
1548
|
+
if (Array.isArray(enumValues)) {
|
|
1549
|
+
const enumError = validateEnum(path, value, enumValues);
|
|
1550
|
+
if (enumError) {
|
|
1551
|
+
errors.push(enumError);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
function validateNumberField(parentKey, key, value, def, errors) {
|
|
1556
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1557
|
+
if (typeof value !== "number") {
|
|
1558
|
+
errors.push(`Field ${path} must be number`);
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
const enumValues = getEnumValues(def);
|
|
1562
|
+
if (Array.isArray(enumValues)) {
|
|
1563
|
+
const enumError = validateEnum(path, value, enumValues);
|
|
1564
|
+
if (enumError) {
|
|
1565
|
+
errors.push(enumError);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
function validateBooleanField(parentKey, key, value, _def, errors) {
|
|
1570
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1571
|
+
if (typeof value !== "boolean") {
|
|
1572
|
+
errors.push(`Field ${path} must be boolean`);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
function validateObjectChildren(parentKey, obj, shape, errors, existing, piiEnforcement, logger) {
|
|
1576
|
+
for (const [childKey, childDef] of Object.entries(shape)) {
|
|
1577
|
+
let childValue = obj[childKey];
|
|
1578
|
+
const existingChild = existing && typeof existing === "object" ? existing[childKey] : void 0;
|
|
1579
|
+
const { value: withDefault, applied } = applyDefault(childDef, childValue);
|
|
1580
|
+
if (applied) {
|
|
1581
|
+
obj[childKey] = withDefault;
|
|
1582
|
+
childValue = withDefault;
|
|
1583
|
+
}
|
|
1584
|
+
const { missing } = checkMissingRequired(
|
|
1585
|
+
parentKey,
|
|
1586
|
+
childKey,
|
|
1587
|
+
childValue,
|
|
1588
|
+
childDef,
|
|
1589
|
+
errors
|
|
1590
|
+
);
|
|
1591
|
+
if (missing) continue;
|
|
1592
|
+
const { immutableViolation } = checkImmutable(
|
|
1593
|
+
parentKey,
|
|
1594
|
+
childKey,
|
|
1595
|
+
childValue,
|
|
1596
|
+
childDef,
|
|
1597
|
+
existing,
|
|
1598
|
+
errors
|
|
1599
|
+
);
|
|
1600
|
+
if (immutableViolation) continue;
|
|
1601
|
+
const { shortCircuit } = enforcePIIField(
|
|
1602
|
+
parentKey,
|
|
1603
|
+
childKey,
|
|
1604
|
+
childValue,
|
|
1605
|
+
childDef,
|
|
1606
|
+
piiEnforcement,
|
|
1607
|
+
errors,
|
|
1608
|
+
logger
|
|
1609
|
+
);
|
|
1610
|
+
if (shortCircuit) continue;
|
|
1611
|
+
const { invalid } = runCustomValidator(
|
|
1612
|
+
parentKey,
|
|
1613
|
+
childKey,
|
|
1614
|
+
childValue,
|
|
1615
|
+
childDef,
|
|
1616
|
+
errors
|
|
1617
|
+
);
|
|
1618
|
+
if (invalid) continue;
|
|
1619
|
+
validateByType(
|
|
1620
|
+
parentKey,
|
|
1621
|
+
childKey,
|
|
1622
|
+
childValue,
|
|
1623
|
+
childDef,
|
|
1624
|
+
errors,
|
|
1625
|
+
existingChild,
|
|
1626
|
+
piiEnforcement,
|
|
1627
|
+
logger
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function validateObjectField(parentKey, key, value, def, errors, existing, piiEnforcement, logger) {
|
|
1632
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1633
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1634
|
+
errors.push(`Field ${path} must be object`);
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
const objShape = getShape(def);
|
|
1638
|
+
if (objShape)
|
|
1639
|
+
validateObjectChildren(path, value, objShape, errors, existing, piiEnforcement, logger);
|
|
1640
|
+
}
|
|
1641
|
+
function validateArrayOfStrings(parentKey, key, arr, itemDef, errors) {
|
|
1642
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1643
|
+
if (!arr.every((v) => typeof v === "string")) {
|
|
1644
|
+
errors.push(`Field ${path} must be string[]`);
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
const enumValues = getEnumValues(itemDef);
|
|
1648
|
+
if (Array.isArray(enumValues)) {
|
|
1649
|
+
const enumError = validateEnum(path, arr, enumValues);
|
|
1650
|
+
if (enumError) {
|
|
1651
|
+
errors.push(enumError);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
const validator = getValidator(itemDef);
|
|
1655
|
+
if (validator) {
|
|
1656
|
+
arr.forEach((v, idx) => {
|
|
1657
|
+
if (!validator(v)) {
|
|
1658
|
+
errors.push(`Invalid value for field: ${path}[${idx}]`);
|
|
1659
|
+
}
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
function validateArrayOfNumbers(parentKey, key, arr, itemDef, errors) {
|
|
1664
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1665
|
+
if (!arr.every((v) => typeof v === "number")) {
|
|
1666
|
+
errors.push(`Field ${path} must be number[]`);
|
|
1667
|
+
}
|
|
1668
|
+
const enumValues = getEnumValues(itemDef);
|
|
1669
|
+
if (Array.isArray(enumValues)) {
|
|
1670
|
+
const enumError = validateEnum(path, arr, enumValues);
|
|
1671
|
+
if (enumError) {
|
|
1672
|
+
errors.push(enumError);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
const validator = getValidator(itemDef);
|
|
1676
|
+
if (validator) {
|
|
1677
|
+
arr.forEach((v, idx) => {
|
|
1678
|
+
if (!validator(v)) {
|
|
1679
|
+
errors.push(`Invalid value for field: ${path}[${idx}]`);
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
function validateArrayOfBooleans(parentKey, key, arr, _itemDef, errors) {
|
|
1685
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1686
|
+
if (!arr.every((v) => typeof v === "boolean")) {
|
|
1687
|
+
errors.push(`Field ${path} must be boolean[]`);
|
|
1688
|
+
}
|
|
1689
|
+
const validator = getValidator(_itemDef);
|
|
1690
|
+
if (validator) {
|
|
1691
|
+
arr.forEach((v, idx) => {
|
|
1692
|
+
if (!validator(v)) {
|
|
1693
|
+
errors.push(`Invalid value for field: ${path}[${idx}]`);
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
function validateArrayOfObjects(parentKey, key, arr, itemDef, errors, existing, piiEnforcement, logger) {
|
|
1699
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1700
|
+
if (!Array.isArray(arr) || !arr.every((v) => typeof v === "object" && v !== null && !Array.isArray(v))) {
|
|
1701
|
+
errors.push(`Field ${path} must be object[]`);
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
const itemShape = getShape(itemDef);
|
|
1705
|
+
if (!itemShape) return;
|
|
1706
|
+
arr.forEach((item, idx) => {
|
|
1707
|
+
const itemParent = `${path}[${idx}]`;
|
|
1708
|
+
const existingItem = Array.isArray(existing) ? existing[idx] : void 0;
|
|
1709
|
+
for (const [childKey, childDef] of Object.entries(itemShape)) {
|
|
1710
|
+
let childValue = item[childKey];
|
|
1711
|
+
const existingChild = existingItem && typeof existingItem === "object" ? existingItem[childKey] : void 0;
|
|
1712
|
+
const { value: withDefault, applied } = applyDefault(
|
|
1713
|
+
childDef,
|
|
1714
|
+
childValue
|
|
1715
|
+
);
|
|
1716
|
+
if (applied) {
|
|
1717
|
+
item[childKey] = withDefault;
|
|
1718
|
+
childValue = withDefault;
|
|
1719
|
+
}
|
|
1720
|
+
const { missing } = checkMissingRequired(
|
|
1721
|
+
itemParent,
|
|
1722
|
+
childKey,
|
|
1723
|
+
childValue,
|
|
1724
|
+
childDef,
|
|
1725
|
+
errors
|
|
1726
|
+
);
|
|
1727
|
+
if (missing) continue;
|
|
1728
|
+
const { immutableViolation } = checkImmutable(
|
|
1729
|
+
itemParent,
|
|
1730
|
+
childKey,
|
|
1731
|
+
childValue,
|
|
1732
|
+
childDef,
|
|
1733
|
+
existingItem,
|
|
1734
|
+
errors
|
|
1735
|
+
);
|
|
1736
|
+
if (immutableViolation) continue;
|
|
1737
|
+
const { shortCircuit } = enforcePIIField(
|
|
1738
|
+
itemParent,
|
|
1739
|
+
childKey,
|
|
1740
|
+
childValue,
|
|
1741
|
+
childDef,
|
|
1742
|
+
piiEnforcement,
|
|
1743
|
+
errors,
|
|
1744
|
+
logger
|
|
1745
|
+
);
|
|
1746
|
+
if (shortCircuit) continue;
|
|
1747
|
+
const { invalid } = runCustomValidator(
|
|
1748
|
+
itemParent,
|
|
1749
|
+
childKey,
|
|
1750
|
+
childValue,
|
|
1751
|
+
childDef,
|
|
1752
|
+
errors
|
|
1753
|
+
);
|
|
1754
|
+
if (invalid) continue;
|
|
1755
|
+
validateByType(
|
|
1756
|
+
itemParent,
|
|
1757
|
+
childKey,
|
|
1758
|
+
childValue,
|
|
1759
|
+
childDef,
|
|
1760
|
+
errors,
|
|
1761
|
+
existingChild,
|
|
1762
|
+
piiEnforcement,
|
|
1763
|
+
logger
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
function validateArrayField(parentKey, key, value, def, errors, existing, piiEnforcement, logger) {
|
|
1769
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1770
|
+
if (!Array.isArray(value)) {
|
|
1771
|
+
errors.push(`Field ${key} must be an array`);
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
const itemType = def.itemType?.type;
|
|
1775
|
+
if (itemType === "string")
|
|
1776
|
+
return validateArrayOfStrings(parentKey, key, value, def.itemType, errors);
|
|
1777
|
+
if (itemType === "number")
|
|
1778
|
+
return validateArrayOfNumbers(parentKey, key, value, def.itemType, errors);
|
|
1779
|
+
if (itemType === "boolean")
|
|
1780
|
+
return validateArrayOfBooleans(parentKey, key, value, def.itemType, errors);
|
|
1781
|
+
if (itemType === "object")
|
|
1782
|
+
return validateArrayOfObjects(
|
|
1783
|
+
parentKey,
|
|
1784
|
+
key,
|
|
1785
|
+
value,
|
|
1786
|
+
def.itemType,
|
|
1787
|
+
errors,
|
|
1788
|
+
existing,
|
|
1789
|
+
piiEnforcement,
|
|
1790
|
+
logger
|
|
1791
|
+
);
|
|
1792
|
+
if (itemType === "ref") {
|
|
1793
|
+
const expectedType = def.itemType.refType;
|
|
1794
|
+
value.forEach((ref, idx) => {
|
|
1795
|
+
if (!ref || typeof ref !== "object" || ref === null || typeof ref.type !== "string" || typeof ref.id !== "string" || expectedType && ref.type !== expectedType) {
|
|
1796
|
+
errors.push(
|
|
1797
|
+
`Field ${path}[${idx}] must be a reference object with type: ${expectedType}`
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
const refShape = getShape(def.itemType);
|
|
1802
|
+
if (refShape) {
|
|
1803
|
+
const existingRefs = Array.isArray(existing) ? existing : [];
|
|
1804
|
+
value.forEach((ref, idx) => {
|
|
1805
|
+
if (ref && typeof ref === "object" && ref !== null) {
|
|
1806
|
+
const existingRef = existingRefs[idx];
|
|
1807
|
+
for (const [childKey, childDef] of Object.entries(refShape)) {
|
|
1808
|
+
const childPath = `${path}[${idx}].${childKey}`;
|
|
1809
|
+
let childValue = ref[childKey];
|
|
1810
|
+
const existingChild = existingRef && typeof existingRef === "object" ? existingRef[childKey] : void 0;
|
|
1811
|
+
const { value: withDefault, applied } = applyDefault(
|
|
1812
|
+
childDef,
|
|
1813
|
+
childValue
|
|
1814
|
+
);
|
|
1815
|
+
if (applied) {
|
|
1816
|
+
ref[childKey] = withDefault;
|
|
1817
|
+
childValue = withDefault;
|
|
1818
|
+
}
|
|
1819
|
+
if ((childValue === void 0 || childValue === null) && !isOptional(childDef)) {
|
|
1820
|
+
errors.push(`Missing required field: ${childPath}`);
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
const { immutableViolation } = checkImmutable(
|
|
1824
|
+
`${path}[${idx}]`,
|
|
1825
|
+
childKey,
|
|
1826
|
+
childValue,
|
|
1827
|
+
childDef,
|
|
1828
|
+
existingRef,
|
|
1829
|
+
errors
|
|
1830
|
+
);
|
|
1831
|
+
if (immutableViolation) continue;
|
|
1832
|
+
const { shortCircuit } = enforcePIIField(
|
|
1833
|
+
`${path}[${idx}]`,
|
|
1834
|
+
childKey,
|
|
1835
|
+
childValue,
|
|
1836
|
+
childDef,
|
|
1837
|
+
piiEnforcement,
|
|
1838
|
+
errors,
|
|
1839
|
+
logger
|
|
1840
|
+
);
|
|
1841
|
+
if (shortCircuit) continue;
|
|
1842
|
+
const childValidator = getValidator(childDef);
|
|
1843
|
+
if (childValidator && childValue !== void 0 && childValue !== null) {
|
|
1844
|
+
const valid = childValidator(childValue);
|
|
1845
|
+
if (!valid) {
|
|
1846
|
+
errors.push(`Invalid value for field: ${childPath}`);
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
validateByType(
|
|
1851
|
+
`${path}[${idx}]`,
|
|
1852
|
+
childKey,
|
|
1853
|
+
childValue,
|
|
1854
|
+
childDef,
|
|
1855
|
+
errors,
|
|
1856
|
+
existingChild,
|
|
1857
|
+
piiEnforcement,
|
|
1858
|
+
logger
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
errors.push(`Field ${path} has unsupported array item type`);
|
|
1867
|
+
}
|
|
1868
|
+
function validateRefField(parentKey, key, value, def, errors, _existing, _piiEnforcement, _logger) {
|
|
1869
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1870
|
+
if (typeof value !== "object" || value === null || typeof value.type !== "string" || typeof value.id !== "string") {
|
|
1871
|
+
errors.push(`Field ${path} must be { type: string; id: string }`);
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
const expectedType = def.refType;
|
|
1875
|
+
if (expectedType && value.type !== expectedType) {
|
|
1876
|
+
errors.push(
|
|
1877
|
+
`Field ${path} must reference type: ${expectedType} (got ${value.type})`
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
function validateByType(parentKey, key, value, def, errors, existing, piiEnforcement, logger) {
|
|
1882
|
+
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1883
|
+
switch (def.type) {
|
|
1884
|
+
case "string":
|
|
1885
|
+
return validateStringField(parentKey, key, value, def, errors);
|
|
1886
|
+
case "number":
|
|
1887
|
+
return validateNumberField(parentKey, key, value, def, errors);
|
|
1888
|
+
case "boolean":
|
|
1889
|
+
return validateBooleanField(parentKey, key, value, def, errors);
|
|
1890
|
+
case "object":
|
|
1891
|
+
return validateObjectField(
|
|
1892
|
+
parentKey,
|
|
1893
|
+
key,
|
|
1894
|
+
value,
|
|
1895
|
+
def,
|
|
1896
|
+
errors,
|
|
1897
|
+
existing,
|
|
1898
|
+
piiEnforcement,
|
|
1899
|
+
logger
|
|
1900
|
+
);
|
|
1901
|
+
case "array":
|
|
1902
|
+
return validateArrayField(
|
|
1903
|
+
parentKey,
|
|
1904
|
+
key,
|
|
1905
|
+
value,
|
|
1906
|
+
def,
|
|
1907
|
+
errors,
|
|
1908
|
+
existing,
|
|
1909
|
+
piiEnforcement,
|
|
1910
|
+
logger
|
|
1911
|
+
);
|
|
1912
|
+
case "ref":
|
|
1913
|
+
return validateRefField(parentKey, key, value, def, errors, existing, piiEnforcement, logger);
|
|
1914
|
+
default:
|
|
1915
|
+
errors.push(`Unknown type for field ${path}: ${def.type}`);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
function createSchema(_shape, entityType, options = {
|
|
1919
|
+
version: "1.0.0",
|
|
1920
|
+
table: "",
|
|
1921
|
+
schemaValidator: () => true,
|
|
1922
|
+
piiEnforcement: "none",
|
|
1923
|
+
schemaUpgrade: void 0
|
|
1924
|
+
}) {
|
|
1925
|
+
const systemFields = {
|
|
1926
|
+
type: field.string().immutable().system(),
|
|
1927
|
+
version: field.string().immutable().system().validator(validateSemVer)
|
|
1928
|
+
};
|
|
1929
|
+
const version = options.version || "1.0.0";
|
|
1930
|
+
const store = options.table || "";
|
|
1931
|
+
const schemaUpgrade = options.schemaUpgrade;
|
|
1932
|
+
const schema = {
|
|
1933
|
+
// 🔗 Define the schema shape
|
|
1934
|
+
_shape: {
|
|
1935
|
+
...systemFields,
|
|
1936
|
+
..._shape
|
|
1937
|
+
},
|
|
1938
|
+
// 🔗 Metadata about the schema
|
|
1939
|
+
meta: { entityType, version },
|
|
1940
|
+
// 🔗 Validate input against the schema
|
|
1941
|
+
validate(input, existing) {
|
|
1942
|
+
const errors = [];
|
|
1943
|
+
const result = {};
|
|
1944
|
+
const piiMode = options.piiEnforcement ?? "none";
|
|
1945
|
+
if (typeof input !== "object" || input === null) {
|
|
1946
|
+
return { valid: false, errors: ["Input must be an object"] };
|
|
1947
|
+
}
|
|
1948
|
+
const working = typeof input === "object" && input !== null ? deepClone(input) : { ...input };
|
|
1949
|
+
if (working.type == null) working.type = entityType;
|
|
1950
|
+
if (working.version == null) working.version = version;
|
|
1951
|
+
const fromVersion = String(working.version ?? "0.0.0");
|
|
1952
|
+
const toVersion = String(version);
|
|
1953
|
+
if (schemaUpgrade && cmpSemver(fromVersion, toVersion) < 0) {
|
|
1954
|
+
const upgradeRes = applySchemaUpgrade(schemaUpgrade, { ...working }, {
|
|
1955
|
+
from: fromVersion,
|
|
1956
|
+
to: toVersion,
|
|
1957
|
+
entityType,
|
|
1958
|
+
describe: () => schema.describe()
|
|
1959
|
+
});
|
|
1960
|
+
if (!upgradeRes || upgradeRes.ok !== true || !upgradeRes.value) {
|
|
1961
|
+
const errs = upgradeRes?.errors?.length ? upgradeRes.errors : [`Failed to upgrade entity from v${fromVersion} to v${toVersion}`];
|
|
1962
|
+
errors.push(...errs);
|
|
1963
|
+
return { valid: false, errors };
|
|
1964
|
+
}
|
|
1965
|
+
for (const k of Object.keys(working)) delete working[k];
|
|
1966
|
+
Object.assign(working, upgradeRes.value);
|
|
1967
|
+
working.type = entityType;
|
|
1968
|
+
working.version = toVersion;
|
|
1969
|
+
}
|
|
1970
|
+
for (const key in schema._shape) {
|
|
1971
|
+
const def = schema._shape[key];
|
|
1972
|
+
let value = working[key];
|
|
1973
|
+
const existingField = existing && typeof existing === "object" ? existing[key] : void 0;
|
|
1974
|
+
if (!def) {
|
|
1975
|
+
errors.push(`Field definition missing for: ${key}`);
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
const { value: withDefault, applied } = applyDefault(def, value);
|
|
1979
|
+
if (applied) {
|
|
1980
|
+
working[key] = withDefault;
|
|
1981
|
+
value = withDefault;
|
|
1982
|
+
}
|
|
1983
|
+
const { missing } = checkMissingRequired("", key, value, def, errors);
|
|
1984
|
+
if (missing) continue;
|
|
1985
|
+
const { immutableViolation } = checkImmutable(
|
|
1986
|
+
"",
|
|
1987
|
+
key,
|
|
1988
|
+
value,
|
|
1989
|
+
def,
|
|
1990
|
+
existing,
|
|
1991
|
+
errors
|
|
1992
|
+
);
|
|
1993
|
+
if (immutableViolation) continue;
|
|
1994
|
+
const { shortCircuit } = enforcePIIField(
|
|
1995
|
+
"",
|
|
1996
|
+
key,
|
|
1997
|
+
value,
|
|
1998
|
+
def,
|
|
1999
|
+
piiMode,
|
|
2000
|
+
errors,
|
|
2001
|
+
console
|
|
2002
|
+
);
|
|
2003
|
+
if (shortCircuit) continue;
|
|
2004
|
+
const validateField = (val) => {
|
|
2005
|
+
const localErrors = [];
|
|
2006
|
+
const { invalid } = runCustomValidator(
|
|
2007
|
+
"",
|
|
2008
|
+
key,
|
|
2009
|
+
val,
|
|
2010
|
+
def,
|
|
2011
|
+
localErrors
|
|
2012
|
+
);
|
|
2013
|
+
if (!invalid) {
|
|
2014
|
+
validateByType("", key, val, def, localErrors, existingField, piiMode, console);
|
|
2015
|
+
}
|
|
2016
|
+
return localErrors;
|
|
2017
|
+
};
|
|
2018
|
+
let fieldValue = value;
|
|
2019
|
+
let fieldErrors = validateField(fieldValue);
|
|
2020
|
+
const entityFrom = String(working.version ?? "0.0.0");
|
|
2021
|
+
const entityTo = String(version);
|
|
2022
|
+
const fieldTo = String(def._version ?? entityTo);
|
|
2023
|
+
const hasUpgrader = typeof def._upgrade === "function";
|
|
2024
|
+
if (fieldErrors.length > 0 && hasUpgrader && cmpSemver(entityFrom, entityTo) < 0) {
|
|
2025
|
+
const up = def._upgrade;
|
|
2026
|
+
const res = up(fieldValue, {
|
|
2027
|
+
entityFrom,
|
|
2028
|
+
entityTo,
|
|
2029
|
+
fieldTo,
|
|
2030
|
+
fieldName: key
|
|
2031
|
+
});
|
|
2032
|
+
if (res && res.ok) {
|
|
2033
|
+
fieldValue = res.value;
|
|
2034
|
+
fieldErrors = validateField(fieldValue);
|
|
2035
|
+
} else {
|
|
2036
|
+
fieldErrors.push(
|
|
2037
|
+
res?.error || `Failed to upgrade field ${key} from v${entityFrom} to v${entityTo}`
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
result[key] = fieldValue;
|
|
2042
|
+
if (fieldErrors.length !== 0) {
|
|
2043
|
+
errors.push(...fieldErrors);
|
|
2044
|
+
continue;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
if (errors.length === 0 && options.schemaValidator) {
|
|
2048
|
+
const castValue = result;
|
|
2049
|
+
if (!options.schemaValidator(castValue)) {
|
|
2050
|
+
errors.push("Schema-level validation failed.");
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
return {
|
|
2054
|
+
valid: errors.length === 0,
|
|
2055
|
+
value: result,
|
|
2056
|
+
errors
|
|
2057
|
+
};
|
|
2058
|
+
},
|
|
2059
|
+
// specific validator for a schema to allow conditional validation
|
|
2060
|
+
schemaValidator: options.schemaValidator,
|
|
2061
|
+
// <== expose it here!
|
|
2062
|
+
/**
|
|
2063
|
+
* Runs the optional schema-level upgrade function once, without validating.
|
|
2064
|
+
* Useful for offline migrations or testing migration logic.
|
|
2065
|
+
*/
|
|
2066
|
+
upgrade(input, log) {
|
|
2067
|
+
const fromVersion = String(input?.version ?? "0.0.0");
|
|
2068
|
+
const toVersion = String(version);
|
|
2069
|
+
if (!schemaUpgrade || cmpSemver(fromVersion, toVersion) >= 0) {
|
|
2070
|
+
return {
|
|
2071
|
+
ok: true,
|
|
2072
|
+
value: { ...input, type: entityType, version: toVersion }
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
const res = applySchemaUpgrade(
|
|
2076
|
+
schemaUpgrade,
|
|
2077
|
+
{ ...input },
|
|
2078
|
+
{
|
|
2079
|
+
from: fromVersion,
|
|
2080
|
+
to: toVersion,
|
|
2081
|
+
entityType,
|
|
2082
|
+
describe: () => schema.describe(),
|
|
2083
|
+
log
|
|
2084
|
+
}
|
|
2085
|
+
);
|
|
2086
|
+
if (res && res.ok && res.value) {
|
|
2087
|
+
const out = { ...res.value, type: entityType, version: toVersion };
|
|
2088
|
+
return { ok: true, value: out };
|
|
2089
|
+
}
|
|
2090
|
+
return {
|
|
2091
|
+
ok: false,
|
|
2092
|
+
errors: res?.errors?.length ? res.errors : [`Failed to upgrade entity from v${fromVersion} to v${toVersion}`]
|
|
2093
|
+
};
|
|
2094
|
+
},
|
|
2095
|
+
/**
|
|
2096
|
+
* Recursively validates entity references defined in this schema.
|
|
2097
|
+
*
|
|
2098
|
+
* Traverses fields of type `ref` and arrays of `ref` and resolves each target
|
|
2099
|
+
* entity using the provided `resolveEntity` function. When `autoValidate` is
|
|
2100
|
+
* enabled (default) and the field's `refPolicy` is `eager`, the referenced
|
|
2101
|
+
* entity's schema is fetched and validated via `validateComposition` up to
|
|
2102
|
+
* `maxDepth` levels.
|
|
2103
|
+
*
|
|
2104
|
+
* Skips fields not listed in `onlyFields` when provided. Prevents cycles via
|
|
2105
|
+
* a `visited` set in `validatorContext`.
|
|
2106
|
+
*
|
|
2107
|
+
* @param entity The root entity to validate (must include `type` and `id`).
|
|
2108
|
+
* @param options Options controlling traversal and resolution behavior.
|
|
2109
|
+
* @param options.resolveEntity Function to resolve a referenced entity by type and id.
|
|
2110
|
+
* @param options.validatorContext Internal context (visited set) to prevent cycles.
|
|
2111
|
+
* @param options.maxDepth Maximum depth for recursive validation (default: 5).
|
|
2112
|
+
* @param options.onlyFields Optional whitelist of field names to validate.
|
|
2113
|
+
* @param options.log Optional logger for traversal/debug output.
|
|
2114
|
+
*
|
|
2115
|
+
* @throws Error if a broken reference is encountered (target cannot be resolved).
|
|
2116
|
+
*/
|
|
2117
|
+
async validateComposition(entity, options2) {
|
|
2118
|
+
const {
|
|
2119
|
+
resolveEntity,
|
|
2120
|
+
validatorContext = { visited: /* @__PURE__ */ new Set() },
|
|
2121
|
+
maxDepth = 5,
|
|
2122
|
+
log
|
|
2123
|
+
} = options2;
|
|
2124
|
+
const entityKey = `${entity.type}:${entity.id}`;
|
|
2125
|
+
if (validatorContext.visited.has(entityKey)) {
|
|
2126
|
+
log?.(`Skipping already visited entity ${entityKey}`);
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
validatorContext.visited.add(entityKey);
|
|
2130
|
+
log?.(`Validating composition for entity ${entityKey}`);
|
|
2131
|
+
for (const [key, def] of Object.entries(schema._shape)) {
|
|
2132
|
+
if (options2.onlyFields && !options2.onlyFields.includes(key)) {
|
|
2133
|
+
log?.(`Skipping field ${key} (not in onlyFields)`);
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
const autoValidate = def.autoValidate !== false;
|
|
2137
|
+
const refPolicy = def.refPolicy ?? "eager";
|
|
2138
|
+
const value = entity[key];
|
|
2139
|
+
if (!value) continue;
|
|
2140
|
+
if (def.type === "ref") {
|
|
2141
|
+
const refType = def.refType;
|
|
2142
|
+
if (!refType)
|
|
2143
|
+
throw new Error(`Missing refType for reference field ${key}`);
|
|
2144
|
+
const ref = value;
|
|
2145
|
+
if (ref.type && ref.type !== refType) {
|
|
2146
|
+
throw new Error(
|
|
2147
|
+
`Reference type mismatch for field ${key}: expected ${refType}, got ${ref.type}`
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
const target = await options2.resolveEntity(refType, ref.id);
|
|
2151
|
+
if (!target)
|
|
2152
|
+
throw new Error(
|
|
2153
|
+
`Broken reference: ${refType} ${ref.id} in field ${key}`
|
|
2154
|
+
);
|
|
2155
|
+
log?.(`Resolved ${refType} ${ref.id} from field ${key}`);
|
|
2156
|
+
if (autoValidate && refPolicy === "eager") {
|
|
2157
|
+
const targetSchema = getSchemaForType(refType);
|
|
2158
|
+
if (options2.maxDepth > 0 && targetSchema) {
|
|
2159
|
+
await targetSchema.validateComposition(target, {
|
|
2160
|
+
...options2,
|
|
2161
|
+
maxDepth: options2.maxDepth - 1
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
} else if (def.type === "array" && def.itemType?.type === "ref") {
|
|
2166
|
+
const refType = def.itemType.refType;
|
|
2167
|
+
if (!refType)
|
|
2168
|
+
throw new Error(`Missing refType for reference array field ${key}`);
|
|
2169
|
+
const refs = value;
|
|
2170
|
+
for (const ref of refs) {
|
|
2171
|
+
if (ref.type && ref.type !== refType) {
|
|
2172
|
+
throw new Error(
|
|
2173
|
+
`Reference type mismatch for field ${key}: expected ${refType}, got ${ref.type}`
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
const target = await options2.resolveEntity(refType, ref.id);
|
|
2177
|
+
if (!target)
|
|
2178
|
+
throw new Error(
|
|
2179
|
+
`Broken reference: ${refType} ${ref.id} in field ${key}`
|
|
2180
|
+
);
|
|
2181
|
+
log?.(`Resolved ${refType} ${ref.id} from field ${key}`);
|
|
2182
|
+
if (autoValidate && refPolicy === "eager") {
|
|
2183
|
+
const targetSchema = getSchemaForType(refType);
|
|
2184
|
+
if (options2.maxDepth > 0 && targetSchema) {
|
|
2185
|
+
await targetSchema.validateComposition(target, {
|
|
2186
|
+
...options2,
|
|
2187
|
+
maxDepth: options2.maxDepth - 1
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
},
|
|
2195
|
+
/**
|
|
2196
|
+
* Returns the configured table name for this schema.
|
|
2197
|
+
*
|
|
2198
|
+
* @throws Error if no store/table name has been defined for this schema.
|
|
2199
|
+
*/
|
|
2200
|
+
tableName() {
|
|
2201
|
+
if (!store || store === "") {
|
|
2202
|
+
throw new Error("Store is not defined for this schema");
|
|
2203
|
+
}
|
|
2204
|
+
return store;
|
|
2205
|
+
},
|
|
2206
|
+
/**
|
|
2207
|
+
* Transforms an input object for persistence by applying PII protection
|
|
2208
|
+
* according to field annotations (e.g., encryption and hashing).
|
|
2209
|
+
*
|
|
2210
|
+
* @param input The raw entity data.
|
|
2211
|
+
* @param encryptFn Function used to encrypt sensitive values.
|
|
2212
|
+
* @param hashFn Function used to hash sensitive values.
|
|
2213
|
+
* @returns A new object safe to store.
|
|
2214
|
+
*/
|
|
2215
|
+
prepareForStorage(input, encryptFn, hashFn) {
|
|
2216
|
+
return prepareForStorage(_shape, input, encryptFn, hashFn);
|
|
2217
|
+
},
|
|
2218
|
+
/**
|
|
2219
|
+
* Reverses storage transformations for read paths (e.g., decrypts values)
|
|
2220
|
+
* according to PII annotations, returning a consumer-friendly object.
|
|
2221
|
+
*
|
|
2222
|
+
* @param stored Data retrieved from storage.
|
|
2223
|
+
* @param decryptFn Function used to decrypt values that were encrypted on write.
|
|
2224
|
+
* @returns A new object suitable for application consumption.
|
|
2225
|
+
*/
|
|
2226
|
+
prepareForRead(stored, decryptFn) {
|
|
2227
|
+
return prepareForRead(_shape, stored, decryptFn);
|
|
2228
|
+
},
|
|
2229
|
+
/**
|
|
2230
|
+
* Produces a log-safe copy of the provided data by redacting or pseudonymizing
|
|
2231
|
+
* PII fields in accordance with field annotations.
|
|
2232
|
+
*
|
|
2233
|
+
* @param data Arbitrary data to sanitize for logging.
|
|
2234
|
+
* @param pseudonymFn Function producing stable pseudonyms for sensitive values.
|
|
2235
|
+
* @returns A copy safe to emit to logs.
|
|
2236
|
+
*/
|
|
2237
|
+
sanitizeForLog(data, pseudonymFn) {
|
|
2238
|
+
return sanitizeForLog(_shape, data, pseudonymFn);
|
|
2239
|
+
},
|
|
2240
|
+
/**
|
|
2241
|
+
* Returns a list of fields annotated with PII metadata for auditing purposes.
|
|
2242
|
+
* Each entry includes classification, required action, and optional log policy.
|
|
2243
|
+
*/
|
|
2244
|
+
getPiiAudit() {
|
|
2245
|
+
return getPiiAudit(_shape);
|
|
2246
|
+
},
|
|
2247
|
+
/**
|
|
2248
|
+
* Produces a copy of stored data suitable for data deletion flows by scrubbing
|
|
2249
|
+
* or blanking PII per field annotations.
|
|
2250
|
+
*
|
|
2251
|
+
* @param stored Data as persisted.
|
|
2252
|
+
* @returns A copy with PII removed or neutralized for deletion.
|
|
2253
|
+
*/
|
|
2254
|
+
scrubPiiForDelete(stored) {
|
|
2255
|
+
return scrubPiiForDelete(_shape, stored);
|
|
2256
|
+
},
|
|
2257
|
+
/**
|
|
2258
|
+
* Returns a normalized description of the schema suitable for documentation
|
|
2259
|
+
* or UI rendering (type, optionality, enum values, PII flags, etc.).
|
|
2260
|
+
*/
|
|
2261
|
+
describe() {
|
|
2262
|
+
const description = {};
|
|
2263
|
+
for (const [key, def] of Object.entries(schema._shape)) {
|
|
2264
|
+
description[key] = {
|
|
2265
|
+
type: def.type,
|
|
2266
|
+
optional: isOptional(def),
|
|
2267
|
+
immutable: !!def.isImmutable,
|
|
2268
|
+
system: !!def.isSystem,
|
|
2269
|
+
description: def._description ?? "",
|
|
2270
|
+
version: def._version ?? "",
|
|
2271
|
+
deprecated: def.deprecated ?? false,
|
|
2272
|
+
deprecatedVersion: def.deprecatedVersion === void 0 ? null : def.deprecatedVersion,
|
|
2273
|
+
enum: getEnumValues(def) ?? null,
|
|
2274
|
+
refType: def.refType ?? null,
|
|
2275
|
+
pii: !def._pii || def._pii.classification === "none" ? null : def._pii
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
return {
|
|
2279
|
+
entityType,
|
|
2280
|
+
version,
|
|
2281
|
+
hasSchemaUpgrade: !!schemaUpgrade,
|
|
2282
|
+
shape: description
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
globalSchemaRegistry.set(entityType, schema);
|
|
2287
|
+
return schema;
|
|
2288
|
+
}
|
|
2289
|
+
function getSchemaForType(type) {
|
|
2290
|
+
return globalSchemaRegistry.get(type);
|
|
2291
|
+
}
|
|
2292
|
+
function getAllSchemas() {
|
|
2293
|
+
return Array.from(globalSchemaRegistry.values());
|
|
2294
|
+
}
|
|
2295
|
+
function renderSchemaDescription(schema) {
|
|
2296
|
+
const meta = schema.describe();
|
|
2297
|
+
return {
|
|
2298
|
+
title: `${meta.entityType} (v${meta.version})`,
|
|
2299
|
+
fields: Object.entries(meta.shape).map(([name, def]) => ({
|
|
2300
|
+
name,
|
|
2301
|
+
type: def.type,
|
|
2302
|
+
optional: def.optional,
|
|
2303
|
+
description: def.description,
|
|
2304
|
+
deprecated: def.deprecated,
|
|
2305
|
+
pii: def.pii ? def.pii.classification : void 0
|
|
2306
|
+
}))
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
// src/components.ts
|
|
2311
|
+
var componentSchemaRegistry = /* @__PURE__ */ new Map();
|
|
2312
|
+
function createComponentSchema(shape, name, version, tableName = "", schemaValidator = () => true) {
|
|
2313
|
+
const schema = createSchema(shape, name, {
|
|
2314
|
+
version,
|
|
2315
|
+
piiEnforcement: "strict",
|
|
2316
|
+
table: tableName,
|
|
2317
|
+
schemaValidator
|
|
2318
|
+
});
|
|
2319
|
+
registerComponentSchema(name, schema);
|
|
2320
|
+
return schema;
|
|
2321
|
+
}
|
|
2322
|
+
function registerComponentSchema(type, schema) {
|
|
2323
|
+
componentSchemaRegistry.set(type, schema);
|
|
2324
|
+
}
|
|
2325
|
+
function getComponentSchema(type) {
|
|
2326
|
+
return componentSchemaRegistry.get(type);
|
|
2327
|
+
}
|
|
2328
|
+
function getAllComponentSchemas() {
|
|
2329
|
+
return Array.from(componentSchemaRegistry.entries());
|
|
2330
|
+
}
|
|
2331
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2332
|
+
0 && (module.exports = {
|
|
2333
|
+
FieldBuilder,
|
|
2334
|
+
IsoLanguageCode,
|
|
2335
|
+
createComponentSchema,
|
|
2336
|
+
createSchema,
|
|
2337
|
+
field,
|
|
2338
|
+
getAllComponentSchemas,
|
|
2339
|
+
getAllSchemas,
|
|
2340
|
+
getComponentSchema,
|
|
2341
|
+
getSchemaForType,
|
|
2342
|
+
isExtensionSingleton,
|
|
2343
|
+
isExtensionSubtag,
|
|
2344
|
+
isIsoLanguageCode,
|
|
2345
|
+
isPrivateUseSingleton,
|
|
2346
|
+
isPrivateUseSubtag,
|
|
2347
|
+
isRegionSubtag,
|
|
2348
|
+
isScriptSubtag,
|
|
2349
|
+
isVariantSubtag,
|
|
2350
|
+
isoCountryCodes,
|
|
2351
|
+
isoCurrencyCodes,
|
|
2352
|
+
registerComponentSchema,
|
|
2353
|
+
renderSchemaDescription,
|
|
2354
|
+
validateCountryCode,
|
|
2355
|
+
validateCurrencyCode,
|
|
2356
|
+
validateDateTimeISO,
|
|
2357
|
+
validateEmail,
|
|
2358
|
+
validateLanguage,
|
|
2359
|
+
validateName,
|
|
2360
|
+
validatePercentage,
|
|
2361
|
+
validatePhone,
|
|
2362
|
+
validateRichText,
|
|
2363
|
+
validateSafeText,
|
|
2364
|
+
validateSemVer,
|
|
2365
|
+
validateUUID,
|
|
2366
|
+
validateUrl,
|
|
2367
|
+
validateUserId,
|
|
2368
|
+
validateUserIdArray
|
|
2369
|
+
});
|
|
2370
|
+
//# sourceMappingURL=index.cjs.map
|