unified-entity-card 0.1.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 +22 -0
- package/package.json +35 -0
- package/src/index.d.ts +145 -0
- package/src/index.js +394 -0
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Unified Entity Card JavaScript Library
|
|
2
|
+
|
|
3
|
+
Lightweight helpers for creating and validating Unified Entity Cards.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { createCharacterUEC, validateUEC } from "./src/index.js";
|
|
9
|
+
|
|
10
|
+
const card = createCharacterUEC({
|
|
11
|
+
id: "4c5d8e2a-7a7f-4cda-9f68-6a2b6f4f4f2f",
|
|
12
|
+
name: "Aster Vale",
|
|
13
|
+
description: "A methodical archivist who values evidence over rumor."
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const result = validateUEC(card, { strict: true });
|
|
17
|
+
if (!result.ok) {
|
|
18
|
+
console.error(result.errors);
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`app_specific_settings` is treated as an opaque object. Validation focuses on schema, kind, and payload structure.
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "unified-entity-card",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unified Entity Card helpers for creating and validating UEC files.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"uec",
|
|
7
|
+
"unified-entity-card",
|
|
8
|
+
"character-card",
|
|
9
|
+
"persona",
|
|
10
|
+
"schema",
|
|
11
|
+
"llm"
|
|
12
|
+
],
|
|
13
|
+
"author": "MegalithOfficial",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/LettuceAI/Unified-Entity-Cards.git"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./src/index.js",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./src/index.d.ts",
|
|
23
|
+
"default": "./src/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"types": "./src/index.d.ts",
|
|
27
|
+
"files": [
|
|
28
|
+
"src",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "node --test"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
export const SCHEMA_NAME: "UEC";
|
|
2
|
+
export const SCHEMA_VERSION: string;
|
|
3
|
+
|
|
4
|
+
export type UecKind = "character" | "persona";
|
|
5
|
+
|
|
6
|
+
export interface UecSchema {
|
|
7
|
+
name: "UEC";
|
|
8
|
+
version: string;
|
|
9
|
+
compat?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface UecAppSpecificSettings {
|
|
13
|
+
disableAvatarGradient?: boolean;
|
|
14
|
+
customGradientEnabled?: boolean;
|
|
15
|
+
customGradientColors?: string[];
|
|
16
|
+
customTextColor?: string;
|
|
17
|
+
customTextSecondary?: string;
|
|
18
|
+
memoryType?: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UecMeta {
|
|
23
|
+
createdAt?: number;
|
|
24
|
+
updatedAt?: number;
|
|
25
|
+
source?: string;
|
|
26
|
+
authors?: string[];
|
|
27
|
+
license?: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface UecExtensions {
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CharacterScene {
|
|
36
|
+
id: string;
|
|
37
|
+
content: string;
|
|
38
|
+
direction?: string;
|
|
39
|
+
createdAt?: number;
|
|
40
|
+
variants?: unknown[];
|
|
41
|
+
selectedVariantId?: string | null;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface VoiceConfig {
|
|
46
|
+
source: string;
|
|
47
|
+
providerId: string;
|
|
48
|
+
voiceId: string;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CharacterPayload {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
avatarPath?: string | null;
|
|
57
|
+
backgroundImagePath?: string | null;
|
|
58
|
+
definitions?: string;
|
|
59
|
+
tags?: string[];
|
|
60
|
+
rules?: string[];
|
|
61
|
+
scenes?: CharacterScene[];
|
|
62
|
+
defaultSceneId?: string | null;
|
|
63
|
+
defaultModelId?: string | null;
|
|
64
|
+
promptTemplateId?: string | null;
|
|
65
|
+
voiceConfig?: VoiceConfig | null;
|
|
66
|
+
voiceAutoplay?: boolean;
|
|
67
|
+
createdAt?: number;
|
|
68
|
+
updatedAt?: number;
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface PersonaPayload {
|
|
73
|
+
id: string;
|
|
74
|
+
title: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
avatarPath?: string | null;
|
|
77
|
+
isDefault?: boolean;
|
|
78
|
+
createdAt?: number;
|
|
79
|
+
updatedAt?: number;
|
|
80
|
+
[key: string]: unknown;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface UecCharacter {
|
|
84
|
+
schema: UecSchema;
|
|
85
|
+
kind: "character";
|
|
86
|
+
payload: CharacterPayload;
|
|
87
|
+
app_specific_settings?: UecAppSpecificSettings;
|
|
88
|
+
meta?: UecMeta;
|
|
89
|
+
extensions?: UecExtensions;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface UecPersona {
|
|
93
|
+
schema: UecSchema;
|
|
94
|
+
kind: "persona";
|
|
95
|
+
payload: PersonaPayload;
|
|
96
|
+
app_specific_settings?: UecAppSpecificSettings;
|
|
97
|
+
meta?: UecMeta;
|
|
98
|
+
extensions?: UecExtensions;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type UecUnion = UecCharacter | UecPersona;
|
|
102
|
+
|
|
103
|
+
export interface ValidationResult {
|
|
104
|
+
ok: boolean;
|
|
105
|
+
errors: string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ValidateOptions {
|
|
109
|
+
strict?: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createUEC(input: {
|
|
113
|
+
kind: UecKind;
|
|
114
|
+
payload: CharacterPayload | PersonaPayload;
|
|
115
|
+
schema?: Partial<UecSchema>;
|
|
116
|
+
appSpecificSettings?: UecAppSpecificSettings;
|
|
117
|
+
meta?: UecMeta;
|
|
118
|
+
extensions?: UecExtensions;
|
|
119
|
+
}): UecUnion;
|
|
120
|
+
|
|
121
|
+
export function createCharacterUEC(
|
|
122
|
+
payload: CharacterPayload,
|
|
123
|
+
options?: {
|
|
124
|
+
schema?: Partial<UecSchema>;
|
|
125
|
+
appSpecificSettings?: UecAppSpecificSettings;
|
|
126
|
+
meta?: UecMeta;
|
|
127
|
+
extensions?: UecExtensions;
|
|
128
|
+
}
|
|
129
|
+
): UecCharacter;
|
|
130
|
+
|
|
131
|
+
export function createPersonaUEC(
|
|
132
|
+
payload: PersonaPayload,
|
|
133
|
+
options?: {
|
|
134
|
+
schema?: Partial<UecSchema>;
|
|
135
|
+
appSpecificSettings?: UecAppSpecificSettings;
|
|
136
|
+
meta?: UecMeta;
|
|
137
|
+
extensions?: UecExtensions;
|
|
138
|
+
}
|
|
139
|
+
): UecPersona;
|
|
140
|
+
|
|
141
|
+
export function validateUEC(value: unknown, options?: ValidateOptions): ValidationResult;
|
|
142
|
+
|
|
143
|
+
export function isUEC(value: unknown, options?: ValidateOptions): value is UecUnion;
|
|
144
|
+
|
|
145
|
+
export function assertUEC(value: unknown, options?: ValidateOptions): UecUnion;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
export const SCHEMA_NAME = "UEC";
|
|
2
|
+
export const SCHEMA_VERSION = "1.0";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_SCHEMA = Object.freeze({
|
|
5
|
+
name: SCHEMA_NAME,
|
|
6
|
+
version: SCHEMA_VERSION,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const isPlainObject = (value) =>
|
|
10
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
|
|
12
|
+
const isString = (value) => typeof value === "string";
|
|
13
|
+
const isNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
14
|
+
const isBoolean = (value) => typeof value === "boolean";
|
|
15
|
+
|
|
16
|
+
const optionalString = (value) =>
|
|
17
|
+
value === undefined || value === null || isString(value);
|
|
18
|
+
|
|
19
|
+
const optionalNumber = (value) => value === undefined || isNumber(value);
|
|
20
|
+
const optionalBoolean = (value) => value === undefined || isBoolean(value);
|
|
21
|
+
|
|
22
|
+
const optionalStringArray = (value) =>
|
|
23
|
+
value === undefined ||
|
|
24
|
+
(Array.isArray(value) && value.every((item) => isString(item)));
|
|
25
|
+
|
|
26
|
+
const optionalUnknownArray = (value) =>
|
|
27
|
+
value === undefined || (Array.isArray(value) && value.length >= 0);
|
|
28
|
+
|
|
29
|
+
const optionalObject = (value) => value === undefined || isPlainObject(value);
|
|
30
|
+
|
|
31
|
+
const pushError = (errors, path, message) => {
|
|
32
|
+
errors.push(`${path}: ${message}`);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const validateSchema = (schema, errors) => {
|
|
36
|
+
if (!isPlainObject(schema)) {
|
|
37
|
+
pushError(errors, "schema", "must be an object");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!isString(schema.name)) {
|
|
42
|
+
pushError(errors, "schema.name", "must be a string");
|
|
43
|
+
} else if (schema.name !== SCHEMA_NAME) {
|
|
44
|
+
pushError(errors, "schema.name", `must be \"${SCHEMA_NAME}\"`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!isString(schema.version)) {
|
|
48
|
+
pushError(errors, "schema.version", "must be a string");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (schema.compat !== undefined && !isString(schema.compat)) {
|
|
52
|
+
pushError(errors, "schema.compat", "must be a string if provided");
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const validateAppSpecificSettings = (settings, errors) => {
|
|
57
|
+
if (settings === undefined) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!isPlainObject(settings)) {
|
|
62
|
+
pushError(errors, "app_specific_settings", "must be an object");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const validateMeta = (meta, errors) => {
|
|
67
|
+
if (meta === undefined) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!isPlainObject(meta)) {
|
|
72
|
+
pushError(errors, "meta", "must be an object");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!optionalNumber(meta.createdAt)) {
|
|
77
|
+
pushError(errors, "meta.createdAt", "must be a number");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!optionalNumber(meta.updatedAt)) {
|
|
81
|
+
pushError(errors, "meta.updatedAt", "must be a number");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!optionalString(meta.source)) {
|
|
85
|
+
pushError(errors, "meta.source", "must be a string");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
meta.authors !== undefined &&
|
|
90
|
+
!(
|
|
91
|
+
Array.isArray(meta.authors) &&
|
|
92
|
+
meta.authors.every((item) => isString(item))
|
|
93
|
+
)
|
|
94
|
+
) {
|
|
95
|
+
pushError(errors, "meta.authors", "must be an array of strings");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!optionalString(meta.license)) {
|
|
99
|
+
pushError(errors, "meta.license", "must be a string");
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const validateScene = (scene, path, errors, options) => {
|
|
104
|
+
if (!isPlainObject(scene)) {
|
|
105
|
+
pushError(errors, path, "must be an object");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!isString(scene.id)) {
|
|
110
|
+
pushError(errors, `${path}.id`, "must be a string");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!isString(scene.content)) {
|
|
114
|
+
pushError(errors, `${path}.content`, "must be a string");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!optionalString(scene.direction)) {
|
|
118
|
+
pushError(errors, `${path}.direction`, "must be a string");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!optionalNumber(scene.createdAt)) {
|
|
122
|
+
pushError(errors, `${path}.createdAt`, "must be a number");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!optionalUnknownArray(scene.variants)) {
|
|
126
|
+
pushError(errors, `${path}.variants`, "must be an array");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
scene.selectedVariantId !== undefined &&
|
|
131
|
+
!optionalString(scene.selectedVariantId)
|
|
132
|
+
) {
|
|
133
|
+
pushError(errors, `${path}.selectedVariantId`, "must be a string or null");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (options.strict) {
|
|
137
|
+
if (!isString(scene.id)) {
|
|
138
|
+
pushError(errors, `${path}.id`, "is required");
|
|
139
|
+
}
|
|
140
|
+
if (!isString(scene.content)) {
|
|
141
|
+
pushError(errors, `${path}.content`, "is required");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const validateVoiceConfig = (voiceConfig, errors) => {
|
|
147
|
+
if (voiceConfig === undefined || voiceConfig === null) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!isPlainObject(voiceConfig)) {
|
|
152
|
+
pushError(errors, "payload.voiceConfig", "must be an object");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!isString(voiceConfig.source)) {
|
|
157
|
+
pushError(errors, "payload.voiceConfig.source", "must be a string");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!isString(voiceConfig.providerId)) {
|
|
161
|
+
pushError(errors, "payload.voiceConfig.providerId", "must be a string");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!isString(voiceConfig.voiceId)) {
|
|
165
|
+
pushError(errors, "payload.voiceConfig.voiceId", "must be a string");
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const validateCharacterPayload = (payload, errors, options) => {
|
|
170
|
+
if (!isPlainObject(payload)) {
|
|
171
|
+
pushError(errors, "payload", "must be an object");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!isString(payload.id)) {
|
|
176
|
+
pushError(errors, "payload.id", "must be a string");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!isString(payload.name)) {
|
|
180
|
+
pushError(errors, "payload.name", "must be a string");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!optionalString(payload.description)) {
|
|
184
|
+
pushError(errors, "payload.description", "must be a string");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!optionalString(payload.definitions)) {
|
|
188
|
+
pushError(errors, "payload.definitions", "must be a string");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!optionalStringArray(payload.tags)) {
|
|
192
|
+
pushError(errors, "payload.tags", "must be an array of strings");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!optionalString(payload.avatarPath)) {
|
|
196
|
+
pushError(errors, "payload.avatarPath", "must be a string or null");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!optionalString(payload.backgroundImagePath)) {
|
|
200
|
+
pushError(
|
|
201
|
+
errors,
|
|
202
|
+
"payload.backgroundImagePath",
|
|
203
|
+
"must be a string or null",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!optionalStringArray(payload.rules)) {
|
|
208
|
+
pushError(errors, "payload.rules", "must be an array of strings");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (payload.scenes !== undefined) {
|
|
212
|
+
if (!Array.isArray(payload.scenes)) {
|
|
213
|
+
pushError(errors, "payload.scenes", "must be an array");
|
|
214
|
+
} else {
|
|
215
|
+
payload.scenes.forEach((scene, index) =>
|
|
216
|
+
validateScene(scene, `payload.scenes[${index}]`, errors, options),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!optionalString(payload.defaultSceneId)) {
|
|
222
|
+
pushError(errors, "payload.defaultSceneId", "must be a string or null");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!optionalString(payload.defaultModelId)) {
|
|
226
|
+
pushError(errors, "payload.defaultModelId", "must be a string or null");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!optionalString(payload.promptTemplateId)) {
|
|
230
|
+
pushError(errors, "payload.promptTemplateId", "must be a string or null");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
validateVoiceConfig(payload.voiceConfig, errors);
|
|
234
|
+
|
|
235
|
+
if (!optionalBoolean(payload.voiceAutoplay)) {
|
|
236
|
+
pushError(errors, "payload.voiceAutoplay", "must be a boolean");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!optionalNumber(payload.createdAt)) {
|
|
240
|
+
pushError(errors, "payload.createdAt", "must be a number");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!optionalNumber(payload.updatedAt)) {
|
|
244
|
+
pushError(errors, "payload.updatedAt", "must be a number");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (options.strict) {
|
|
248
|
+
if (!isString(payload.description)) {
|
|
249
|
+
pushError(errors, "payload.description", "is required in strict mode");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!Array.isArray(payload.rules)) {
|
|
253
|
+
pushError(errors, "payload.rules", "is required in strict mode");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!Array.isArray(payload.scenes)) {
|
|
257
|
+
pushError(errors, "payload.scenes", "is required in strict mode");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!isNumber(payload.createdAt)) {
|
|
261
|
+
pushError(errors, "payload.createdAt", "is required in strict mode");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!isNumber(payload.updatedAt)) {
|
|
265
|
+
pushError(errors, "payload.updatedAt", "is required in strict mode");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const validatePersonaPayload = (payload, errors, options) => {
|
|
271
|
+
if (!isPlainObject(payload)) {
|
|
272
|
+
pushError(errors, "payload", "must be an object");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!isString(payload.id)) {
|
|
277
|
+
pushError(errors, "payload.id", "must be a string");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!isString(payload.title)) {
|
|
281
|
+
pushError(errors, "payload.title", "must be a string");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!optionalString(payload.description)) {
|
|
285
|
+
pushError(errors, "payload.description", "must be a string");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!optionalString(payload.avatarPath)) {
|
|
289
|
+
pushError(errors, "payload.avatarPath", "must be a string or null");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!optionalBoolean(payload.isDefault)) {
|
|
293
|
+
pushError(errors, "payload.isDefault", "must be a boolean");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!optionalNumber(payload.createdAt)) {
|
|
297
|
+
pushError(errors, "payload.createdAt", "must be a number");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!optionalNumber(payload.updatedAt)) {
|
|
301
|
+
pushError(errors, "payload.updatedAt", "must be a number");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (options.strict) {
|
|
305
|
+
if (!isString(payload.description)) {
|
|
306
|
+
pushError(errors, "payload.description", "is required in strict mode");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!isNumber(payload.createdAt)) {
|
|
310
|
+
pushError(errors, "payload.createdAt", "is required in strict mode");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!isNumber(payload.updatedAt)) {
|
|
314
|
+
pushError(errors, "payload.updatedAt", "is required in strict mode");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export const createUEC = ({
|
|
320
|
+
kind,
|
|
321
|
+
payload,
|
|
322
|
+
schema,
|
|
323
|
+
appSpecificSettings,
|
|
324
|
+
meta,
|
|
325
|
+
extensions,
|
|
326
|
+
} = {}) => {
|
|
327
|
+
if (!kind) {
|
|
328
|
+
throw new Error("kind is required");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (!isPlainObject(payload)) {
|
|
332
|
+
throw new Error("payload must be an object");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
schema: { ...DEFAULT_SCHEMA, ...(schema || {}) },
|
|
337
|
+
kind,
|
|
338
|
+
payload,
|
|
339
|
+
app_specific_settings: appSpecificSettings || {},
|
|
340
|
+
meta: meta || {},
|
|
341
|
+
extensions: extensions || {},
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export const createCharacterUEC = (payload, options = {}) =>
|
|
346
|
+
createUEC({ kind: "character", payload, ...options });
|
|
347
|
+
|
|
348
|
+
export const createPersonaUEC = (payload, options = {}) =>
|
|
349
|
+
createUEC({ kind: "persona", payload, ...options });
|
|
350
|
+
|
|
351
|
+
export const validateUEC = (value, options = {}) => {
|
|
352
|
+
const errors = [];
|
|
353
|
+
const settings = { strict: false, ...options };
|
|
354
|
+
|
|
355
|
+
if (!isPlainObject(value)) {
|
|
356
|
+
pushError(errors, "root", "must be an object");
|
|
357
|
+
return { ok: false, errors };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
validateSchema(value.schema, errors);
|
|
361
|
+
|
|
362
|
+
if (value.kind !== "character" && value.kind !== "persona") {
|
|
363
|
+
pushError(errors, "kind", 'must be "character" or "persona"');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!isPlainObject(value.payload)) {
|
|
367
|
+
pushError(errors, "payload", "must be an object");
|
|
368
|
+
} else if (value.kind === "character") {
|
|
369
|
+
validateCharacterPayload(value.payload, errors, settings);
|
|
370
|
+
} else if (value.kind === "persona") {
|
|
371
|
+
validatePersonaPayload(value.payload, errors, settings);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
validateAppSpecificSettings(value.app_specific_settings, errors);
|
|
375
|
+
validateMeta(value.meta, errors);
|
|
376
|
+
|
|
377
|
+
if (value.extensions !== undefined && !isPlainObject(value.extensions)) {
|
|
378
|
+
pushError(errors, "extensions", "must be an object");
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return { ok: errors.length === 0, errors };
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
export const isUEC = (value, options) => validateUEC(value, options).ok;
|
|
385
|
+
|
|
386
|
+
export const assertUEC = (value, options) => {
|
|
387
|
+
const result = validateUEC(value, options);
|
|
388
|
+
if (!result.ok) {
|
|
389
|
+
const error = new Error(`Invalid UEC: ${result.errors.join("; ")}`);
|
|
390
|
+
error.errors = result.errors;
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
return value;
|
|
394
|
+
};
|