Skip to content

Commit 658411c

Browse files
authored
feat: standalone mode (#460)
* feat: standalone mode * docs: update toc
1 parent b57abf1 commit 658411c

File tree

6 files changed

+297
-200
lines changed

6 files changed

+297
-200
lines changed

README.md

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ compile-json-stringify date format x 1,086,187 ops/sec ±0.16% (99 runs sampled)
6262
- <a href="#nullable">`Nullable`</a>
6363
- <a href="#largearrays">`Large Arrays`</a>
6464
- <a href="#security">`Security Notice`</a>
65+
- <a href="#debug">`Debug Mode`</a>
66+
- <a href="#standalone">`Standalone Mode`</a>
6567
- <a href="#acknowledgements">`Acknowledgements`</a>
6668
- <a href="#license">`License`</a>
6769

@@ -663,13 +665,37 @@ const debugCompiled = fastJson({
663665
type: 'string'
664666
}
665667
}
666-
}, { debugMode: true })
668+
}, { mode: 'debug' })
667669

668-
console.log(debugCompiled) // it is an array of functions that can create your `stringify` function
669-
console.log(debugCompiled.toString()) // print a "ready to read" string function, you can save it to a file
670+
console.log(debugCompiled) // it is a object contain code, ajv instance
671+
const rawString = debugCompiled.code // it is the generated code
672+
console.log(rawString)
670673

