@plasius/schema 1.2.17 → 1.3.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 +34 -0
- package/dist/index.cjs +180 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +116 -5
- package/dist/index.d.ts +116 -5
- package/dist/index.js +174 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,25 @@ uses `dist-cjs/*.js` with `type: module`, it must generate
|
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
+
## Player-system event contracts
|
|
34
|
+
|
|
35
|
+
The package exports versioned contracts for a blob-backed, privacy-safe player
|
|
36
|
+
event pipeline:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import {
|
|
40
|
+
PlayerSystemCuratedSnapshotSchema,
|
|
41
|
+
PlayerSystemNormalizedEventBatchSchema,
|
|
42
|
+
PlayerSystemObservedEventBatchSchema,
|
|
43
|
+
} from "@plasius/schema";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The contracts cover raw observed-event ingress, normalized batches with
|
|
47
|
+
deterministic keys, and curated per-player Event Log/Achievement snapshots.
|
|
48
|
+
Only the stable `playerId` is treated as a personal identifier. It is retained
|
|
49
|
+
for storage partitioning and pseudonymized by `sanitizeForLog`; unknown fields
|
|
50
|
+
and hidden server truth are excluded by schema serialization.
|
|
51
|
+
|
|
33
52
|
## Demo
|
|
34
53
|
|
|
35
54
|
```bash
|
|
@@ -88,6 +107,21 @@ const UserFields = {
|
|
|
88
107
|
```
|
|
89
108
|
|
|
90
109
|
Common methods (non‑exhaustive): `.required()`, `.optional()`, `.default(v|fn)`, `.description(text)`, and type‑specific helpers like `.email()`, `.uuid()`, `.min()`, `.max()`, `.enum([...])`.
|
|
110
|
+
|
|
111
|
+
Validation standards notes:
|
|
112
|
+
|
|
113
|
+
- `languageCode()` checks a documented RFC 5646/BCP 47 subset
|
|
114
|
+
case-insensitively, including grandfathered and private-use tags;
|
|
115
|
+
two-letter primaries are checked against the package's ISO 639-1 registry.
|
|
116
|
+
Registered 4-8 letter primaries remain outside this dependency-free subset.
|
|
117
|
+
- `uuid()` accepts the RFC 9562 versions 1-8, Nil, and Max text forms. Legacy
|
|
118
|
+
RFC 4122-named module paths remain compatibility aliases.
|
|
119
|
+
- `email()` is a pragmatic dot-domain mailbox subset, not a claim of complete
|
|
120
|
+
RFC 5322 grammar support.
|
|
121
|
+
|
|
122
|
+
Runtime rollout inherits `governance.rfc-compliance-remediation.enabled`;
|
|
123
|
+
disabled consumers may retain the prior validators only during a documented
|
|
124
|
+
migration window.
|
|
91
125
|
Defaults are applied during validation when inputs are missing/`undefined`.
|
|
92
126
|
Fields are required by default; call `.optional()` (or provide `.default()`) to allow omission.
|
|
93
127
|
Use `.internal()` or `.exposure("internal")` on fields that the server may validate/store but must not include in client-facing payloads.
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,12 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
FieldBuilder: () => FieldBuilder,
|
|
24
24
|
IsoLanguageCode: () => IsoLanguageCode,
|
|
25
|
+
PLAYER_SYSTEM_ACHIEVEMENT_TIERS: () => PLAYER_SYSTEM_ACHIEVEMENT_TIERS,
|
|
26
|
+
PLAYER_SYSTEM_ACHIEVEMENT_TRACK_STATUSES: () => PLAYER_SYSTEM_ACHIEVEMENT_TRACK_STATUSES,
|
|
27
|
+
PLAYER_SYSTEM_EVENT_KINDS: () => PLAYER_SYSTEM_EVENT_KINDS,
|
|
28
|
+
PlayerSystemCuratedSnapshotSchema: () => PlayerSystemCuratedSnapshotSchema,
|
|
29
|
+
PlayerSystemNormalizedEventBatchSchema: () => PlayerSystemNormalizedEventBatchSchema,
|
|
30
|
+
PlayerSystemObservedEventBatchSchema: () => PlayerSystemObservedEventBatchSchema,
|
|
25
31
|
createComponentSchema: () => createComponentSchema,
|
|
26
32
|
createSchema: () => createSchema,
|
|
27
33
|
field: () => field,
|
|
@@ -284,8 +290,8 @@ var validateUrl = (value) => {
|
|
|
284
290
|
// src/validation/uuid.RFC4122.ts
|
|
285
291
|
var validateUUID = (value) => {
|
|
286
292
|
if (typeof value !== "string") return false;
|
|
287
|
-
const
|
|
288
|
-
return
|
|
293
|
+
const standard = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
294
|
+
return standard.test(value) || value.toLowerCase() === "00000000-0000-0000-0000-000000000000" || value.toLowerCase() === "ffffffff-ffff-ffff-ffff-ffffffffffff";
|
|
289
295
|
};
|
|
290
296
|
|
|
291
297
|
// src/validation/dateTime.ISO8601.ts
|
|
@@ -1032,10 +1038,10 @@ function isIsoLanguageCode(value) {
|
|
|
1032
1038
|
return typeof value === "string" && ISO_LANGUAGE_SET.has(value.toLowerCase());
|
|
1033
1039
|
}
|
|
1034
1040
|
function isRegionSubtag(value) {
|
|
1035
|
-
return /^[A-
|
|
1041
|
+
return /^[A-Za-z]{2}$/.test(value) || /^\d{3}$/.test(value);
|
|
1036
1042
|
}
|
|
1037
1043
|
function isScriptSubtag(value) {
|
|
1038
|
-
return /^[A-
|
|
1044
|
+
return /^[A-Za-z]{4}$/.test(value);
|
|
1039
1045
|
}
|
|
1040
1046
|
function isVariantSubtag(value) {
|
|
1041
1047
|
return /^([0-9][A-Za-z0-9]{3}|[A-Za-z0-9]{5,8})$/.test(value);
|
|
@@ -1055,20 +1061,42 @@ function isPrivateUseSubtag(value) {
|
|
|
1055
1061
|
function validateLanguage(value) {
|
|
1056
1062
|
if (typeof value !== "string" || value.length === 0) return false;
|
|
1057
1063
|
const parts = value.split("-");
|
|
1064
|
+
if (parts.some((part) => part.length === 0)) return false;
|
|
1065
|
+
const lowerTag = value.toLowerCase();
|
|
1066
|
+
if (GRANDFATHERED_TAGS.has(lowerTag)) return true;
|
|
1067
|
+
if (parts[0]?.toLowerCase() === "x") {
|
|
1068
|
+
return parts.length > 1 && parts.slice(1).every(isPrivateUseSubtag);
|
|
1069
|
+
}
|
|
1058
1070
|
let i = 0;
|
|
1059
1071
|
const lang = parts[i];
|
|
1060
|
-
if (!lang ||
|
|
1072
|
+
if (!lang || !/^[A-Za-z]{2,3}$/.test(lang)) return false;
|
|
1073
|
+
if (lang.length === 2 && !isIsoLanguageCode(lang)) return false;
|
|
1061
1074
|
i += 1;
|
|
1075
|
+
if (lang.length <= 3) {
|
|
1076
|
+
let extlangCount = 0;
|
|
1077
|
+
while (i < parts.length && extlangCount < 3 && /^[A-Za-z]{3}$/.test(parts[i])) {
|
|
1078
|
+
i += 1;
|
|
1079
|
+
extlangCount += 1;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1062
1082
|
if (i < parts.length && isScriptSubtag(parts[i])) {
|
|
1063
1083
|
i += 1;
|
|
1064
1084
|
}
|
|
1065
1085
|
if (i < parts.length && isRegionSubtag(parts[i].toUpperCase())) {
|
|
1066
1086
|
i += 1;
|
|
1067
1087
|
}
|
|
1088
|
+
const variants = /* @__PURE__ */ new Set();
|
|
1068
1089
|
while (i < parts.length && isVariantSubtag(parts[i])) {
|
|
1090
|
+
const variant = parts[i].toLowerCase();
|
|
1091
|
+
if (variants.has(variant)) return false;
|
|
1092
|
+
variants.add(variant);
|
|
1069
1093
|
i += 1;
|
|
1070
1094
|
}
|
|
1095
|
+
const extensionSingletons = /* @__PURE__ */ new Set();
|
|
1071
1096
|
while (i < parts.length && isExtensionSingleton(parts[i])) {
|
|
1097
|
+
const singleton = parts[i].toLowerCase();
|
|
1098
|
+
if (extensionSingletons.has(singleton)) return false;
|
|
1099
|
+
extensionSingletons.add(singleton);
|
|
1072
1100
|
i += 1;
|
|
1073
1101
|
if (!(i < parts.length && isExtensionSubtag(parts[i]))) return false;
|
|
1074
1102
|
while (i < parts.length && isExtensionSubtag(parts[i])) {
|
|
@@ -1084,6 +1112,34 @@ function validateLanguage(value) {
|
|
|
1084
1112
|
}
|
|
1085
1113
|
return i === parts.length;
|
|
1086
1114
|
}
|
|
1115
|
+
var GRANDFATHERED_TAGS = /* @__PURE__ */ new Set([
|
|
1116
|
+
"art-lojban",
|
|
1117
|
+
"cel-gaulish",
|
|
1118
|
+
"en-gb-oed",
|
|
1119
|
+
"i-ami",
|
|
1120
|
+
"i-bnn",
|
|
1121
|
+
"i-default",
|
|
1122
|
+
"i-enochian",
|
|
1123
|
+
"i-hak",
|
|
1124
|
+
"i-klingon",
|
|
1125
|
+
"i-lux",
|
|
1126
|
+
"i-mingo",
|
|
1127
|
+
"i-navajo",
|
|
1128
|
+
"i-pwn",
|
|
1129
|
+
"i-tao",
|
|
1130
|
+
"i-tay",
|
|
1131
|
+
"i-tsu",
|
|
1132
|
+
"no-bok",
|
|
1133
|
+
"no-nyn",
|
|
1134
|
+
"sgn-be-fr",
|
|
1135
|
+
"sgn-be-nl",
|
|
1136
|
+
"sgn-ch-de",
|
|
1137
|
+
"zh-guoyu",
|
|
1138
|
+
"zh-hakka",
|
|
1139
|
+
"zh-min",
|
|
1140
|
+
"zh-min-nan",
|
|
1141
|
+
"zh-xiang"
|
|
1142
|
+
]);
|
|
1087
1143
|
|
|
1088
1144
|
// src/field.ts
|
|
1089
1145
|
var field = {
|
|
@@ -2509,10 +2565,129 @@ function getComponentSchema(type) {
|
|
|
2509
2565
|
function getAllComponentSchemas() {
|
|
2510
2566
|
return Array.from(componentSchemaRegistry.entries());
|
|
2511
2567
|
}
|
|
2568
|
+
|
|
2569
|
+
// src/player-system-events.ts
|
|
2570
|
+
var PLAYER_SYSTEM_EVENT_KINDS = [
|
|
2571
|
+
"combat",
|
|
2572
|
+
"social",
|
|
2573
|
+
"mission",
|
|
2574
|
+
"crafting",
|
|
2575
|
+
"world"
|
|
2576
|
+
];
|
|
2577
|
+
var PLAYER_SYSTEM_ACHIEVEMENT_TIERS = [
|
|
2578
|
+
"bronze",
|
|
2579
|
+
"silver",
|
|
2580
|
+
"gold"
|
|
2581
|
+
];
|
|
2582
|
+
var PLAYER_SYSTEM_ACHIEVEMENT_TRACK_STATUSES = [
|
|
2583
|
+
"tracking",
|
|
2584
|
+
"earned"
|
|
2585
|
+
];
|
|
2586
|
+
var playerIdField = () => field.string().min(1).max(128).PID({
|
|
2587
|
+
classification: "low",
|
|
2588
|
+
action: "none",
|
|
2589
|
+
logHandling: "pseudonym",
|
|
2590
|
+
purpose: "a stable player-scoped storage identifier"
|
|
2591
|
+
}).description(
|
|
2592
|
+
"Stable player identifier; the only personal identifier retained in event projections"
|
|
2593
|
+
);
|
|
2594
|
+
var eventIdField = () => field.string().min(1).max(128);
|
|
2595
|
+
var eventKindField = () => field.string().enum(PLAYER_SYSTEM_EVENT_KINDS);
|
|
2596
|
+
var eventTitleField = () => field.generalText().min(1).max(120).description("Player-visible event title");
|
|
2597
|
+
var eventSummaryField = () => field.generalText().min(1).max(320).description("Player-visible event summary");
|
|
2598
|
+
var observedEventFields = (defaultHighlighted = true) => ({
|
|
2599
|
+
id: eventIdField(),
|
|
2600
|
+
playerId: playerIdField(),
|
|
2601
|
+
occurredAt: field.dateTimeISO(),
|
|
2602
|
+
kind: eventKindField(),
|
|
2603
|
+
title: eventTitleField(),
|
|
2604
|
+
summary: eventSummaryField(),
|
|
2605
|
+
highlighted: defaultHighlighted ? field.boolean().default(false) : field.boolean()
|
|
2606
|
+
});
|
|
2607
|
+
var eventHighlightFields = () => ({
|
|
2608
|
+
id: eventIdField(),
|
|
2609
|
+
title: eventTitleField(),
|
|
2610
|
+
summary: eventSummaryField(),
|
|
2611
|
+
occurredAt: field.dateTimeISO(),
|
|
2612
|
+
kind: eventKindField()
|
|
2613
|
+
});
|
|
2614
|
+
var eventEntryFields = () => ({
|
|
2615
|
+
id: eventIdField(),
|
|
2616
|
+
title: eventTitleField(),
|
|
2617
|
+
summary: eventSummaryField(),
|
|
2618
|
+
occurredAt: field.dateTimeISO(),
|
|
2619
|
+
kind: eventKindField(),
|
|
2620
|
+
highlighted: field.boolean()
|
|
2621
|
+
});
|
|
2622
|
+
var achievementFields = () => ({
|
|
2623
|
+
id: eventIdField(),
|
|
2624
|
+
title: eventTitleField(),
|
|
2625
|
+
tier: field.string().enum(PLAYER_SYSTEM_ACHIEVEMENT_TIERS),
|
|
2626
|
+
progressLabel: eventSummaryField(),
|
|
2627
|
+
earnedAt: field.dateTimeISO().optional()
|
|
2628
|
+
});
|
|
2629
|
+
var achievementTrackFields = () => ({
|
|
2630
|
+
id: eventIdField(),
|
|
2631
|
+
title: eventTitleField(),
|
|
2632
|
+
current: field.number().min(0).max(1e3),
|
|
2633
|
+
target: field.number().min(1).max(1e3),
|
|
2634
|
+
status: field.string().enum(PLAYER_SYSTEM_ACHIEVEMENT_TRACK_STATUSES)
|
|
2635
|
+
});
|
|
2636
|
+
var PlayerSystemObservedEventBatchSchema = createSchema(
|
|
2637
|
+
{
|
|
2638
|
+
source: field.string().min(1).max(120),
|
|
2639
|
+
emittedAt: field.dateTimeISO(),
|
|
2640
|
+
events: field.array(field.object(observedEventFields()))
|
|
2641
|
+
},
|
|
2642
|
+
"player-system-observed-event-batch",
|
|
2643
|
+
{
|
|
2644
|
+
version: "1.0.0",
|
|
2645
|
+
piiEnforcement: "strict"
|
|
2646
|
+
}
|
|
2647
|
+
);
|
|
2648
|
+
var PlayerSystemNormalizedEventBatchSchema = createSchema(
|
|
2649
|
+
{
|
|
2650
|
+
sourceBlobName: field.string().min(1).max(512),
|
|
2651
|
+
processedAt: field.dateTimeISO(),
|
|
2652
|
+
events: field.array(
|
|
2653
|
+
field.object({
|
|
2654
|
+
...observedEventFields(false),
|
|
2655
|
+
deterministicKey: field.string().min(1).max(256)
|
|
2656
|
+
})
|
|
2657
|
+
)
|
|
2658
|
+
},
|
|
2659
|
+
"player-system-normalized-event-batch",
|
|
2660
|
+
{
|
|
2661
|
+
version: "1.0.0",
|
|
2662
|
+
piiEnforcement: "strict"
|
|
2663
|
+
}
|
|
2664
|
+
);
|
|
2665
|
+
var PlayerSystemCuratedSnapshotSchema = createSchema(
|
|
2666
|
+
{
|
|
2667
|
+
playerId: playerIdField(),
|
|
2668
|
+
projectedAt: field.dateTimeISO(),
|
|
2669
|
+
lastObservedAt: field.dateTimeISO(),
|
|
2670
|
+
highlights: field.array(field.object(eventHighlightFields())),
|
|
2671
|
+
events: field.array(field.object(eventEntryFields())),
|
|
2672
|
+
achievements: field.array(field.object(achievementFields())),
|
|
2673
|
+
tracks: field.array(field.object(achievementTrackFields()))
|
|
2674
|
+
},
|
|
2675
|
+
"player-system-curated-snapshot",
|
|
2676
|
+
{
|
|
2677
|
+
version: "1.0.0",
|
|
2678
|
+
piiEnforcement: "strict"
|
|
2679
|
+
}
|
|
2680
|
+
);
|
|
2512
2681
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2513
2682
|
0 && (module.exports = {
|
|
2514
2683
|
FieldBuilder,
|
|
2515
2684
|
IsoLanguageCode,
|
|
2685
|
+
PLAYER_SYSTEM_ACHIEVEMENT_TIERS,
|
|
2686
|
+
PLAYER_SYSTEM_ACHIEVEMENT_TRACK_STATUSES,
|
|
2687
|
+
PLAYER_SYSTEM_EVENT_KINDS,
|
|
2688
|
+
PlayerSystemCuratedSnapshotSchema,
|
|
2689
|
+
PlayerSystemNormalizedEventBatchSchema,
|
|
2690
|
+
PlayerSystemObservedEventBatchSchema,
|
|
2516
2691
|
createComponentSchema,
|
|
2517
2692
|
createSchema,
|
|
2518
2693
|
field,
|