Skip to content

Commit 15f6a43

Browse files
committed
🎉 feat: ref
1 parent 0d1bd0d commit 15f6a43

16 files changed

+190
-75
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 0.1.3 - 14 Mar 2025
2+
Bug fix:
3+
- support `t.Module`, `t.Ref`
4+
15
# 0.1.2 - 4 Mar 2025
26
Bug fix:
37
- handle primitive type when array is root

example/index.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import { t } from 'elysia'
22
import { createAccelerator } from '../src/index'
33

4-
const shape = t.Array(t.Number())
4+
const v = t.Module({
5+
a: t.Object({
6+
name: t.String(),
7+
job: t.Optional(t.Ref('job')),
8+
trait: t.Optional(t.String())
9+
}),
10+
job: t.Number()
11+
})
512

6-
const value = [1,2] satisfies typeof shape.static
13+
const shape = v.Import('a')
714

8-
const stringify = createAccelerator(shape)
15+
const value = {
16+
name: 'Jane Doe',
17+
job: 'Software Engineer',
18+
trait: 'Friendly'
19+
} satisfies typeof shape.static
920

10-
console.log(stringify(value))
11-
// console.log(stringify.toString())
21+
const mirror = createAccelerator(shape)
22+
23+
console.log(mirror(value))

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json-accelerator",
3-
"version": "0.1.2",
3+
"version": "0.1.3",
44
"description": "Speed up JSON stringification by providing OpenAPI/TypeBox model",
55
"license": "MIT",
66
"scripts": {

src/index.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ interface Instruction {
167167
* @default 'sanitize'
168168
**/
169169
sanitize: 'auto' | 'manual' | 'throw'
170+
definitions: Record<string, TAnySchema>
170171
}
171172

172173
const SANITIZE = {
@@ -225,6 +226,16 @@ const accelerate = (
225226
): string => {
226227
if (!schema) return ''
227228

229+
if (
230+
Kind in schema &&
231+
schema[Kind] === 'Import' &&
232+
schema.$ref in schema.$defs
233+
)
234+
return accelerate(schema.$defs[schema.$ref], property, {
235+
...instruction,
236+
definitions: Object.assign(instruction.definitions, schema.$defs)
237+
})
238+
228239
let v = ''
229240
const isRoot = property === 'v'
230241

@@ -376,14 +387,14 @@ const accelerate = (
376387
instruction.array++
377388

378389
if (schema.items.type === 'string') {
379-
if(isRoot) v += 'return `'
390+
if (isRoot) v += 'return `'
380391

381392
if (nullableCondition)
382393
v += `\${${nullableCondition}?"null":${property}.length?\`[${joinStringArray(property)}]\`:"[]"}`
383394
else
384395
v += `\${${property}.length?\`[${joinStringArray(property)}]\`:"[]"}`
385396

386-
if(isRoot) v += '`'
397+
if (isRoot) v += '`'
387398

388399
break
389400
}
@@ -394,14 +405,14 @@ const accelerate = (
394405
schema.items.type === 'bigint' ||
395406
isInteger(schema.items)
396407
) {
397-
if(isRoot) v += 'return `'
408+
if (isRoot) v += 'return `'
398409

399410
if (nullableCondition)
400411
v += `\${${nullableCondition}?'"null"':${property}.length?\`[$\{${property}.toString()}]\`:"[]"`
401412
else
402413
v += `\${${property}.length?\`[$\{${property}.toString()}]\`:"[]"}`
403414

404-
if(isRoot)v += '`'
415+
if (isRoot) v += '`'
405416

406417
break
407418
}
@@ -427,6 +438,13 @@ const accelerate = (
427438
break
428439

429440
default:
441+
if (schema.$ref && schema.$ref in instruction.definitions)
442+
return accelerate(
443+
instruction.definitions[schema.$ref],
444+
property,
445+
instruction
446+
)
447+
430448
if (isDateType(schema)) {
431449
if (isNullable || isUndefinable)
432450
v = `\${${nullableCondition}?${schema.default !== undefined ? `'"${schema.default}"'` : "'null'"}:typeof ${property}==="object"?\`\"\${${property}.toISOString()}\"\`:${property}}`
@@ -490,17 +508,17 @@ const accelerate = (
490508
export const createAccelerator = <T extends TAnySchema>(
491509
schema: T,
492510
{
493-
sanitize = 'auto'
494-
}: {
495-
sanitize?: Instruction['sanitize']
496-
} = {}
511+
sanitize = 'auto',
512+
definitions = {}
513+
}: Partial<Pick<Instruction, 'sanitize' | 'definitions'>> = {}
497514
): ((v: T['static']) => string) => {
498515
const f = accelerate(schema, 'v', {
499516
array: 0,
500517
optional: 0,
501518
properties: [],
502519
hasString: false,
503-
sanitize
520+
sanitize,
521+
definitions
504522
})
505523

506524
return Function('v', f) as any

test/array.test.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import { t } from 'elysia'
2-
import { type TAnySchema } from '@sinclair/typebox'
3-
import { createAccelerator } from '../src'
4-
5-
import { describe, expect, it } from 'bun:test'
1+
import { describe, it } from 'bun:test'
2+
import { isEqual } from './utils'
63

7-
const isEqual = (shape: TAnySchema, value: unknown, expected = value) =>
8-
expect(JSON.parse(createAccelerator(shape)(value))).toEqual(expected)
4+
import { t } from 'elysia'
95

106
describe('Array', () => {
117
it('handle string array at root', () => {

test/default.test.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import { t } from 'elysia'
2-
import { type TAnySchema } from '@sinclair/typebox'
3-
import { createAccelerator } from '../src'
4-
5-
import { describe, expect, it } from 'bun:test'
1+
import { describe, it } from 'bun:test'
2+
import { isEqual } from './utils'
63

7-
const isEqual = (shape: TAnySchema, value: unknown, expected = value) =>
8-
expect(JSON.parse(createAccelerator(shape)(value))).toEqual(expected)
4+
import { t } from 'elysia'
95

106
describe('Default', () => {
117
it('handle default value for optional string', () => {

test/index.test.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { t } from 'elysia'
2-
import { type TAnySchema } from '@sinclair/typebox'
3-
import { createAccelerator } from '../src'
4-
51
import { describe, expect, it } from 'bun:test'
2+
import { isEqual } from './utils'
63

7-
const isEqual = (shape: TAnySchema, value: unknown, expected = value) =>
8-
expect(JSON.parse(createAccelerator(shape)(value))).toEqual(expected)
4+
import { t } from 'elysia'
5+
6+
import { createAccelerator } from '../src'
97

108
describe('Core', () => {
119
it('handle string', () => {

test/merge-intersection.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { describe, expect, it } from 'bun:test'
2+
13
import { Type as t } from '@sinclair/typebox'
2-
import { mergeObjectIntersection } from '../src'
34

4-
import { describe, expect, it } from 'bun:test'
5+
import { mergeObjectIntersection } from '../src'
56

67
describe('Merge Object Intersection', () => {
78
it('work', () => {

test/record.test.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import { t } from 'elysia'
2-
import { type TAnySchema } from '@sinclair/typebox'
3-
import { createAccelerator } from '../src'
4-
5-
import { describe, expect, it } from 'bun:test'
1+
import { describe, it } from 'bun:test'
2+
import { isEqual } from './utils'
63

7-
const isEqual = (shape: TAnySchema, value: unknown, expected = value) =>
8-
expect(JSON.parse(createAccelerator(shape)(value))).toEqual(expected)
4+
import { t } from 'elysia'
95

106
describe('Record', () => {
117
it('handle record', () => {

test/ref.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { describe, it, expect } from 'bun:test'
2+
import { isEqual } from './utils'
3+
4+
import { t } from 'elysia'
5+
6+
import { createAccelerator } from '../src'
7+
8+
describe('Ref', () => {
9+
it('handle module', () => {
10+
const modules = t.Module({
11+
object: t.Object({
12+
name: t.String(),
13+
optional: t.Optional(t.String())
14+
})
15+
})
16+
17+
const shape = modules.Import('object')
18+
19+
const value = {
20+
name: 'salt'
21+
} satisfies typeof shape.static
22+
23+
isEqual(shape, value)
24+
})
25+
26+
it('handle nested ref', () => {
27+
const modules = t.Module({
28+
object: t.Object({
29+
name: t.String(),
30+
info: t.Ref('info')
31+
}),
32+
info: t.Object({
33+
id: t.Number(),
34+
name: t.String()
35+
})
36+
})
37+
38+
const shape = modules.Import('object')
39+
40+
const value = {
41+
name: 'salt',
42+
info: {
43+
id: 123,
44+
name: 'salt'
45+
}
46+
} satisfies typeof shape.static
47+
48+
isEqual(shape, value)
49+
})
50+
51+
it('handle optional ref', () => {
52+
const modules = t.Module({
53+
object: t.Object({
54+
name: t.String(),
55+
info: t.Optional(t.Ref('info'))
56+
}),
57+
info: t.Object({
58+
id: t.Number(),
59+
name: t.String()
60+
})
61+
})
62+
63+
const shape = modules.Import('object')
64+
65+
const value = {
66+
name: 'salt'
67+
} satisfies typeof shape.static
68+
69+
isEqual(shape, {
70+
name: 'salt'
71+
})
72+
73+
isEqual(shape, {
74+
name: 'salt',
75+
info: {
76+
id: 123,
77+
name: 'salt'
78+
}
79+
})
80+
})
81+
82+
it('handle custom modules', () => {
83+
const definitions = {
84+
object: t.Object({
85+
name: t.String(),
86+
optional: t.Optional(t.String())
87+
})
88+
}
89+
90+
const shape = definitions.object
91+
92+
const value = {
93+
name: 'salt'
94+
} satisfies typeof shape.static
95+
96+
expect(
97+
createAccelerator(shape, {
98+
definitions
99+
})(value)
100+
).toEqual(JSON.stringify(value))
101+
})
102+
})

0 commit comments

Comments
 (0)