Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: standalone ajv schema #461

Merged
merged 10 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -679,8 +679,8 @@ console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"F
### Standalone Mode

The standalone mode is used to compile the code that can be directly run by `node`
itself. You need to install `fast-json-stringify`, `ajv`, `fast-uri` and `ajv-formats`
in order to let the standalone code works.
itself. You need to install `ajv`, `fast-uri` and `ajv-formats` for
the standalone code to work.

```js
const fs = require('fs')
Expand Down
26 changes: 25 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Options as AjvOptions } from "ajv"
import Ajv, { Options as AjvOptions } from "ajv"
declare namespace build {
interface BaseSchema {
/**
Expand Down Expand Up @@ -157,14 +157,38 @@ declare namespace build {
* Optionally configure how the integer will be rounded
*/
rounding?: 'ceil' | 'floor' | 'round'
/**
* @deprecated
* Enable debug mode. Please use `mode: "debug"` instead
*/
debugMode?: boolean
/**
* Running mode of fast-json-stringify
*/
mode?: 'debug' | 'standalone'
}
}

interface DebugOption extends build.Options {
mode: 'debug'
}

interface DeprecateDebugOption extends build.Options {
debugMode: true
}

interface StandaloneOption extends build.Options {
mode: 'standalone'
}

/**
* Build a stringify function using a schema of the documents that should be stringified
* @param schema The schema used to stringify values
* @param options The options to use (optional)
*/
declare function build(schema: build.AnySchema, options: DebugOption): { code: string, ajv: Ajv };
declare function build(schema: build.AnySchema, options: DeprecateDebugOption): { code: string, ajv: Ajv };
declare function build(schema: build.AnySchema, options: StandaloneOption): string;
declare function build(schema: build.AnySchema, options?: build.Options): (doc: any) => any;
declare function build(schema: build.StringSchema, options?: build.Options): (doc: string) => string;
declare function build(schema: build.IntegerSchema | build.NumberSchema, options?: build.Options): (doc: number) => string;
Expand Down
16 changes: 3 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,19 +136,9 @@ function build (schema, options) {
}

if (options.mode === 'standalone') {
return `
'use strict'

const Serializer = require('fast-json-stringify/serializer')
const buildAjv = require('fast-json-stringify/ajv')

const serializer = new Serializer(${JSON.stringify(options || {})})
const ajv = buildAjv(${JSON.stringify(options.ajv || {})})

${contextFunctionCode.replace('return main', '')}

module.exports = main
`
// lazy load
const buildStandaloneCode = require('./standalone')
return buildStandaloneCode(options, ajvInstance, contextFunctionCode)
}

/* eslint no-new-func: "off" */
Expand Down
42 changes: 42 additions & 0 deletions standalone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const fs = require('fs')
const path = require('path')

function buildStandaloneCode (options, ajvInstance, contextFunctionCode) {
const serializerCode = fs.readFileSync(path.join(__dirname, 'serializer.js')).toString()
let buildAjvCode = ''
let defaultAjvSchema = ''
const defaultMeta = ajvInstance.defaultMeta()
if (typeof defaultMeta === 'string') {
defaultAjvSchema = defaultMeta
} else {
defaultAjvSchema = defaultMeta.$id || defaultMeta.id
}
const shouldUseAjv = contextFunctionCode.indexOf('ajv') !== -1
// we need to export the custom json schema
let ajvSchemasCode = ''
if (shouldUseAjv) {
ajvSchemasCode += `const ajv = buildAjv(${JSON.stringify(options.ajv || {})})\n`
for (const [id, schema] of Object.entries(ajvInstance.schemas)) {
// should skip ajv default schema
if (id === defaultAjvSchema) continue
ajvSchemasCode += `ajv.addSchema(${JSON.stringify(schema.schema)}, "${id}")\n`
}
buildAjvCode = fs.readFileSync(path.join(__dirname, 'ajv.js')).toString()
buildAjvCode = buildAjvCode.replace("'use strict'", '').replace('module.exports = buildAjv', '')
}
return `
'use strict'

${serializerCode.replace("'use strict'", '').replace('module.exports = ', '')}
${buildAjvCode}

const serializer = new Serializer(${JSON.stringify(options || {})})
${ajvSchemasCode}

${contextFunctionCode.replace('return main', '')}

module.exports = main
`
}

module.exports = buildStandaloneCode
93 changes: 88 additions & 5 deletions test/standalone-mode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const fjs = require('..')
const fs = require('fs')
const path = require('path')

function build (opts) {
return fjs({
function build (opts, schema) {
return fjs(schema || {
title: 'default string',
type: 'object',
properties: {
Expand All @@ -21,10 +21,10 @@ function build (opts) {
const tmpDir = 'test/fixtures'

test('activate standalone mode', async (t) => {
t.plan(2)
let code = build({ mode: 'standalone' })
t.plan(3)
const code = build({ mode: 'standalone' })
t.type(code, 'string')
code = code.replace(/fast-json-stringify/g, '../..')
t.equal(code.indexOf('ajv'), -1)

const destionation = path.resolve(tmpDir, 'standalone.js')

Expand All @@ -36,3 +36,86 @@ test('activate standalone mode', async (t) => {
const standalone = require(destionation)
t.same(standalone({ firstName: 'Foo', surname: 'bar' }), JSON.stringify({ firstName: 'Foo' }), 'surname evicted')
})

test('test ajv schema', async (t) => {
t.plan(3)
const code = build({ mode: 'standalone' }, {
type: 'object',
properties: {
},
if: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['foobar'] }
}
},
then: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['foobar'] },
foo: { type: 'string' },
bar: { type: 'number' },
list: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'string' }
}
}
}
}
},
else: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['greeting'] },
hi: { type: 'string' },
hello: { type: 'number' },
list: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'string' }
}
}
}
}
}
})
t.type(code, 'string')
t.equal(code.indexOf('ajv') > 0, true)

const destionation = path.resolve(tmpDir, 'standalone2.js')

t.teardown(async () => {
await fs.promises.rm(destionation, { force: true })
})

await fs.promises.writeFile(destionation, code)
const standalone = require(destionation)
t.same(standalone({
kind: 'foobar',
foo: 'FOO',
list: [{
name: 'name',
value: 'foo'
}],
bar: 42,
hi: 'HI',
hello: 45,
a: 'A',
b: 35
}), JSON.stringify({
kind: 'foobar',
foo: 'FOO',
bar: 42,
list: [{
name: 'name',
value: 'foo'
}]
}))
})
10 changes: 9 additions & 1 deletion test/types/test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Ajv from 'ajv'
import build, { Schema } from '../..'

// Number schemas
Expand Down Expand Up @@ -142,4 +143,11 @@ const schema12: Schema = {
format: 'date-time'
}

build(schema12)(new Date())
build(schema12)(new Date())

let str: string, ajv: Ajv
str = build(schema1, { debugMode: true }).code
ajv = build(schema1, { debugMode: true }).ajv
str = build(schema1, { mode: 'debug' }).code
ajv = build(schema1, { mode: 'debug' }).ajv
str = build(schema1, { mode: 'standalone' })