From 12fa1e90a27a27abfcce5be3adac55de208795d3 Mon Sep 17 00:00:00 2001 From: KaKa Date: Mon, 13 Jun 2022 18:07:48 +0800 Subject: [PATCH] fix: standalone ajv schema (#461) * fix: standalone ajv schemas * docs: update readme * types: update types * docs: update readme Co-authored-by: Frazer Smith * chore: typo Co-authored-by: Matteo Collina * chore: remove console log * feat: on-demand ajv * chore: lint * chore: typo Co-authored-by: Zed * chore: update types Co-authored-by: Zed Co-authored-by: Frazer Smith Co-authored-by: Matteo Collina Co-authored-by: Zed --- README.md | 4 +- index.d.ts | 26 +++++++++- index.js | 16 ++----- standalone.js | 42 ++++++++++++++++ test/standalone-mode.test.js | 93 ++++++++++++++++++++++++++++++++++-- test/types/test.ts | 10 +++- 6 files changed, 169 insertions(+), 22 deletions(-) create mode 100644 standalone.js diff --git a/README.md b/README.md index 38157685..88ec1a09 100644 --- a/README.md +++ b/README.md @@ -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') diff --git a/index.d.ts b/index.d.ts index 42d92220..350d23bc 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -import { Options as AjvOptions } from "ajv" +import Ajv, { Options as AjvOptions } from "ajv" declare namespace build { interface BaseSchema { /** @@ -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; diff --git a/index.js b/index.js index ea02f2b4..19529050 100644 --- a/index.js +++ b/index.js @@ -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" */ diff --git a/standalone.js b/standalone.js new file mode 100644 index 00000000..3af879c1 --- /dev/null +++ b/standalone.js @@ -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 diff --git a/test/standalone-mode.test.js b/test/standalone-mode.test.js index d5378608..1e1570f2 100644 --- a/test/standalone-mode.test.js +++ b/test/standalone-mode.test.js @@ -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: { @@ -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') @@ -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' + }] + })) +}) diff --git a/test/types/test.ts b/test/types/test.ts index 939d5317..3e7d2631 100644 --- a/test/types/test.ts +++ b/test/types/test.ts @@ -1,3 +1,4 @@ +import Ajv from 'ajv' import build, { Schema } from '../..' // Number schemas @@ -142,4 +143,11 @@ const schema12: Schema = { format: 'date-time' } -build(schema12)(new Date()) \ No newline at end of file +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' }) \ No newline at end of file