671-
const rawString = debugCompiled.toString()
672-
const stringify = fastJson.restore(rawString) // use the generated string to get back the `stringify` function
674+
const stringify = fastJson.restore(debugCompiled) // use the generated string to get back the `stringify` function
675+
console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'
676+
```
677+
678+
<a name="standalone"></a>
679+
### Standalone Mode
680+
681+
The standalone mode is used to compile the code that can be directly run by `node`
682+
itself. You need to install `fast-json-stringify`, `ajv`, `fast-uri` and `ajv-formats`
683+
in order to let the standalone code works.
684+
685+
```js
686+
const fs = require('fs')
687+
const code = fastJson({
688+
title: 'default string',
689+
type: 'object',
690+
properties: {
691+
firstName: {
692+
type: 'string'
693+
}
694+
}
695+
}, { mode: 'standalone' })
696+
697+
fs.writeFileSync('stringify.js', code)
698+
const stringify = require('stringify.js')
673699
console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'
674700
```
675701

ajv.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict'
2+
3+
const Ajv = require('ajv')
4+
const fastUri = require('fast-uri')
5+
const ajvFormats = require('ajv-formats')
6+
7+
module.exports = buildAjv
8+
9+
function buildAjv (options) {
10+
const ajvInstance = new Ajv({ ...options, strictSchema: false, uriResolver: fastUri })
11+
ajvFormats(ajvInstance)
12+
13+
const validateDateTimeFormat = ajvFormats.get('date-time').validate
14+
const validateDateFormat = ajvFormats.get('date').validate
15+
const validateTimeFormat = ajvFormats.get('time').validate
16+
17+
ajvInstance.addKeyword({
18+
keyword: 'fjs_date_type',
19+
validate: (schema, date) => {
20+
if (date instanceof Date) {
21+
return true
22+
}
23+
if (schema === 'date-time') {
24+
return validateDateTimeFormat(date)
25+
}
26+
if (schema === 'date') {
27+
return validateDateFormat(date)
28+
}
29+
if (schema === 'time') {
30+
return validateTimeFormat(date)
31+
}
32+
return false
33+
}
34+
})
35+
36+
return ajvInstance
37+
}

index.js

Lines changed: 23 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22

33
/* eslint no-prototype-builtins: 0 */
44

5-
const Ajv = require('ajv')
6-
const fastUri = require('fast-uri')
7-
const ajvFormats = require('ajv-formats')
85
const merge = require('deepmerge')
96
const clone = require('rfdc')({ proto: true })
107
const fjsCloned = Symbol('fast-json-stringify.cloned')
118
const { randomUUID } = require('crypto')
129

1310
const validate = require('./schema-validator')
11+
const Serializer = require('./serializer')
12+
const buildAjv = require('./ajv')
1413

1514
let largeArraySize = 2e4
1615
let stringSimilarity = null
@@ -57,173 +56,6 @@ const schemaReferenceMap = new Map()
5756
let ajvInstance = null
5857
let contextFunctions = null
5958

60-
class Serializer {
61-
constructor (options = {}) {
62-
switch (options.rounding) {
63-
case 'floor':
64-
this.parseInteger = Math.floor
65-
break
66-
case 'ceil':
67-
this.parseInteger = Math.ceil
68-
break
69-
case 'round':
70-
this.parseInteger = Math.round
71-
break
72-
default:
73-
this.parseInteger = Math.trunc
74-
break
75-
}
76-
}
77-
78-
asAny (i) {
79-
return JSON.stringify(i)
80-
}
81-
82-
asNull () {
83-
return 'null'
84-
}
85-
86-
asInteger (i) {
87-
if (typeof i === 'bigint') {
88-
return i.toString()
89-
} else if (Number.isInteger(i)) {
90-
return '' + i
91-
} else {
92-
/* eslint no-undef: "off" */
93-
const integer = this.parseInteger(i)
94-
if (Number.isNaN(integer)) {
95-
throw new Error(`The value "${i}" cannot be converted to an integer.`)
96-
} else {
97-
return '' + integer
98-
}
99-
}
100-
}
101-
102-
asIntegerNullable (i) {
103-
return i === null ? 'null' : this.asInteger(i)
104-
}
105-
106-
asNumber (i) {
107-
const num = Number(i)
108-
if (Number.isNaN(num)) {
109-
throw new Error(`The value "${i}" cannot be converted to a number.`)
110-
} else {
111-
return '' + num
112-
}
113-
}
114-
115-
asNumberNullable (i) {
116-
return i === null ? 'null' : this.asNumber(i)
117-
}
118-
119-
asBoolean (bool) {
120-
return bool && 'true' || 'false' // eslint-disable-line
121-
}
122-
123-
asBooleanNullable (bool) {
124-
return bool === null ? 'null' : this.asBoolean(bool)
125-
}
126-
127-
asDatetime (date, skipQuotes) {
128-
const quotes = skipQuotes === true ? '' : '"'
129-
if (date instanceof Date) {
130-
return quotes + date.toISOString() + quotes
131-
}
132-
return this.asString(date, skipQuotes)
133-
}
134-
135-
asDatetimeNullable (date, skipQuotes) {
136-
return date === null ? 'null' : this.asDatetime(date, skipQuotes)
137-
}
138-
139-
asDate (date, skipQuotes) {
140-
const quotes = skipQuotes === true ? '' : '"'
141-
if (date instanceof Date) {
142-
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
143-
}
144-
return this.asString(date, skipQuotes)
145-
}
146-
147-
asDateNullable (date, skipQuotes) {
148-
return date === null ? 'null' : this.asDate(date, skipQuotes)
149-
}
150-
151-
asTime (date, skipQuotes) {
152-
const quotes = skipQuotes === true ? '' : '"'
153-
if (date instanceof Date) {
154-
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
155-
}
156-
return this.asString(date, skipQuotes)
157-
}
158-
159-
asTimeNullable (date, skipQuotes) {
160-
return date === null ? 'null' : this.asTime(date, skipQuotes)
161-
}
162-
163-
asString (str, skipQuotes) {
164-
const quotes = skipQuotes === true ? '' : '"'
165-
if (str instanceof Date) {
166-
return quotes + str.toISOString() + quotes
167-
} else if (str === null) {
168-
return quotes + quotes
169-
} else if (str instanceof RegExp) {
170-
str = str.source
171-
} else if (typeof str !== 'string') {
172-
str = str.toString()
173-
}
174-
// If we skipQuotes it means that we are using it as test
175-
// no need to test the string length for the render
176-
if (skipQuotes) {
177-
return str
178-
}
179-
180-
if (str.length < 42) {
181-
return this.asStringSmall(str)
182-
} else {
183-
return JSON.stringify(str)
184-
}
185-
}
186-
187-
asStringNullable (str) {
188-
return str === null ? 'null' : this.asString(str)
189-
}
190-
191-
// magically escape strings for json
192-
// relying on their charCodeAt
193-
// everything below 32 needs JSON.stringify()
194-
// every string that contain surrogate needs JSON.stringify()
195-
// 34 and 92 happens all the time, so we
196-
// have a fast case for them
197-
asStringSmall (str) {
198-
const l = str.length
199-
let result = ''
200-
let last = 0
201-
let found = false
202-
let surrogateFound = false
203-
let point = 255
204-
// eslint-disable-next-line
205-
for (var i = 0; i < l && point >= 32; i++) {
206-
point = str.charCodeAt(i)
207-
if (point >= 0xD800 && point <= 0xDFFF) {
208-
// The current character is a surrogate.
209-
surrogateFound = true
210-
}
211-
if (point === 34 || point === 92) {
212-
result += str.slice(last, i) + '\\'
213-
last = i
214-
found = true
215-
}
216-
}
217-
218-
if (!found) {
219-
result = str
220-
} else {
221-
result += str.slice(last)
222-
}
223-
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
224-
}
225-
}
226-
22759
function build (schema, options) {
22860
arrayItemsReferenceSerializersMap.clear()
22961
objectReferenceSerializersMap.clear()
@@ -232,31 +64,7 @@ function build (schema, options) {
23264
contextFunctions = []
23365
options = options || {}
23466

235-
ajvInstance = new Ajv({ ...options.ajv, strictSchema: false, uriResolver: fastUri })
236-
ajvFormats(ajvInstance)
237-
238-
const validateDateTimeFormat = ajvFormats.get('date-time').validate
239-
const validateDateFormat = ajvFormats.get('date').validate
240-
const validateTimeFormat = ajvFormats.get('time').validate
241-
242-
ajvInstance.addKeyword({
243-
keyword: 'fjs_date_type',
244-
validate: (schema, date) => {
245-
if (date instanceof Date) {
246-
return true
247-
}
248-
if (schema === 'date-time') {
249-
return validateDateTimeFormat(date)
250-
}
251-
if (schema === 'date') {
252-
return validateDateFormat(date)
253-
}
254-
if (schema === 'time') {
255-
return validateTimeFormat(date)
256-
}
257-
return false
258-
}
259-
})
67+
ajvInstance = buildAjv(options.ajv)
26068

26169
isValidSchema(schema)
26270
if (options.schema) {
@@ -320,9 +128,29 @@ function build (schema, options) {
320128
const dependenciesName = ['ajv', 'serializer', contextFunctionCode]
321129

322130
if (options.debugMode) {
131+
options.mode = 'debug'
132+
}
133+
134+
if (options.mode === 'debug') {
323135
return { code: dependenciesName.join('\n'), ajv: ajvInstance }
324136
}
325137

138+
if (options.mode === 'standalone') {
139+
return `
140+
'use strict'
141+
142+
const Serializer = require('fast-json-stringify/serializer')
143+
const buildAjv = require('fast-json-stringify/ajv')
144+
145+
const serializer = new Serializer(${JSON.stringify(options || {})})
146+
const ajv = buildAjv(${JSON.stringify(options.ajv || {})})
147+
148+
${contextFunctionCode.replace('return main', '')}
149+
150+
module.exports = main
151+
`
152+
}
153+
326154
/* eslint no-new-func: "off" */
327155
const contextFunc = new Function('ajv', 'serializer', contextFunctionCode)
328156
const stringifyFunc = contextFunc(ajvInstance, serializer)

0 commit comments

Comments
 (0)