@kubun/protocol 0.3.10 → 0.4.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.
@@ -1,156 +1,9 @@
1
1
  import { toB64 } from '@enkaku/codec';
2
2
  import { assertType, createValidator } from '@enkaku/schema';
3
- import { DocumentModelID, digestJSON, ScalarModelID, ShapeModelID } from '@kubun/id';
4
- import { pascalCase } from 'change-case';
5
- import { documentModel, documentModelsCluster } from './document.js';
3
+ import { DocumentModelID, digestJSON } from '@kubun/id';
4
+ import { getKubunLogger } from '@kubun/logger';
5
+ import { DocumentModelNormalizer, documentModelsCluster } from './document.js';
6
6
  import { binaryStringValue } from './value.js';
7
- const JSONObjectSchema = {
8
- type: 'object',
9
- title: 'JSONObject',
10
- additionalProperties: true
11
- };
12
- export const REFERENCE_PREFIX = '#/$defs/';
13
- function toReference(id) {
14
- return `${REFERENCE_PREFIX}${id}`;
15
- }
16
- function resolveReference(root, ref) {
17
- if (!ref.startsWith('#')) {
18
- throw new Error(`Invalid reference format: ${ref}`);
19
- }
20
- // Handle #/$defs/ references
21
- if (ref.startsWith(REFERENCE_PREFIX)) {
22
- const key = ref.slice(REFERENCE_PREFIX.length);
23
- const resolved = root.$defs?.[key];
24
- if (resolved == null) {
25
- throw new Error(`Reference not found: ${ref}`);
26
- }
27
- return resolved;
28
- }
29
- // Handle other JSON Schema references like #/properties/, #/items/, etc.
30
- const segments = ref.split('/').slice(1);
31
- // biome-ignore lint/suspicious/noExplicitAny: mixed type
32
- let current = root;
33
- for (const segment of segments){
34
- if (current == null || typeof current !== 'object') {
35
- throw new Error(`Invalid reference path: ${ref}`);
36
- }
37
- current = current[segment];
38
- if (current == null) {
39
- throw new Error(`Reference not found: ${ref}`);
40
- }
41
- }
42
- return current;
43
- }
44
- function addScalar(refs, schema) {
45
- const id = ScalarModelID.create(schema);
46
- let normalizedSchema = schema;
47
- if (schema.type === 'boolean') {
48
- const { title: _, ...rest } = schema;
49
- normalizedSchema = rest;
50
- } else if (schema.title != null) {
51
- normalizedSchema = {
52
- ...schema,
53
- title: pascalCase(schema.title)
54
- };
55
- }
56
- refs[id.toString()] = normalizedSchema;
57
- return toReference(id);
58
- }
59
- function addShape(refs, schema) {
60
- const id = ShapeModelID.create(schema);
61
- refs[id.toString()] = schema;
62
- return toReference(id);
63
- }
64
- function encodeSchema(root, refs, schema) {
65
- if (schema.$ref != null) {
66
- const resolvedSchema = resolveReference(root, schema.$ref);
67
- return encodeSchema(root, refs, resolvedSchema);
68
- }
69
- const type = schema.type;
70
- if (type == null) {
71
- throw new Error('Invalid schema: missing type');
72
- }
73
- switch(type){
74
- case 'array':
75
- {
76
- if (schema.title == null) {
77
- throw new Error('Invalid array schema: missing title');
78
- }
79
- if (schema.items == null) {
80
- throw new Error('Invalid array schema: missing items');
81
- }
82
- const { default: _, ...rest } = schema;
83
- return addShape(refs, {
84
- ...rest,
85
- title: pascalCase(schema.title),
86
- items: {
87
- $ref: encodeSchema(root, refs, schema.items)
88
- }
89
- });
90
- }
91
- case 'object':
92
- {
93
- if (schema.additionalProperties) {
94
- return addShape(refs, JSONObjectSchema);
95
- }
96
- if (schema.title == null) {
97
- throw new Error('Invalid object schema: missing title');
98
- }
99
- const entries = Object.entries(schema.properties ?? {});
100
- if (entries.length === 0) {
101
- throw new Error('Invalid object schema: must contain at least one property');
102
- }
103
- const properties = {};
104
- for (const [key, value] of entries){
105
- properties[key] = {
106
- $ref: encodeSchema(root, refs, value)
107
- };
108
- }
109
- return addShape(refs, {
110
- type: 'object',
111
- title: pascalCase(schema.title),
112
- properties,
113
- required: schema.required ?? [],
114
- additionalProperties: false
115
- });
116
- }
117
- default:
118
- return addScalar(refs, schema);
119
- }
120
- }
121
- function normalizeDocumentSchema(schema) {
122
- const entries = Object.entries(schema.properties ?? {});
123
- if (entries.length === 0) {
124
- throw new Error('Document schema must contain at least one property');
125
- }
126
- const properties = {};
127
- const refs = schema.$defs ?? {};
128
- for (const [key, value] of entries){
129
- properties[key] = {
130
- $ref: encodeSchema(schema, refs, value)
131
- };
132
- }
133
- return {
134
- type: 'object',
135
- properties,
136
- additionalProperties: false,
137
- required: schema.required ?? [],
138
- $defs: refs
139
- };
140
- }
141
- const LOCAL_ID_REGEXP = /^#([0-9]+)$/;
142
- function normalizeFieldsMeta(fields) {
143
- const meta = {};
144
- for (const [key, field] of Object.entries(fields)){
145
- const match = field.relationModel?.match(LOCAL_ID_REGEXP);
146
- meta[key] = match == null ? field : {
147
- ...field,
148
- relationModel: DocumentModelID.local(Number.parseInt(match[1], 10)).toString()
149
- };
150
- }
151
- return meta;
152
- }
153
- export const validateDocumentModel = createValidator(documentModel);
154
7
  export const clusterModel = {
155
8
  $id: 'urn:kubun:protocol:model:cluster',
156
9
  type: 'object',
@@ -202,6 +55,10 @@ export function verifyCluster(cluster) {
202
55
  }
203
56
  export class ClusterBuilder {
204
57
  #cluster = [];
58
+ #logger;
59
+ constructor(params = {}){
60
+ this.#logger = params.logger ?? getKubunLogger('protocol');
61
+ }
205
62
  get cluster() {
206
63
  return this.#cluster;
207
64
  }
@@ -248,13 +105,7 @@ export class ClusterBuilder {
248
105
  return model;
249
106
  }
250
107
  add(inputModel) {
251
- const inputSchema = inputModel.schema ?? {};
252
108
  const interfaceIDs = inputModel.interfaces ?? [];
253
- const interfaces = new Set();
254
- const schemaProperties = {};
255
- const schemaReferences = {};
256
- const schemaRequired = new Set();
257
- const fieldsMeta = {};
258
109
  const normalizedInterfaceIDs = interfaceIDs.map((id)=>{
259
110
  if (id.startsWith('#')) {
260
111
  const index = Number.parseInt(id.slice(1), 10);
@@ -265,55 +116,17 @@ export class ClusterBuilder {
265
116
  }
266
117
  return id;
267
118
  });
268
- // Add interface fields
269
- for (const interfaceModel of normalizedInterfaceIDs.map((id)=>this.get(id))){
270
- for (const interfaceID of interfaceModel.interfaces){
271
- interfaces.add(interfaceID);
272
- }
273
- for (const requiredKey of interfaceModel.schema.required){
274
- schemaRequired.add(requiredKey);
275
- }
276
- Object.assign(schemaProperties, interfaceModel.schema.properties);
277
- Object.assign(schemaReferences, interfaceModel.schema.$defs);
278
- Object.assign(fieldsMeta, interfaceModel.fieldsMeta ?? {});
279
- }
280
- // Add model extra fields
281
- for (const interfaceID of normalizedInterfaceIDs){
282
- interfaces.add(interfaceID);
283
- }
284
- for (const requiredKey of inputSchema.required ?? []){
285
- schemaRequired.add(requiredKey);
286
- }
287
- Object.assign(schemaProperties, inputSchema.properties ?? {});
288
- Object.assign(fieldsMeta, inputModel.fieldsMeta ?? {});
119
+ const model = new DocumentModelNormalizer({
120
+ inputModel,
121
+ interfaceModels: normalizedInterfaceIDs.map((id)=>this.get(id)),
122
+ logger: this.#logger,
123
+ normalizedInterfaceIDs
124
+ }).toDocumentModel();
289
125
  const index = this.#cluster.length;
290
- const model = {
291
- version: '1.0',
292
- behavior: 'default',
293
- ...inputModel,
294
- interfaces: Array.from(interfaces),
295
- schema: normalizeDocumentSchema({
296
- ...inputSchema,
297
- type: 'object',
298
- properties: schemaProperties,
299
- required: Array.from(schemaRequired),
300
- additionalProperties: false,
301
- $defs: {
302
- ...inputSchema.$defs,
303
- ...schemaReferences
304
- }
305
- }),
306
- fieldsMeta: normalizeFieldsMeta(fieldsMeta)
307
- };
308
- assertType(validateDocumentModel, model);
309
126
  this.#cluster.push(model);
310
127
  return DocumentModelID.local(index);
311
128
  }
312
129
  addAll(models) {
313
- const ids = [];
314
- for (const model of models){
315
- ids.push(this.add(model));
316
- }
317
- return ids;
130
+ return models.map((input)=>this.add(input));
318
131
  }
319
132
  }