@kashscript/lexicons 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/LICENSE +199 -0
- package/README.md +73 -0
- package/dist/engine.d.ts +48 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +110 -0
- package/dist/engine.js.map +1 -0
- package/dist/envelope.d.ts +59 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +43 -0
- package/dist/envelope.js.map +1 -0
- package/dist/extensions/social.d.ts +82 -0
- package/dist/extensions/social.d.ts.map +1 -0
- package/dist/extensions/social.js +99 -0
- package/dist/extensions/social.js.map +1 -0
- package/dist/extensions/trade.d.ts +94 -0
- package/dist/extensions/trade.d.ts.map +1 -0
- package/dist/extensions/trade.js +116 -0
- package/dist/extensions/trade.js.map +1 -0
- package/dist/extensions/zkp.d.ts +76 -0
- package/dist/extensions/zkp.d.ts.map +1 -0
- package/dist/extensions/zkp.js +68 -0
- package/dist/extensions/zkp.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/json-schema.d.ts +18 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +125 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/lexicons/index.d.ts +32 -0
- package/dist/lexicons/index.d.ts.map +1 -0
- package/dist/lexicons/index.js +59 -0
- package/dist/lexicons/index.js.map +1 -0
- package/dist/lexicons/kash-id-audit.d.ts +32 -0
- package/dist/lexicons/kash-id-audit.d.ts.map +1 -0
- package/dist/lexicons/kash-id-audit.js +21 -0
- package/dist/lexicons/kash-id-audit.js.map +1 -0
- package/dist/lexicons/kash-id-preferences.d.ts +60 -0
- package/dist/lexicons/kash-id-preferences.d.ts.map +1 -0
- package/dist/lexicons/kash-id-preferences.js +43 -0
- package/dist/lexicons/kash-id-preferences.js.map +1 -0
- package/dist/lexicons/kash-id-profile.d.ts +46 -0
- package/dist/lexicons/kash-id-profile.d.ts.map +1 -0
- package/dist/lexicons/kash-id-profile.js +31 -0
- package/dist/lexicons/kash-id-profile.js.map +1 -0
- package/dist/registry.d.ts +78 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +133 -0
- package/dist/registry.js.map +1 -0
- package/dist/schema.d.ts +101 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +224 -0
- package/dist/schema.js.map +1 -0
- package/dist/semver.d.ts +29 -0
- package/dist/semver.d.ts.map +1 -0
- package/dist/semver.js +42 -0
- package/dist/semver.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +80 -0
- package/src/engine.ts +141 -0
- package/src/envelope.ts +128 -0
- package/src/extensions/social.ts +179 -0
- package/src/extensions/trade.ts +210 -0
- package/src/extensions/zkp.ts +108 -0
- package/src/index.ts +30 -0
- package/src/json-schema.ts +141 -0
- package/src/lexicons/index.ts +68 -0
- package/src/lexicons/kash-id-audit.ts +61 -0
- package/src/lexicons/kash-id-preferences.ts +114 -0
- package/src/lexicons/kash-id-profile.ts +82 -0
- package/src/registry.ts +168 -0
- package/src/schema.ts +347 -0
- package/src/semver.ts +56 -0
- package/src/types.ts +27 -0
package/src/schema.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency schema DSL + validation engine.
|
|
3
|
+
*
|
|
4
|
+
* Schemas are plain data — no class methods, no closures — so they are
|
|
5
|
+
* trivially serialisable and comparable. The `S` namespace provides a
|
|
6
|
+
* Zod-style fluent builder for ergonomic authoring.
|
|
7
|
+
*
|
|
8
|
+
* Validation is depth-first and accumulates ALL errors (not fail-fast)
|
|
9
|
+
* so callers can render a complete diagnostic on first attempt.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/* ── schema variants ────────────────────────────────────────────── */
|
|
13
|
+
|
|
14
|
+
export type Schema =
|
|
15
|
+
| AnySchema
|
|
16
|
+
| StringSchema
|
|
17
|
+
| NumberSchema
|
|
18
|
+
| BooleanSchema
|
|
19
|
+
| LiteralSchema
|
|
20
|
+
| EnumSchema
|
|
21
|
+
| ArraySchema
|
|
22
|
+
| ObjectSchema
|
|
23
|
+
| UnionSchema
|
|
24
|
+
| OptionalSchema
|
|
25
|
+
| RecordSchema;
|
|
26
|
+
|
|
27
|
+
export interface AnySchema {
|
|
28
|
+
readonly kind: "any";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SchemaStringFormat =
|
|
32
|
+
| "did-ks"
|
|
33
|
+
| "cid"
|
|
34
|
+
| "iso8601"
|
|
35
|
+
| "url"
|
|
36
|
+
| "multibase"
|
|
37
|
+
| "bcp47"
|
|
38
|
+
| "iso4217"
|
|
39
|
+
| "lexicon-id"
|
|
40
|
+
| "nsid";
|
|
41
|
+
|
|
42
|
+
export interface StringSchema {
|
|
43
|
+
readonly kind: "string";
|
|
44
|
+
readonly minLength?: number;
|
|
45
|
+
readonly maxLength?: number;
|
|
46
|
+
readonly pattern?: RegExp;
|
|
47
|
+
readonly format?: SchemaStringFormat;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface NumberSchema {
|
|
51
|
+
readonly kind: "number";
|
|
52
|
+
readonly min?: number;
|
|
53
|
+
readonly max?: number;
|
|
54
|
+
readonly integer?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BooleanSchema {
|
|
58
|
+
readonly kind: "boolean";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LiteralSchema {
|
|
62
|
+
readonly kind: "literal";
|
|
63
|
+
readonly value: string | number | boolean | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface EnumSchema {
|
|
67
|
+
readonly kind: "enum";
|
|
68
|
+
readonly values: readonly (string | number)[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ArraySchema {
|
|
72
|
+
readonly kind: "array";
|
|
73
|
+
readonly items: Schema;
|
|
74
|
+
readonly minItems?: number;
|
|
75
|
+
readonly maxItems?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ObjectSchema {
|
|
79
|
+
readonly kind: "object";
|
|
80
|
+
readonly properties: Readonly<Record<string, Schema>>;
|
|
81
|
+
readonly required: readonly string[];
|
|
82
|
+
/** When false, unknown keys cause validation failure. Default: false. */
|
|
83
|
+
readonly additionalProperties: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface UnionSchema {
|
|
87
|
+
readonly kind: "union";
|
|
88
|
+
readonly variants: readonly Schema[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface OptionalSchema {
|
|
92
|
+
readonly kind: "optional";
|
|
93
|
+
readonly inner: Schema;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface RecordSchema {
|
|
97
|
+
readonly kind: "record";
|
|
98
|
+
readonly keys: StringSchema;
|
|
99
|
+
readonly values: Schema;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ── fluent builders ────────────────────────────────────────────── */
|
|
103
|
+
|
|
104
|
+
export const S = {
|
|
105
|
+
any(): AnySchema {
|
|
106
|
+
return { kind: "any" };
|
|
107
|
+
},
|
|
108
|
+
string(opts?: Omit<StringSchema, "kind">): StringSchema {
|
|
109
|
+
return { kind: "string", ...opts };
|
|
110
|
+
},
|
|
111
|
+
number(opts?: Omit<NumberSchema, "kind">): NumberSchema {
|
|
112
|
+
return { kind: "number", ...opts };
|
|
113
|
+
},
|
|
114
|
+
boolean(): BooleanSchema {
|
|
115
|
+
return { kind: "boolean" };
|
|
116
|
+
},
|
|
117
|
+
literal(value: string | number | boolean | null): LiteralSchema {
|
|
118
|
+
return { kind: "literal", value };
|
|
119
|
+
},
|
|
120
|
+
enum(...values: readonly (string | number)[]): EnumSchema {
|
|
121
|
+
return { kind: "enum", values };
|
|
122
|
+
},
|
|
123
|
+
array(items: Schema, opts?: { readonly minItems?: number; readonly maxItems?: number }): ArraySchema {
|
|
124
|
+
return { kind: "array", items, ...opts };
|
|
125
|
+
},
|
|
126
|
+
object(
|
|
127
|
+
properties: Readonly<Record<string, Schema>>,
|
|
128
|
+
required: readonly string[] = [],
|
|
129
|
+
additionalProperties: boolean = false,
|
|
130
|
+
): ObjectSchema {
|
|
131
|
+
return { kind: "object", properties, required, additionalProperties };
|
|
132
|
+
},
|
|
133
|
+
union(...variants: readonly Schema[]): UnionSchema {
|
|
134
|
+
return { kind: "union", variants };
|
|
135
|
+
},
|
|
136
|
+
optional(inner: Schema): OptionalSchema {
|
|
137
|
+
return { kind: "optional", inner };
|
|
138
|
+
},
|
|
139
|
+
record(values: Schema, keys: StringSchema = { kind: "string" }): RecordSchema {
|
|
140
|
+
return { kind: "record", keys, values };
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/* ── validation results ─────────────────────────────────────────── */
|
|
145
|
+
|
|
146
|
+
export interface ValidationError {
|
|
147
|
+
readonly path: readonly (string | number)[];
|
|
148
|
+
readonly message: string;
|
|
149
|
+
readonly received?: unknown;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type ValidationResult<T = unknown> =
|
|
153
|
+
| { readonly ok: true; readonly value: T }
|
|
154
|
+
| { readonly ok: false; readonly errors: readonly ValidationError[] };
|
|
155
|
+
|
|
156
|
+
/* ── string format patterns ─────────────────────────────────────── */
|
|
157
|
+
|
|
158
|
+
/** Canonical source-of-truth for the built-in string formats.
|
|
159
|
+
* Exported so the JSON-Schema exporter can read `.source` without
|
|
160
|
+
* re-typing the regex literals. */
|
|
161
|
+
export const FORMAT_PATTERNS: Record<SchemaStringFormat, RegExp> = {
|
|
162
|
+
// identity-core's DID method is `did:kash:` with a base58btc-encoded pubkey
|
|
163
|
+
// body. Ed25519 pubkeys (32 bytes) round-trip to 43-44 chars in base58btc.
|
|
164
|
+
// The format-id stays `did-ks` (schema-internal name) for backward
|
|
165
|
+
// compatibility; the regex now matches what `@kashscript/identity-core`
|
|
166
|
+
// emits via `deriveDid()`.
|
|
167
|
+
"did-ks": /^did:kash:[1-9A-HJ-NP-Za-km-z]{32,48}$/,
|
|
168
|
+
"cid": /^bafy[a-z2-7]{52,}$/,
|
|
169
|
+
"iso8601": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/,
|
|
170
|
+
"url": /^https?:\/\/[^\s]+$/,
|
|
171
|
+
"multibase": /^z[1-9A-HJ-NP-Za-km-z]+$/,
|
|
172
|
+
"bcp47": /^[a-z]{2,3}(-[A-Z]{2})?$/,
|
|
173
|
+
"iso4217": /^[A-Z]{3}$/,
|
|
174
|
+
"lexicon-id": /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+@\d+\.\d+\.\d+$/,
|
|
175
|
+
"nsid": /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/* ── public entry point ─────────────────────────────────────────── */
|
|
179
|
+
|
|
180
|
+
export function validate<T = unknown>(schema: Schema, value: unknown): ValidationResult<T> {
|
|
181
|
+
const errors: ValidationError[] = [];
|
|
182
|
+
validateRec(schema, value, [], errors);
|
|
183
|
+
return errors.length === 0
|
|
184
|
+
? { ok: true, value: value as T }
|
|
185
|
+
: { ok: false, errors };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Convenience type-guard. Returns true iff `value` matches `schema`. */
|
|
189
|
+
export function matches(schema: Schema, value: unknown): boolean {
|
|
190
|
+
const errors: ValidationError[] = [];
|
|
191
|
+
validateRec(schema, value, [], errors);
|
|
192
|
+
return errors.length === 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* ── recursive validator ────────────────────────────────────────── */
|
|
196
|
+
|
|
197
|
+
function validateRec(
|
|
198
|
+
schema: Schema,
|
|
199
|
+
value: unknown,
|
|
200
|
+
path: readonly (string | number)[],
|
|
201
|
+
errors: ValidationError[],
|
|
202
|
+
): void {
|
|
203
|
+
switch (schema.kind) {
|
|
204
|
+
case "any":
|
|
205
|
+
return;
|
|
206
|
+
case "string":
|
|
207
|
+
validateString(schema, value, path, errors);
|
|
208
|
+
return;
|
|
209
|
+
case "number":
|
|
210
|
+
validateNumber(schema, value, path, errors);
|
|
211
|
+
return;
|
|
212
|
+
case "boolean":
|
|
213
|
+
if (typeof value !== "boolean") errors.push(err(path, "expected boolean", value));
|
|
214
|
+
return;
|
|
215
|
+
case "literal":
|
|
216
|
+
if (value !== schema.value) {
|
|
217
|
+
errors.push(err(path, `expected literal ${JSON.stringify(schema.value)}`, value));
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
case "enum":
|
|
221
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
222
|
+
errors.push(err(path, "expected string|number for enum", value));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (!schema.values.includes(value as string | number)) {
|
|
226
|
+
errors.push(err(path, `expected one of ${JSON.stringify(schema.values)}`, value));
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
case "array":
|
|
230
|
+
validateArray(schema, value, path, errors);
|
|
231
|
+
return;
|
|
232
|
+
case "object":
|
|
233
|
+
validateObject(schema, value, path, errors);
|
|
234
|
+
return;
|
|
235
|
+
case "union":
|
|
236
|
+
validateUnion(schema, value, path, errors);
|
|
237
|
+
return;
|
|
238
|
+
case "optional":
|
|
239
|
+
if (value === undefined) return;
|
|
240
|
+
validateRec(schema.inner, value, path, errors);
|
|
241
|
+
return;
|
|
242
|
+
case "record":
|
|
243
|
+
validateRecord(schema, value, path, errors);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function err(path: readonly (string | number)[], message: string, received: unknown): ValidationError {
|
|
249
|
+
return { path, message, received };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function validateString(s: StringSchema, value: unknown, path: readonly (string | number)[], errors: ValidationError[]): void {
|
|
253
|
+
if (typeof value !== "string") {
|
|
254
|
+
errors.push(err(path, "expected string", value));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (s.minLength !== undefined && value.length < s.minLength) {
|
|
258
|
+
errors.push(err(path, `string too short (min ${s.minLength}, got ${value.length})`, value));
|
|
259
|
+
}
|
|
260
|
+
if (s.maxLength !== undefined && value.length > s.maxLength) {
|
|
261
|
+
errors.push(err(path, `string too long (max ${s.maxLength}, got ${value.length})`, value));
|
|
262
|
+
}
|
|
263
|
+
if (s.pattern && !s.pattern.test(value)) {
|
|
264
|
+
errors.push(err(path, `does not match pattern ${s.pattern.toString()}`, value));
|
|
265
|
+
}
|
|
266
|
+
if (s.format && !FORMAT_PATTERNS[s.format].test(value)) {
|
|
267
|
+
errors.push(err(path, `does not match format "${s.format}"`, value));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function validateNumber(s: NumberSchema, value: unknown, path: readonly (string | number)[], errors: ValidationError[]): void {
|
|
272
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
273
|
+
errors.push(err(path, "expected number", value));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (s.integer && !Number.isInteger(value)) {
|
|
277
|
+
errors.push(err(path, "expected integer", value));
|
|
278
|
+
}
|
|
279
|
+
if (s.min !== undefined && value < s.min) {
|
|
280
|
+
errors.push(err(path, `below minimum ${s.min}`, value));
|
|
281
|
+
}
|
|
282
|
+
if (s.max !== undefined && value > s.max) {
|
|
283
|
+
errors.push(err(path, `above maximum ${s.max}`, value));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function validateArray(s: ArraySchema, value: unknown, path: readonly (string | number)[], errors: ValidationError[]): void {
|
|
288
|
+
if (!Array.isArray(value)) {
|
|
289
|
+
errors.push(err(path, "expected array", value));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (s.minItems !== undefined && value.length < s.minItems) {
|
|
293
|
+
errors.push(err(path, `array too short (min ${s.minItems}, got ${value.length})`, value));
|
|
294
|
+
}
|
|
295
|
+
if (s.maxItems !== undefined && value.length > s.maxItems) {
|
|
296
|
+
errors.push(err(path, `array too long (max ${s.maxItems}, got ${value.length})`, value));
|
|
297
|
+
}
|
|
298
|
+
for (let i = 0; i < value.length; i++) {
|
|
299
|
+
validateRec(s.items, value[i], [...path, i], errors);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function validateObject(s: ObjectSchema, value: unknown, path: readonly (string | number)[], errors: ValidationError[]): void {
|
|
304
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
305
|
+
errors.push(err(path, "expected object", value));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const obj = value as Record<string, unknown>;
|
|
309
|
+
for (const k of s.required) {
|
|
310
|
+
if (!(k in obj)) {
|
|
311
|
+
errors.push(err([...path, k], "missing required field", undefined));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
for (const k of Object.keys(s.properties)) {
|
|
315
|
+
if (k in obj) {
|
|
316
|
+
const subSchema = s.properties[k]!;
|
|
317
|
+
validateRec(subSchema, obj[k], [...path, k], errors);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (!s.additionalProperties) {
|
|
321
|
+
for (const k of Object.keys(obj)) {
|
|
322
|
+
if (!(k in s.properties)) {
|
|
323
|
+
errors.push(err([...path, k], "unknown field", obj[k]));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function validateUnion(s: UnionSchema, value: unknown, path: readonly (string | number)[], errors: ValidationError[]): void {
|
|
330
|
+
for (const variant of s.variants) {
|
|
331
|
+
const subErrors: ValidationError[] = [];
|
|
332
|
+
validateRec(variant, value, path, subErrors);
|
|
333
|
+
if (subErrors.length === 0) return;
|
|
334
|
+
}
|
|
335
|
+
errors.push(err(path, `value matched none of ${s.variants.length} union variants`, value));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function validateRecord(s: RecordSchema, value: unknown, path: readonly (string | number)[], errors: ValidationError[]): void {
|
|
339
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
340
|
+
errors.push(err(path, "expected object (record)", value));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
344
|
+
validateRec(s.keys, k, [...path, k], errors);
|
|
345
|
+
validateRec(s.values, v, [...path, k], errors);
|
|
346
|
+
}
|
|
347
|
+
}
|
package/src/semver.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semver parsing and forward-compat resolution for Lexicon IDs.
|
|
3
|
+
* Lexicon ID syntax: <nsid>@<major>.<minor>.<patch> — e.g., "kash.id.profile@1.2.0".
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { LexiconId } from "./types";
|
|
7
|
+
|
|
8
|
+
export interface Semver {
|
|
9
|
+
readonly major: number;
|
|
10
|
+
readonly minor: number;
|
|
11
|
+
readonly patch: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ParsedLexiconId {
|
|
15
|
+
readonly nsid: string;
|
|
16
|
+
readonly semver: Semver;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const LEXICON_ID_RE = /^([a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+)@(\d+)\.(\d+)\.(\d+)$/;
|
|
20
|
+
|
|
21
|
+
/** Parse a Lexicon ID. Returns null on malformed input. */
|
|
22
|
+
export function parseLexiconId(id: string): ParsedLexiconId | null {
|
|
23
|
+
const m = LEXICON_ID_RE.exec(id);
|
|
24
|
+
if (!m) return null;
|
|
25
|
+
return {
|
|
26
|
+
nsid: m[1]!,
|
|
27
|
+
semver: {
|
|
28
|
+
major: Number.parseInt(m[2]!, 10),
|
|
29
|
+
minor: Number.parseInt(m[3]!, 10),
|
|
30
|
+
patch: Number.parseInt(m[4]!, 10),
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Build a Lexicon ID string from parts. */
|
|
36
|
+
export function formatLexiconId(nsid: string, semver: Semver): LexiconId {
|
|
37
|
+
return `${nsid}@${semver.major}.${semver.minor}.${semver.patch}` as LexiconId;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Lexicographic compare. Returns negative/zero/positive (like Array.sort). */
|
|
41
|
+
export function compareSemver(a: Semver, b: Semver): number {
|
|
42
|
+
if (a.major !== b.major) return a.major - b.major;
|
|
43
|
+
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
44
|
+
return a.patch - b.patch;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Is the `record` semver forward-compatible with the `consumer` semver?
|
|
49
|
+
*
|
|
50
|
+
* v1.0 evolution rule: any record minted under <nsid>@X.Y.Z is acceptable to
|
|
51
|
+
* a consumer that knows <nsid>@X.*. Cross-major requires an explicit migrator
|
|
52
|
+
* (out of scope for this layer — handled by the consumer at a higher level).
|
|
53
|
+
*/
|
|
54
|
+
export function isForwardCompatible(consumer: Semver, record: Semver): boolean {
|
|
55
|
+
return consumer.major === record.major;
|
|
56
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared brand types used across the lexicon framework.
|
|
3
|
+
* Phase-3 may re-export these from @kashscript/identity once that package
|
|
4
|
+
* publishes its own brand types.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** W3C DID restricted to the did:kash method emitted by `@kashscript/identity-core`. */
|
|
8
|
+
export type DID = `did:kash:${string}`;
|
|
9
|
+
|
|
10
|
+
/** CIDv1 multibase, base32, "bafy" prefix. */
|
|
11
|
+
export type CID = `bafy${string}`;
|
|
12
|
+
|
|
13
|
+
/** Lexicon ID — <nsid>@<semver>, e.g., "kash.id.profile@1.0.0". */
|
|
14
|
+
export type LexiconId = `${string}@${number}.${number}.${number}`;
|
|
15
|
+
|
|
16
|
+
/** Multibase-encoded byte string ("z"-prefixed base58-btc). */
|
|
17
|
+
export type Multibase = `z${string}`;
|
|
18
|
+
|
|
19
|
+
/** ISO-8601 timestamp with offset, e.g. "2026-05-09T12:34:56Z". */
|
|
20
|
+
export type Iso8601 = string;
|
|
21
|
+
|
|
22
|
+
/** Signature algorithm identifier. Phase-4 adds "es256"; Phase-5 adds "bls12-381-g2". */
|
|
23
|
+
export type SignatureAlgorithm = "ed25519" | "es256" | "bls12-381-g2";
|
|
24
|
+
|
|
25
|
+
/** Domain-Separation Tag for record-tier signatures (Identity Protocol §5.5). */
|
|
26
|
+
export const KASH_RECORD_DST = "kash-record-v1\n" as const;
|
|
27
|
+
export type KashRecordDST = typeof KASH_RECORD_DST;
|