ajsc 2.0.0 → 3.0.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.
Files changed (36) hide show
  1. package/dist/JSONSchemaConverter.d.ts +4 -12
  2. package/dist/JSONSchemaConverter.js +135 -100
  3. package/dist/JSONSchemaConverter.js.map +1 -1
  4. package/dist/JSONSchemaConverter.test.js +195 -0
  5. package/dist/JSONSchemaConverter.test.js.map +1 -1
  6. package/dist/TypescriptBaseConverter.d.ts +29 -38
  7. package/dist/TypescriptBaseConverter.js +156 -85
  8. package/dist/TypescriptBaseConverter.js.map +1 -1
  9. package/dist/TypescriptConverter.d.ts +3 -6
  10. package/dist/TypescriptConverter.js +7 -107
  11. package/dist/TypescriptConverter.js.map +1 -1
  12. package/dist/TypescriptConverter.test.js +24 -0
  13. package/dist/TypescriptConverter.test.js.map +1 -1
  14. package/dist/TypescriptProcedureConverter.d.ts +6 -7
  15. package/dist/TypescriptProcedureConverter.js +19 -94
  16. package/dist/TypescriptProcedureConverter.js.map +1 -1
  17. package/dist/{TypescriptProceduresConverter.test.js → TypescriptProcedureConverter.test.js} +1 -2
  18. package/dist/TypescriptProcedureConverter.test.js.map +1 -0
  19. package/dist/types.d.ts +46 -6
  20. package/dist/utils/path-utils.test.js.map +1 -1
  21. package/package.json +6 -6
  22. package/dist/TypescriptProceduresConverter.test.js.map +0 -1
  23. package/src/JSONSchemaConverter.test.ts +0 -342
  24. package/src/JSONSchemaConverter.ts +0 -546
  25. package/src/Typebox.test.ts +0 -127
  26. package/src/TypescriptBaseConverter.ts +0 -190
  27. package/src/TypescriptConverter.test.ts +0 -399
  28. package/src/TypescriptConverter.ts +0 -189
  29. package/src/TypescriptProcedureConverter.ts +0 -180
  30. package/src/TypescriptProceduresConverter.test.ts +0 -1013
  31. package/src/index.ts +0 -4
  32. package/src/types.ts +0 -108
  33. package/src/utils/path-utils.test.ts +0 -102
  34. package/src/utils/path-utils.ts +0 -89
  35. package/src/utils/to-pascal-case.ts +0 -10
  36. /package/dist/{TypescriptProceduresConverter.test.d.ts → TypescriptProcedureConverter.test.d.ts} +0 -0
