@json-layout/core 0.1.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +70 -0
  3. package/src/compile/index.js +136 -0
  4. package/src/compile/serialize.js +76 -0
  5. package/src/compile/skeleton-node.js +162 -0
  6. package/src/compile/skeleton-tree.js +28 -0
  7. package/src/compile/types.ts +50 -0
  8. package/src/compile/utils/resolve-refs.js +69 -0
  9. package/src/i18n/en.js +10 -0
  10. package/src/i18n/fr.js +10 -0
  11. package/src/i18n/index.js +8 -0
  12. package/src/i18n/types.ts +14 -0
  13. package/src/index.js +3 -0
  14. package/src/state/index.js +381 -0
  15. package/src/state/state-node.js +317 -0
  16. package/src/state/state-tree.js +59 -0
  17. package/src/state/types.ts +89 -0
  18. package/src/state/utils/display.js +132 -0
  19. package/src/state/utils/immutable.js +11 -0
  20. package/types/compile/index.d.ts +14 -0
  21. package/types/compile/index.d.ts.map +1 -0
  22. package/types/compile/serialize.d.ts +6 -0
  23. package/types/compile/serialize.d.ts.map +1 -0
  24. package/types/compile/skeleton-node.d.ts +14 -0
  25. package/types/compile/skeleton-node.d.ts.map +1 -0
  26. package/types/compile/skeleton-tree.d.ts +12 -0
  27. package/types/compile/skeleton-tree.d.ts.map +1 -0
  28. package/types/compile/types.d.ts +43 -0
  29. package/types/compile/types.d.ts.map +1 -0
  30. package/types/compile/utils/resolve-refs.d.ts +9 -0
  31. package/types/compile/utils/resolve-refs.d.ts.map +1 -0
  32. package/types/i18n/en.d.ts +3 -0
  33. package/types/i18n/en.d.ts.map +1 -0
  34. package/types/i18n/fr.d.ts +3 -0
  35. package/types/i18n/fr.d.ts.map +1 -0
  36. package/types/i18n/index.d.ts +3 -0
  37. package/types/i18n/index.d.ts.map +1 -0
  38. package/types/i18n/types.d.ts +13 -0
  39. package/types/i18n/types.d.ts.map +1 -0
  40. package/types/index.d.ts +4 -0
  41. package/types/index.d.ts.map +1 -0
  42. package/types/state/index.d.ts +185 -0
  43. package/types/state/index.d.ts.map +1 -0
  44. package/types/state/state-node.d.ts +31 -0
  45. package/types/state/state-node.d.ts.map +1 -0
  46. package/types/state/state-tree.d.ts +13 -0
  47. package/types/state/state-tree.d.ts.map +1 -0
  48. package/types/state/types.d.ts +119 -0
  49. package/types/state/types.d.ts.map +1 -0
  50. package/types/state/utils/display.d.ts +51 -0
  51. package/types/state/utils/display.d.ts.map +1 -0
  52. package/types/state/utils/immutable.d.ts +8 -0
  53. package/types/state/utils/immutable.d.ts.map +1 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 json-layout
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@json-layout/core",
3
+ "version": "0.1.0",
4
+ "description": "Compilation and state management utilities for JSON Layout.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": {
9
+ "default": "./src/index.js",
10
+ "types": "./types/index.d.ts"
11
+ }
12
+ },
13
+ "./src/compile/serialize": {
14
+ "import": {
15
+ "default": "./src/compile/serialize.js",
16
+ "types": "./types/compile/serialize.d.ts"
17
+ }
18
+ }
19
+ },
20
+ "files": [
21
+ "src",
22
+ "types"
23
+ ],
24
+ "scripts": {
25
+ "test:single": "node --no-experimental-fetch --test --test-only test/*.spec.js",
26
+ "test": "node --no-experimental-fetch --test test/*.spec.js",
27
+ "build": "rm -rf ./types && tsc -p tsconfig.build.json",
28
+ "lint": "eslint . --fix",
29
+ "watch:build": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
30
+ "watch:test": "node --no-experimental-fetch --test --watch test/*.spec.js"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/json-layout/json-layout.git"
35
+ },
36
+ "keywords": [
37
+ "json",
38
+ "schema",
39
+ "forms",
40
+ "layout"
41
+ ],
42
+ "author": "Alban Mouton <alban.mouton@gmail.com>",
43
+ "license": "MIT",
44
+ "bugs": {
45
+ "url": "https://github.com/json-layout/json-layout/issues"
46
+ },
47
+ "homepage": "https://github.com/json-layout/json-layout#readme",
48
+ "dependencies": {
49
+ "@json-layout/vocabulary": "^0.1.0",
50
+ "@types/markdown-it": "^13.0.1",
51
+ "ajv": "^8.12.0",
52
+ "ajv-errors": "^3.0.0",
53
+ "ajv-formats": "^2.1.1",
54
+ "ajv-i18n": "^4.2.0",
55
+ "debug": "^4.3.4",
56
+ "immer": "^10.0.3",
57
+ "magicast": "^0.3.0",
58
+ "markdown-it": "^13.0.2",
59
+ "mitt": "^3.0.1",
60
+ "recast": "^0.23.4",
61
+ "rfdc": "^1.3.0"
62
+ },
63
+ "devDependencies": {
64
+ "@types/debug": "^4.1.7",
65
+ "@types/node-fetch": "^2.6.5",
66
+ "json-schema-to-typescript": "^13.1.1",
67
+ "nock": "^13.3.3",
68
+ "node-fetch": "^2.7.0"
69
+ }
70
+ }
@@ -0,0 +1,136 @@
1
+ // compileStatic is meant to produce a serializable result
2
+
3
+ import ajvModule from 'ajv'
4
+ import rfdc from 'rfdc'
5
+ // import { Parser as ExprEvalParser } from 'expr-eval'
6
+ import addFormats from 'ajv-formats'
7
+ import ajvErrors from 'ajv-errors'
8
+ import ajvLocalizeModule from 'ajv-i18n'
9
+ import MarkdownIt from 'markdown-it'
10
+ import i18n from '../i18n/index.js'
11
+ import { makeSkeletonTree } from './skeleton-tree.js'
12
+ import { resolveRefs } from './utils/resolve-refs.js'
13
+
14
+ export { resolveRefs } from './utils/resolve-refs.js'
15
+
16
+ /**
17
+ * @typedef {import('./types.js').SkeletonTree} SkeletonTree
18
+ * @typedef {import('./types.js').SkeletonNode} SkeletonNode
19
+ * @typedef {import('./types.js').CompiledLayout} CompiledLayout
20
+ * @typedef {import('./types.js').CompileOptions} CompileOptions
21
+ * @typedef {import('./types.js').PartialCompileOptions} PartialCompileOptions
22
+ * @typedef {import('./types.js').CompiledExpression} CompiledExpression
23
+ */
24
+
25
+ // @ts-ignore
26
+ const Ajv = /** @type {typeof ajvModule.default} */ (ajvModule)
27
+ // @ts-ignore
28
+ const ajvLocalize = /** @type {typeof ajvLocalizeModule.default} */ (ajvLocalizeModule)
29
+
30
+ const expressionsParams = ['data', 'options', 'display']
31
+
32
+ const clone = rfdc()
33
+ // const exprEvalParser = new ExprEvalParser()
34
+
35
+ /**
36
+ * @param {PartialCompileOptions} partialOptions
37
+ * @returns {CompileOptions}
38
+ */
39
+ const fillOptions = (partialOptions) => {
40
+ let ajv = partialOptions.ajv
41
+ if (!ajv) {
42
+ /** @type {import('ajv').Options} */
43
+ const ajvOpts = { strict: false, allErrors: true }
44
+ if (partialOptions.code) ajvOpts.code = { source: true, esm: true, lines: true }
45
+ const newAjv = new Ajv(ajvOpts)
46
+ addFormats.default(newAjv)
47
+ ajvErrors.default(newAjv)
48
+ ajv = newAjv
49
+ }
50
+
51
+ let markdown = partialOptions.markdown
52
+ if (!markdown) {
53
+ const markdownIt = new MarkdownIt(partialOptions.markdownIt ?? {})
54
+ markdown = markdownIt.render.bind(markdownIt)
55
+ }
56
+
57
+ const locale = partialOptions.locale || 'en'
58
+ const messages = { ...i18n[locale] || i18n.en }
59
+ if (partialOptions.messages) Object.assign(messages, partialOptions.messages)
60
+
61
+ return {
62
+ ajv,
63
+ code: false,
64
+ markdown,
65
+ ...partialOptions,
66
+ locale,
67
+ messages
68
+ }
69
+ }
70
+
71
+ /**
72
+ * @param {object} _schema
73
+ * @param {PartialCompileOptions} [partialOptions]
74
+ * @returns {CompiledLayout}
75
+ */
76
+ export function compile (_schema, partialOptions = {}) {
77
+ const options = fillOptions(partialOptions)
78
+
79
+ const schema = /** @type {import('ajv').SchemaObject} */(clone(_schema))
80
+
81
+ schema.$id = schema.$id ?? '_jl'
82
+ resolveRefs(schema, options.ajv, options.locale)
83
+
84
+ /** @type {string[]} */
85
+ const validatePointers = []
86
+ /** @type {Record<string, import('@json-layout/vocabulary').NormalizedLayout>} */
87
+ const normalizedLayouts = {}
88
+ /** @type {import('@json-layout/vocabulary').Expression[]} */
89
+ const expressionsDefinitions = []
90
+
91
+ const skeletonTree = makeSkeletonTree(schema, options, validatePointers, normalizedLayouts, expressionsDefinitions, `${schema.$id}#`, 'main')
92
+
93
+ options.ajv.addSchema(schema)
94
+
95
+ const uriResolver = options.ajv.opts.uriResolver
96
+ /** @type {Record<string, import('ajv').ValidateFunction>} */
97
+ const validates = {}
98
+ for (const pointer of validatePointers) {
99
+ const fullPointer = uriResolver.resolve(schema.$id, pointer)
100
+ validates[pointer] = options.ajv.compile({ $ref: fullPointer })
101
+ }
102
+
103
+ /** @type {CompiledExpression[]} */
104
+ const expressions = []
105
+
106
+ for (const expression of expressionsDefinitions) {
107
+ /* if (expression.type === 'expr-eval') {
108
+ expressions.push(exprEvalParser.parse(expression.expr).toJSFunction(expressionsParams.join(',')))
109
+ } */
110
+ if (expression.type === 'js-fn') {
111
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
112
+ expressions.push(/** @type {CompiledExpression} */(new Function(...expressionsParams, expression.expr)))
113
+ }
114
+ if (expression.type === 'js-eval') {
115
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func, @typescript-eslint/restrict-plus-operands
116
+ expressions.push(/** @type {CompiledExpression} */(new Function(...expressionsParams, 'return (' + expression.expr + ')')))
117
+ }
118
+ if (expression.type === 'js-tpl') {
119
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func, @typescript-eslint/restrict-plus-operands
120
+ expressions.push(/** @type {CompiledExpression} */(new Function(...expressionsParams, 'return `' + expression.expr + '`')))
121
+ }
122
+ }
123
+
124
+ return {
125
+ options,
126
+ schema,
127
+ skeletonTree,
128
+ validates,
129
+ normalizedLayouts,
130
+ expressions,
131
+ locale: options.locale,
132
+ messages: options.messages,
133
+ // @ts-ignore
134
+ localizeErrors: ajvLocalize[options.locale] || ajvLocalize.en
135
+ }
136
+ }
@@ -0,0 +1,76 @@
1
+ // import Debug from 'debug'
2
+ import { ok } from 'assert/strict'
3
+ import standaloneCode from 'ajv/dist/standalone/index.js'
4
+ import { parseModule, generateCode, builders } from 'magicast'
5
+ import { parse, print } from 'recast'
6
+
7
+ /**
8
+ * @param {import('./index.js').CompiledLayout} compiledLayout
9
+ * @returns {string}
10
+ */
11
+ export function serialize (compiledLayout) {
12
+ ok(compiledLayout.schema)
13
+ ok(compiledLayout.options)
14
+ const ajv = compiledLayout.options.ajv
15
+
16
+ /** @type {Record<string, string>} */
17
+ const validatesExports = {}
18
+ let i = 0
19
+ for (const pointer of Object.keys(compiledLayout.validates)) {
20
+ const fullPointer = ajv.opts.uriResolver.resolve(/** @type {string} */(compiledLayout.schema.$id), pointer)
21
+ const exportKey = `export${i++}`
22
+ ajv.addSchema({ $id: exportKey, $ref: fullPointer })
23
+ validatesExports[exportKey] = exportKey
24
+ }
25
+ let code = standaloneCode.default(ajv, validatesExports)
26
+
27
+ code = code.replace('"use strict";', '')
28
+
29
+ // some internal imports to ajv are not translated to asm, we do it here
30
+ // cf https://github.com/ajv-validator/ajv-formats/pull/73
31
+ if (code.includes('require("ajv-formats/dist/formats")')) {
32
+ code = 'import { fullFormats } from "ajv-formats/dist/formats.js";\n' + code
33
+ code = code.replace(/require\("ajv-formats\/dist\/formats"\)\.fullFormats/g, 'fullFormats')
34
+ }
35
+ if (code.includes('require("ajv/dist/runtime/ucs2length")')) {
36
+ code = 'import ucs2length from "ajv/dist/runtime/ucs2length.js";\n' + code
37
+ code = code.replace(/require\("ajv\/dist\/runtime\/ucs2length"\)/g, 'ucs2length')
38
+ }
39
+
40
+ // importe only the current locale from ajv-i18n
41
+ code = `import localizeErrors from "ajv-i18n/localize/${compiledLayout.locale}/index.js";
42
+ export const exportLocalizeErrors = localizeErrors;\n` + code
43
+
44
+ i = 0
45
+ const expressionsNodes = []
46
+ for (const expression of compiledLayout.expressions) {
47
+ const fn = parse(expression.toString()).program.body[0]
48
+ fn.id = `expression${i++}`
49
+ code += `\n${print(fn)}\n`
50
+ expressionsNodes.push(builders.raw(fn.id))
51
+ }
52
+
53
+ const ast = parseModule(code)
54
+ ast.exports.compiledLayout = {
55
+ skeletonTree: compiledLayout.skeletonTree,
56
+ normalizedLayouts: compiledLayout.normalizedLayouts,
57
+ validates: {},
58
+ expressions: expressionsNodes,
59
+ locale: compiledLayout.locale,
60
+ messages: compiledLayout.messages,
61
+ localizeErrors: ast.exports.exportLocalizeErrors
62
+ }
63
+ delete ast.exports.exportLocalizeErrors
64
+
65
+ i = 0
66
+ for (const pointer of Object.keys(compiledLayout.validates)) {
67
+ const exportKey = `export${i++}`
68
+ ast.exports.compiledLayout.validates[pointer] = ast.exports[exportKey]
69
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
70
+ delete ast.exports[exportKey]
71
+ }
72
+
73
+ const generatedCode = generateCode(ast).code.replace('export const compiledLayout = {', 'const compiledLayout = {')
74
+
75
+ return generatedCode
76
+ }
@@ -0,0 +1,162 @@
1
+ // import Debug from 'debug'
2
+ import { normalizeLayoutFragment, isSwitchStruct, isGetItemsExpression, isSelectLayout, isGetItemsFetch } from '@json-layout/vocabulary'
3
+ import { makeSkeletonTree } from './skeleton-tree.js'
4
+
5
+ /**
6
+ * @param {import('@json-layout/vocabulary').Expression[]} expressions
7
+ * @param {import('@json-layout/vocabulary').Expression} expression
8
+ */
9
+ const pushExpression = (expressions, expression) => {
10
+ const index = expressions.findIndex(e => e.type === expression.type && e.expr === expression.expr)
11
+ if (index !== -1) {
12
+ expression.ref = index
13
+ } else {
14
+ expression.ref = expressions.length
15
+ expressions.push(expression)
16
+ }
17
+ }
18
+
19
+ /**
20
+ * @param {any} schema
21
+ * @param {import('./index.js').CompileOptions} options
22
+ * @param {string[]} validates
23
+ * @param {Record<string, import('@json-layout/vocabulary').NormalizedLayout>} normalizedLayouts
24
+ * @param {import('@json-layout/vocabulary').Expression[]} expressions
25
+ * @param {string | number} key
26
+ * @param {string} pointer
27
+ * @param {string | null} parentPointer
28
+ * @param {boolean} required
29
+ * @returns {import('./types.js').SkeletonNode}
30
+ */
31
+ export function makeSkeletonNode (
32
+ schema,
33
+ options,
34
+ validates,
35
+ normalizedLayouts,
36
+ expressions,
37
+ key,
38
+ pointer,
39
+ parentPointer,
40
+ required
41
+ ) {
42
+ // consolidate schema
43
+ if (!schema.type && schema.properties) schema.type = 'object'
44
+
45
+ // improve on ajv error messages based on ajv-errors (https://ajv.js.org/packages/ajv-errors.html)
46
+ schema.errorMessage = schema.errorMessage ?? {}
47
+ /** @type {import('@json-layout/vocabulary').NormalizedLayout} */
48
+ const normalizedLayout = normalizedLayouts[pointer] ?? normalizeLayoutFragment(/** @type {import('@json-layout/vocabulary').SchemaFragment} */(schema), pointer, options.markdown)
49
+ normalizedLayouts[pointer] = normalizedLayout
50
+
51
+ const compObjects = isSwitchStruct(normalizedLayout) ? normalizedLayout.switch : [normalizedLayout]
52
+ for (const compObject of compObjects) {
53
+ if (schema.description && !compObject.help) compObject.help = schema.description
54
+ if (compObject.if) pushExpression(expressions, compObject.if)
55
+ if (isSelectLayout(compObject) && compObject.getItems) {
56
+ if (isGetItemsExpression(compObject.getItems)) pushExpression(expressions, compObject.getItems)
57
+ if (isGetItemsFetch(compObject.getItems)) pushExpression(expressions, compObject.getItems.url)
58
+ if (compObject.getItems.itemTitle) pushExpression(expressions, compObject.getItems.itemTitle)
59
+ if (compObject.getItems.itemKey) pushExpression(expressions, compObject.getItems.itemKey)
60
+ if (compObject.getItems.itemValue) pushExpression(expressions, compObject.getItems.itemValue)
61
+ if (compObject.getItems.itemsResults) pushExpression(expressions, compObject.getItems.itemsResults)
62
+ }
63
+ }
64
+
65
+ let defaultData
66
+ if (schema.const) defaultData = schema.const
67
+ else if (schema.default) defaultData = schema.default
68
+ if (required) {
69
+ if (schema.type === 'object') defaultData = {} // TODO: this is only true if property is required ?
70
+ if (schema.type === 'array') defaultData = []
71
+ if (schema.type === 'string' && !schema.format) defaultData = ''
72
+ }
73
+
74
+ /** @type {import('./types.js').SkeletonNode} */
75
+ const node = { key: key ?? '', pointer, parentPointer, defaultData }
76
+ if (schema.const) node.const = schema.const
77
+ if (schema.type === 'object') {
78
+ if (schema.properties) {
79
+ node.children = node.children ?? []
80
+ for (const propertyKey of Object.keys(schema.properties)) {
81
+ node.children.push(makeSkeletonNode(
82
+ schema.properties[propertyKey],
83
+ options,
84
+ validates,
85
+ normalizedLayouts,
86
+ expressions,
87
+ propertyKey,
88
+ `${pointer}/properties/${propertyKey}`,
89
+ pointer,
90
+ schema.required?.includes(propertyKey)
91
+ ))
92
+ if (schema?.required?.includes(propertyKey)) {
93
+ schema.errorMessage.required = schema.errorMessage.required ?? {}
94
+ schema.errorMessage.required[propertyKey] = options.messages.errorRequired
95
+ }
96
+ }
97
+ }
98
+ if (schema.allOf) {
99
+ node.children = node.children ?? []
100
+ for (let i = 0; i < schema.allOf.length; i++) {
101
+ node.children.push(makeSkeletonNode(
102
+ schema.allOf[i],
103
+ options,
104
+ validates,
105
+ normalizedLayouts,
106
+ expressions,
107
+ `$allOf-${i}`,
108
+ `${pointer}/allOf/${i}`,
109
+ pointer,
110
+ false
111
+ ))
112
+ }
113
+ }
114
+ if (schema.oneOf) {
115
+ const oneOfPointer = `${pointer}/oneOf`
116
+ normalizedLayouts[oneOfPointer] = normalizedLayouts[oneOfPointer] ?? normalizeLayoutFragment(schema, oneOfPointer, options.markdown, 'oneOf')
117
+ /** @type {import('./types.js').SkeletonTree[]} */
118
+ const childrenTrees = []
119
+ for (let i = 0; i < schema.oneOf.length; i++) {
120
+ if (!schema.oneOf[i].type) schema.oneOf[i].type = schema.type
121
+ const title = schema.oneOf[i].title ?? `option ${i}`
122
+ delete schema.oneOf[i].title
123
+ childrenTrees.push(makeSkeletonTree(schema.oneOf[i], options, validates, normalizedLayouts, expressions, `${oneOfPointer}/${i}`, title))
124
+ }
125
+ node.children = node.children ?? []
126
+ node.children.push({ key: '$oneOf', pointer: `${pointer}/oneOf`, parentPointer: pointer, childrenTrees })
127
+
128
+ schema.errorMessage.oneOf = options.messages.errorOneOf
129
+ }
130
+ }
131
+
132
+ if (schema.type === 'array' && schema.items) {
133
+ if (Array.isArray(schema.items)) {
134
+ node.children = schema.items.map((/** @type {any} */ itemSchema, /** @type {number} */ i) => {
135
+ return makeSkeletonNode(
136
+ itemSchema,
137
+ options,
138
+ validates,
139
+ normalizedLayouts,
140
+ expressions,
141
+ i,
142
+ `${pointer}/items/${i}`,
143
+ pointer,
144
+ true
145
+ )
146
+ })
147
+ } else {
148
+ node.childrenTrees = [
149
+ makeSkeletonTree(
150
+ schema.items,
151
+ options,
152
+ validates,
153
+ normalizedLayouts,
154
+ expressions,
155
+ `${pointer}/items`,
156
+ schema.items.title
157
+ )
158
+ ]
159
+ }
160
+ }
161
+ return node
162
+ }
@@ -0,0 +1,28 @@
1
+ // import Debug from 'debug'
2
+ import { makeSkeletonNode } from './skeleton-node.js'
3
+
4
+ // const debug = Debug('json-layout:compile-raw')
5
+
6
+ /**
7
+ * @param {any} schema
8
+ * @param {import('./index.js').CompileOptions} options
9
+ * @param {string[]} validates
10
+ * @param {Record<string, import('@json-layout/vocabulary').NormalizedLayout>} normalizedLayouts
11
+ * @param {import('@json-layout/vocabulary').Expression[]} expressions
12
+ * @param {string} pointer
13
+ * @param {string} title
14
+ * @returns {import('./types.js').SkeletonTree}
15
+ */
16
+ export function makeSkeletonTree (
17
+ schema,
18
+ options,
19
+ validates,
20
+ normalizedLayouts,
21
+ expressions,
22
+ pointer,
23
+ title
24
+ ) {
25
+ const root = makeSkeletonNode(schema, options, validates, normalizedLayouts, expressions, '', pointer, null, true)
26
+ validates.push(pointer)
27
+ return { title, root }
28
+ }
@@ -0,0 +1,50 @@
1
+ import type ajvModule from 'ajv'
2
+ import type MarkdownIt from 'markdown-it'
3
+ import { type NormalizedLayout, type StateNodeOptions } from '@json-layout/vocabulary'
4
+ import { type ValidateFunction, type SchemaObject } from 'ajv'
5
+ import { type Display } from '../state/utils/display.js'
6
+ import { type LocaleMessages } from '../i18n/types.js'
7
+
8
+ export type CompiledExpression = (data: any, options: StateNodeOptions, display: Display) => any
9
+
10
+ export interface CompileOptions {
11
+ ajv: ajvModule.default
12
+ code: boolean
13
+ markdown: (text: string) => string
14
+ markdownIt?: MarkdownIt.Options
15
+ locale: string
16
+ messages: LocaleMessages
17
+ }
18
+
19
+ export type PartialCompileOptions = Partial<Omit<CompileOptions, 'messages'>> & { messages?: Partial<LocaleMessages> }
20
+
21
+ export interface CompiledLayout {
22
+ options?: CompileOptions
23
+ schema?: SchemaObject
24
+ skeletonTree: SkeletonTree
25
+ validates: Record<string, ValidateFunction>
26
+ normalizedLayouts: Record<string, NormalizedLayout>
27
+ expressions: CompiledExpression[]
28
+ locale: string
29
+ messages: LocaleMessages
30
+ localizeErrors: (errors: ajvModule.ErrorObject[]) => void
31
+ }
32
+
33
+ // a tree is a root node and a validation function
34
+ // it will be used to instantiate a StateLayoutTree with 1 validation context
35
+ export interface SkeletonTree {
36
+ title: string
37
+ root: SkeletonNode
38
+ }
39
+
40
+ // a skeleton node is a light recursive structure
41
+ // at runtime each one will be instantiated as a StateNode with a value and an associated component instance
42
+ export interface SkeletonNode {
43
+ key: string | number
44
+ pointer: string
45
+ parentPointer: string | null
46
+ defaultData?: unknown
47
+ const?: unknown
48
+ children?: SkeletonNode[] // optional children in the case of arrays and object nodes
49
+ childrenTrees?: SkeletonTree[] // other trees that can be instantiated with separate validation (for example in the case of new array items of oneOfs, etc)
50
+ }
@@ -0,0 +1,69 @@
1
+ import ajvModule from 'ajv'
2
+
3
+ const Ajv = ajvModule.default
4
+
5
+ /**
6
+ * @param {Record<string, import('ajv').SchemaObject>} schemas
7
+ * @param {string} ref
8
+ * @param {ajvModule.default} ajv
9
+ * @returns {[any, string]}
10
+ */
11
+ const getJSONRef = (schemas, ref, ajv) => {
12
+ const [schemaId, pointer] = ref.split('#')
13
+ schemas[schemaId] = schemas[schemaId] ?? (ajv.getSchema(schemaId)?.schema)
14
+ if (!schemas[schemaId]) throw new Error(`reference not found ${schemaId}`)
15
+ const pointerParts = pointer.split('/').filter(p => !!p)
16
+ const { value: fragment } = pointerParts.reduce((a, pointerPart) => {
17
+ a.path.push(pointerPart)
18
+ if (!(pointerPart in a.value)) throw new Error(`reference not found ${schemaId}#${a.path.join('/')}`)
19
+ a.value = a.value[pointerPart]
20
+ return a
21
+ }, { path: ['/'], value: schemas[schemaId] })
22
+ return [fragment, schemaId]
23
+ }
24
+
25
+ /**
26
+ * @param {Record<string, import('ajv').SchemaObject>} schemas
27
+ * @param {import('ajv').SchemaObject} schemaFragment
28
+ * @param {string} schemaId
29
+ * @param {ajvModule.default} ajv
30
+ * @param {string} locale
31
+ * @returns {import('ajv').SchemaObject}
32
+ */
33
+ const recurse = (schemas, schemaFragment, schemaId, ajv, locale = 'en') => {
34
+ for (const key of Object.keys(schemaFragment)) {
35
+ if (schemaFragment[key] && typeof schemaFragment[key] === 'object') {
36
+ if ('$ref' in schemaFragment[key]) {
37
+ const fullRef = ajv.opts.uriResolver.resolve(schemaId, schemaFragment[key].$ref).replace('~$locale~', locale)
38
+ const fullRefDefaultLocale = ajv.opts.uriResolver.resolve(schemaId, schemaFragment[key].$ref).replace('~$locale~', 'en')
39
+ let refFragment, refSchemaId
40
+ try {
41
+ [refFragment, refSchemaId] = getJSONRef(schemas, fullRef, ajv)
42
+ } catch (err) {
43
+ [refFragment, refSchemaId] = getJSONRef(schemas, fullRefDefaultLocale, ajv)
44
+ }
45
+ if (typeof refFragment === 'object' && !Array.isArray(refFragment)) {
46
+ schemaFragment[key] = { ...refFragment, ...schemaFragment[key] }
47
+ delete schemaFragment[key].$ref
48
+ } else {
49
+ schemaFragment[key] = refFragment
50
+ }
51
+ recurse(schemas, schemaFragment[key], refSchemaId, ajv, locale)
52
+ } else {
53
+ recurse(schemas, schemaFragment[key], schemaId, ajv, locale)
54
+ }
55
+ }
56
+ }
57
+ return schemaFragment
58
+ }
59
+
60
+ /**
61
+ * @param {import('ajv').SchemaObject} schema
62
+ * @param {ajvModule.default} ajv
63
+ * @param {string} locale
64
+ * @returns {import('ajv').SchemaObject}
65
+ */
66
+ export function resolveRefs (schema, ajv, locale = 'en') {
67
+ if (!schema.$id) throw new Error('missing schema id')
68
+ return recurse({ [schema.$id]: schema }, schema, schema.$id, ajv ?? new Ajv(), locale)
69
+ }
package/src/i18n/en.js ADDED
@@ -0,0 +1,10 @@
1
+ /** @type {import('./types.js').LocaleMessages} */
2
+ export default {
3
+ errorOneOf: 'chose one',
4
+ errorRequired: 'required information',
5
+ addItem: 'Add item',
6
+ delete: 'Delete',
7
+ edit: 'Edit',
8
+ duplicate: 'Duplicate',
9
+ sort: 'Sort'
10
+ }
package/src/i18n/fr.js ADDED
@@ -0,0 +1,10 @@
1
+ /** @type {import('./types.js').LocaleMessages} */
2
+ export default {
3
+ errorOneOf: 'choisissez une valeur',
4
+ errorRequired: 'information obligatoire',
5
+ addItem: 'Ajouter un élément',
6
+ delete: 'Supprimer',
7
+ edit: 'Éditer',
8
+ duplicate: 'Dupliquer',
9
+ sort: 'Trier'
10
+ }
@@ -0,0 +1,8 @@
1
+ import en from './en.js'
2
+ import fr from './fr.js'
3
+
4
+ /** @type {Record<string, import('./types.js').LocaleMessages>} */
5
+ export default {
6
+ en,
7
+ fr
8
+ }
@@ -0,0 +1,14 @@
1
+ export interface CompileOptionsMessages {
2
+ errorOneOf: string
3
+ errorRequired: string
4
+ }
5
+
6
+ export interface StateOptionsMessages {
7
+ addItem: string
8
+ delete: string
9
+ edit: string
10
+ duplicate: string
11
+ sort: string
12
+ }
13
+
14
+ export type LocaleMessages = CompileOptionsMessages & StateOptionsMessages
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './compile/index.js'
2
+ export * from './state/index.js'
3
+ export { default as i18n } from './i18n/index.js'