@@ -1,546 +0,0 @@
1
- import { JSONSchema7, JSONSchema7Definition } from "json-schema";
2
- import { ConverterOptions, IRNode, SignatureOccurrences } from "./types.js";
3
-
4
- /**
5
- * JSONSchemaConverter converts a JSON Schema (Draft‑07) into an
6
- * intermediate representation (IR) that can later be transformed
7
- * into target language code via a language plugin.
8
- *
9
- * This implementation now supports internal definitions via both
10
- * "$defs" and "definitions", and resolves local "$ref" pointers.
11
- */
12
- export class JSONSchemaConverter {
13
- private ir: IRNode;
14
- // The root schema is stored to support reference resolution.
15
- private rootSchema: JSONSchema7Definition | null = null;
16
- // Keep track of the occurrences of each schema signature and the schema/property names
17
- // used to reference the signature.
18
- private signatureOccurrences: SignatureOccurrences = new Map();
19
-
20
- get irNode(): IRNode {
21
- return this.ir;
22
- }
23
-
24
- get signatureOccurrencesMap(): SignatureOccurrences {
25
- return this.signatureOccurrences;
26
- }
27
-
28
- constructor(
29
- schema: JSONSchema7Definition,
30
- private opts?: ConverterOptions,
31
- ) {
32
- if (typeof schema === "object") {
33
- schema.title = schema.title || "Root";
34
- }
35
-
36
- // Optionally validate the schema.
37
- if (this.opts?.validateSchema) {
38
- this.validateSchema(schema);
39
- }
40
-
41
- // Resolve references (for now, this is a placeholder).
42
- const resolvedSchema = this.resolveReferences(schema);
43
-
44
- // Store the root schema (if it is an object) to support local $ref resolution.
45
- if (typeof resolvedSchema === "object") {
46
- this.rootSchema = resolvedSchema;
47
- }
48
-
49
- // Convert the resolved schema to our intermediate representation.
50
- let ir = this.convertToIR(resolvedSchema);
51
-
52
- // add calculated signature occurrences by traversing the IR for all object types
53
-
54
- // Apply any custom IR transformation.
55
- if (this.opts?.transform) {
56
- ir = this.opts.transform(ir);
57
- }
58
-
59
- this.ir = ir;
60
- }
61
-
62
- private calcObjSignatureOccurrences(
63
- schema: JSONSchema7Definition & {
64
- type: "object";
65
- properties: Record<string, JSONSchema7Definition>;
66
- },
67
- node: IRNode & { name: string },
68
- ): string {
69
- // If no path then it's the root object
70
- if (!node.path) {
71
- return "";
72
- }
73
-
74
- const signature = this.getSchemaSignature(schema);
75
-
76
- if (Object.keys(node.properties ?? {}).length === 0) {
77
- // empty object - can be extended with options ie: emptyObjectAsUnknown
78
- return signature;
79
- }
80
-
81
- if (this.signatureOccurrences.has(signature)) {
82
- const occurrences = this.signatureOccurrences.get(signature)!;
83
- occurrences.total += 1;
84
-
85
- const foundPath = occurrences.occurrences.find(
86
- (x) => x.nodePath === node.path,
87
- );
88
- if (foundPath) {
89
- foundPath.count += 1;
90
- } else {
91
- occurrences.occurrences.push({ node, nodePath: node.path, count: 1 });
92
- }
93
-
94
- this.signatureOccurrences.set(signature, occurrences);
95
- } else {
96
- this.signatureOccurrences.set(signature, {
97
- total: 1,
98
- occurrences: [{ node, nodePath: node.path, count: 1 }],
99
- signature,
100
- });
101
- }
102
-
103
- return signature;
104
- }
105
-
106
- /**
107
- * Recursively converts a JSON Schema definition into an IRNode.
108
- *
109
- * This method now handles $ref resolution (using the root schema),
110
- * combinators (oneOf, anyOf, allOf), as well as enums, const, objects,
111
- * arrays, and primitives.
112
- */
113
- private convertSchema(
114
- schema: JSONSchema7Definition,
115
- ctx: {
116
- path: string;
117
- },
118
- ): IRNode {
119
- if (!schema) {
120
- // Return an unknown
121
- return {
122
- type: "null",
123
- path: ctx.path,
124
- };
125
- }
126
- // Handle boolean schemas.
127
- if (typeof schema === "boolean") {
128
- if (schema === true) {
129
- // A schema of 'true' accepts any value.
130
- return {
131
- type: "object",
132
- properties: {},
133
- path: ctx.path,
134
- };
135
- } else {
136
- throw new Error(
137
- "Encountered JSON Schema 'false', which is not supported.",
138
- );
139
- }
140
- }
141
-
142
- // If the schema contains a $ref, resolve it.
143
- if (schema.$ref) {
144
- const resolved = this.resolveRef(schema);
145
- return this.convertSchema(resolved, ctx);
146
- }
147
-
148
- // Handle combinators.
149
- if (schema.oneOf) {
150
- const options = schema.oneOf.map((subSchema) =>
151
- this.convertSchema(subSchema, ctx),
152
- );
153
- return {
154
- type: "union",
155
- options,
156
- constraints: { combinator: "oneOf" },
157
- path: ctx.path,
158
- };
159
- }
160
- if (schema.anyOf) {
161
- const options = schema.anyOf.map((subSchema) =>
162
- this.convertSchema(subSchema, ctx),
163
- );
164
- return {
165
- type: "union",
166
- options,
167
- constraints: { combinator: "anyOf" },
168
- path: ctx.path,
169
- };
170
- }
171
- if (schema.allOf) {
172
- const options = schema.allOf.map((subSchema) =>
173
- this.convertSchema(subSchema, ctx),
174
- );
175
- return {
176
- type: "intersection",
177
- options,
178
- path: ctx.path,
179
- };
180
- }
181
-
182
- // Handle "enum".
183
- if (schema.enum) {
184
- return {
185
- type: "enum",
186
- values: schema.enum,
187
- path: ctx.path,
188
- };
189
- }
190
-
191
- // Handle "const".
192
- if (schema.const !== undefined) {
193
- return {
194
- type: "literal",
195
- constraints: { value: schema.const },
196
- path: ctx.path,
197
- };
198
- }
199
-
200
- // Process based on the "type" keyword.
201
- if (schema.type) {
202
- // If multiple types are provided, treat as a union.
203
- if (Array.isArray(schema.type)) {
204
- const options = schema.type.map((t) =>
205
- this.convertSchema({ ...schema, type: t }, ctx),
206
- );
207
- return {
208
- type: "union",
209
- options,
210
- path: ctx.path,
211
- };
212
- } else {
213
- switch (schema.type) {
214
- case "object": {
215
- const name = this.getTypeName(schema, ctx);
216
-
217
- const node: IRNode & {
218
- name: string;
219
- properties: {};
220
- type: "object";
221
- } = {
222
- type: "object",
223
- properties: {},
224
- path: ctx.path,
225
- name,
226
- };
227
-
228
- if (schema.properties) {
229
- for (const key in schema.properties) {
230
- const propSchema = schema.properties[key];
231
-
232
- let child = this.convertSchema(propSchema, {
233
- path: ctx.path ? `${ctx.path}.${key}` : key,
234
- });
235
-
236
- // Mark the property as required if listed in the "required" array.
237
- child.required = schema.required
238
- ? schema.required.includes(key)
239
- : false;
240
- node.properties![key] = child;
241
- }
242
- }
243
-
244
- if (schema.additionalProperties) {
245
- /**
246
- * Json-schema additionalProperties have unknown keys.
247
- * @example json-schema
248
- * {
249
- * "additionalProperties": {
250
- * "type": "object",
251
- * "properties": {
252
- * "profile": {
253
- * "type": "string"
254
- * }
255
- * },
256
- * "required": [
257
- * "profile"
258
- * ]
259
- * },
260
- * "type": "object",
261
- * "properties": {
262
- * "id": {
263
- * "type": "string"
264
- * }
265
- * },
266
- * "required": [
267
- * "id"
268
- * ]
269
- * }
270
- */
271
- node.additionalProperties = this.convertSchema(
272
- schema.additionalProperties,
273
- {
274
- // The "*" indicates an any/unknown key
275
- path: ctx.path ? `${ctx.path}.*` : "*",
276
- },
277
- );
278
- }
279
-
280
- if (schema.patternProperties) {
281
- /**
282
- * Json-schema patternProperties are not directly supported in most languages.
283
- * We mark each property key as
284
- * @example json-schema
285
- * {
286
- * "type": "object",
287
- * "patternProperties": {
288
- * "^(.*)$": {
289
- * "type": "object",
290
- * "properties": {
291
- * "name": {
292
- * "type": "string"
293
- * },
294
- * "email": {
295
- * "type": "string"
296
- * }
297
- * },
298
- * "required": [
299
- * "name",
300
- * "email"
301
- * ]
302
- * }
303
- * }
304
- * }
305
- */
306
- const propertySchemas = Object.values(schema.patternProperties);
307
-
308
- if (propertySchemas.length > 1) {
309
- // if multiple patternProperties, treat as a union of the property types.
310
- node.additionalProperties = this.convertSchema(
311
- {
312
- anyOf: propertySchemas,
313
- },
314
- {
315
- // The "*" indicates an any/unknown key
316
- path: ctx.path ? `${ctx.path}.*` : "*",
317
- },
318
- );
319
- } else {
320
- // else just convert the single property schema
321
- node.additionalProperties = this.convertSchema(
322
- propertySchemas[0],
323
- {
324
- // The "*" indicates an any/unknown key
325
- path: ctx.path ? `${ctx.path}.*` : "*",
326
- },
327
- );
328
- }
329
- }
330
-
331
- node.signature = this.calcObjSignatureOccurrences(
332
- schema as JSONSchema7 & {
333
- type: "object";
334
- properties: Record<string, JSONSchema7Definition>;
335
- },
336
- node,
337
- );
338
-
339
- return node;
340
- }
341
- case "array": {
342
- const name = this.getTypeName(schema, ctx);
343
-
344
- const node: IRNode & { name: string } = {
345
- type: "array",
346
- path: ctx.path,
347
- name,
348
- };
349
-
350
- if (schema.items) {
351
- if (Array.isArray(schema.items)) {
352
- // For tuple validation, treat as a union of the item types.
353
- const options = schema.items.map((item, i) =>
354
- this.convertSchema(item, ctx),
355
- );
356
- node.items = { type: "union", options, path: ctx.path };
357
- } else {
358
- node.items = this.convertSchema(schema.items, {
359
- path: ctx.path ? `${ctx.path}.0` : "0",
360
- });
361
- }
362
- }
363
- return node;
364
- }
365
- case "string":
366
- case "number":
367
- case "integer":
368
- case "boolean":
369
- case "null": {
370
- const node: IRNode = { type: schema.type, path: ctx.path };
371
- node.constraints = {};
372
- if (schema.pattern) node.constraints.pattern = schema.pattern;
373
- if (schema.minLength !== undefined)
374
- node.constraints.minLength = schema.minLength;
375
- if (schema.maxLength !== undefined)
376
- node.constraints.maxLength = schema.maxLength;
377
- if (schema.minimum !== undefined)
378
- node.constraints.minimum = schema.minimum;
379
- if (schema.maximum !== undefined)
380
- node.constraints.maximum = schema.maximum;
381
- return node;
382
- }
383
- default:
384
- throw new Error(
385
- `Unsupported schema type: ${schema.type} - ${JSON.stringify(schema)}`,
386
- );
387
- }
388
- }
389
- }
390
-
391
- // Fallback: if no type is provided, assume an object.
392
- return { type: "object", properties: {}, path: ctx.path };
393
- }
394
-
395
- /**
396
- * Converts a JSON Schema definition to an IRNode.
397
- */
398
- public convertToIRSchema(schema: JSONSchema7Definition): IRNode {
399
- // reset the root schema
400
- this.rootSchema = null;
401
-
402
- // Optionally validate the schema.
403
- if (this.opts?.validateSchema) {
404
- this.validateSchema(schema);
405
- }
406
-
407
- // Resolve references (for now, this is a placeholder).
408
- const resolvedSchema = this.resolveReferences(schema);
409
-
410
- // Store the root schema (if it is an object) to support local $ref resolution.
411
- if (typeof resolvedSchema === "object") {
412
- this.rootSchema = resolvedSchema;
413
- }
414
-
415
- // Convert the resolved schema to our intermediate representation.
416
- let ir = this.convertToIR(resolvedSchema);
417
-
418
- // Apply any custom IR transformation.
419
- if (this.opts?.transform) {
420
- ir = this.opts.transform(ir);
421
- }
422
-
423
- return ir;
424
- }
425
-
426
- private validateSchema(schema: JSONSchema7Definition): void {
427
- // Implement or integrate with a JSON Schema validator if desired.
428
- // For now, we assume the schema is valid.
429
- }
430
-
431
- /**
432
- * A placeholder for reference resolution. In a more advanced implementation,
433
- * this method might inline external references or perform more complex processing.
434
- * ie: fetching a remote/url $def
435
- */
436
- private resolveReferences(
437
- schema: JSONSchema7Definition,
438
- ): JSONSchema7Definition {
439
- // For this implementation, simply return the schema unchanged.
440
- return schema;
441
- }
442
-
443
- private convertToIR(schema: JSONSchema7Definition): IRNode {
444
- return this.convertSchema(schema, { path: "" });
445
- }
446
-
447
- private getNameFromPath(path: string): string | undefined {
448
- // ignore numbers
449
- const split = path.split(".").filter((x) => !x.match(/^\d+$/));
450
-
451
- return split[split.length - 1] || undefined;
452
- }
453
-
454
- private getNextObjectSequence(): number {
455
- return this.signatureOccurrences.size;
456
- }
457
-
458
- private getParentNameFromPath(path: string): string | undefined {
459
- const split = path.split(".").filter((x) => !x.match(/^\d+$/));
460
- // ignore numbers
461
- return split[split.length - 2] || undefined;
462
- }
463
-
464
- /**
465
- * Generates a unique signature for a schema.
466
- * Uses a stable JSON.stringify that sorts keys to ensure that
467
- * semantically equivalent schemas produce the same string.
468
- */
469
- private getSchemaSignature(schema: JSONSchema7Definition): string {
470
- function sortKeys(obj: any): any {
471
- if (typeof obj !== "object" || obj === null) return obj;
472
- if (Array.isArray(obj)) return obj.map(sortKeys);
473
- const sorted: any = {};
474
- Object.keys(obj)
475
- .sort()
476
- .forEach((key) => {
477
- sorted[key] = sortKeys(obj[key]);
478
- });
479
- return sorted;
480
- }
481
- return this.simpleHash(JSON.stringify(sortKeys(schema))).toString();
482
- }
483
-
484
- private getTypeName(schema: JSONSchema7, ctx: { path: string }): string {
485
- return (
486
- schema.title ||
487
- this.getNameFromPath(ctx.path) ||
488
- `Object${this.getNextObjectSequence()}`
489
- );
490
- }
491
-
492
- /**
493
- * Resolves a local $ref using the stored root schema.
494
- *
495
- * This method expects local references (starting with "#/") and uses a simple
496
- * JSON Pointer resolution algorithm.
497
- */
498
- private resolveRef(schema: JSONSchema7): JSONSchema7Definition {
499
- if (!schema.$ref) return schema as unknown as JSONSchema7Definition;
500
- if (!this.rootSchema || typeof this.rootSchema !== "object") {
501
- throw new Error("Root schema not available for reference resolution.");
502
- }
503
- const ref = schema.$ref;
504
- if (!ref.startsWith("#/")) {
505
- throw new Error(
506
- `Only local references are supported. Encountered: ${ref}`,
507
- );
508
- }
509
- return this.resolvePointer(ref, this.rootSchema);
510
- }
511
-
512
- /**
513
- * Resolves a JSON Pointer (RFC 6901) within the given document.
514
- *
515
- * @param pointer A JSON Pointer string (e.g., "#/definitions/Foo" or "#/$defs/Bar")
516
- * @param document The root document object.
517
- */
518
- private resolvePointer(pointer: string, document: any): any {
519
- // Remove the leading '#' character.
520
- if (pointer[0] === "#") {
521
- pointer = pointer.substring(1);
522
- }
523
- if (!pointer) return document;
524
- // Split the pointer by "/" and filter out empty parts.
525
- const parts = pointer.split("/").filter((part) => part);
526
- let current = document;
527
- for (const part of parts) {
528
- // Unescape any "~1" to "/" and "~0" to "~".
529
- const unescaped = part.replace(/~1/g, "/").replace(/~0/g, "~");
530
- current = current[unescaped];
531
- if (current === undefined) {
532
- throw new Error(`Reference "${pointer}" not found in document.`);
533
- }
534
- }
535
- return current;
536
- }
537
-
538
- private simpleHash(str: string) {
539
- let hash = 0;
540
- for (let i = 0; i < str.length; i++) {
541
- hash = (hash << 5) - hash + str.charCodeAt(i);
542
- hash |= 0; // Convert to 32bit integer
543
- }
544
- return hash;
545
- }
546
- }
@@ -1,127 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { JSONSchemaConverter } from "./JSONSchemaConverter.js";
3
- import { Type } from "@sinclair/typebox";
4
-
5
- describe("Typebox to IRNode Test", () => {
6
- it("should convert strings", () => {
7
- expect(new JSONSchemaConverter(Type.String()).irNode.type).toMatch(
8
- "string",
9
- );
10
- });
11
-
12
- it("should convert numbers", () => {
13
- expect(new JSONSchemaConverter(Type.Number()).irNode.type).toMatch(
14
- "number",
15
- );
16
- });
17
-
18
- it("should convert booleans", () => {
19
- expect(new JSONSchemaConverter(Type.Boolean()).irNode.type).toMatch(
20
- "boolean",
21
- );
22
- });
23
-
24
- it("should convert null", () => {
25
- expect(new JSONSchemaConverter(Type.Null()).irNode.type).toMatch("null");
26
- });
27
-
28
- it("should convert literals", () => {
29
- expect(
30
- new JSONSchemaConverter(Type.Literal("fixedValue")).irNode,
31
- ).toMatchObject({ type: "literal", constraints: { value: "fixedValue" } });
32
- });
33
-
34
- it("should convert enums", () => {
35
- expect(
36
- new JSONSchemaConverter(
37
- Type.Enum({ Value1: "value1", Value2: "value2", Value3: "value3" }),
38
- ).irNode,
39
- ).toMatchObject({
40
- type: "union",
41
- options: [
42
- { type: "literal", constraints: { value: "value1" } },
43
- { type: "literal", constraints: { value: "value2" } },
44
- { type: "literal", constraints: { value: "value3" } },
45
- ],
46
- });
47
- });
48
-
49
- it("should convert Record", () => {
50
- expect(
51
- new JSONSchemaConverter(
52
- Type.Record(
53
- Type.String(),
54
- Type.Object({
55
- name: Type.String(),
56
- email: Type.String(),
57
- }),
58
- ),
59
- ).irNode,
60
- ).toMatchObject({
61
- type: "object",
62
- additionalProperties: {
63
- type: "object",
64
- properties: {
65
- name: {
66
- type: "string",
67
- path: "*.name",
68
- constraints: {},
69
- required: true,
70
- },
71
- email: {
72
- type: "string",
73
- path: "*.email",
74
- constraints: {},
75
- required: true,
76
- },
77
- },
78
- },
79
- });
80
- });
81
-
82
- it("should convert unions", () => {
83
- expect(
84
- new JSONSchemaConverter(Type.Union([Type.String(), Type.Number()]))
85
- .irNode,
86
- ).toMatchObject({
87
- type: "union",
88
- options: [{ type: "string" }, { type: "number" }],
89
- });
90
- });
91
-
92
- it("should convert composite unions", () => {
93
- expect(
94
- new JSONSchemaConverter(
95
- Type.Composite([
96
- Type.Object({ a: Type.String() }),
97
- Type.Object({ b: Type.Number() }),
98
- ]),
99
- ).irNode,
100
- ).toMatchObject({
101
- type: "object",
102
- properties: {
103
- a: { type: "string", path: "a", required: true },
104
- b: { type: "number", path: "b", required: true },
105
- },
106
- });
107
- });
108
-
109
- it("should convert intersections", () => {
110
- expect(
111
- new JSONSchemaConverter(Type.Intersect([Type.String(), Type.Number()]))
112
- .irNode,
113
- ).toMatchObject({
114
- type: "intersection",
115
- options: [{ type: "string" }, { type: "number" }],
116
- });
117
- });
118
-
119
- it("should convert arrays", () => {
120
- expect(
121
- new JSONSchemaConverter(Type.Array(Type.String())).irNode,
122
- ).toMatchObject({
123
- type: "array",
124
- items: { type: "string" },
125
- });
126
- });
127
- });