@platformatic/sql-json-schema-mapper 0.19.0 → 0.19.2

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.
package/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  'use strict'
2
2
 
3
+ const CodeBlockWriter = require('code-block-writer').default
4
+ const { property } = require('safe-identifier')
5
+
3
6
  function mapSQLTypeToOpenAPIType (sqlType) {
4
7
  // TODO support more types
5
8
  /* istanbul ignore next */
@@ -91,7 +94,7 @@ function mapSQLEntityToJSONSchema (entity, ignore = {}) {
91
94
  properties[field.camelcase].enum = field.enum
92
95
  }
93
96
  }
94
- return {
97
+ const res = {
95
98
  $id: entity.name,
96
99
  title: entity.name,
97
100
  description: `A ${entity.name}`,
@@ -99,6 +102,104 @@ function mapSQLEntityToJSONSchema (entity, ignore = {}) {
99
102
  properties,
100
103
  required
101
104
  }
105
+
106
+ return res
107
+ }
108
+
109
+ function mapOpenAPItoTypes (obj, opts = {}) {
110
+ let { writer, addedProps } = opts
111
+ addedProps ??= new Set()
112
+ writer ??= new CodeBlockWriter()
113
+ const { title, description, properties, required, additionalProperties } = obj
114
+ writer.write('/**').newLine()
115
+ writer.write(` * ${title}`).newLine()
116
+ writer.write(` * ${description}`).newLine()
117
+ writer.write(' */').newLine()
118
+ writer.write(`declare interface ${title}`).block(() => {
119
+ renderProperties(writer, addedProps, properties, additionalProperties, required)
120
+ })
121
+ return writer.toString()
122
+ }
123
+
124
+ function renderProperties (writer, addedProps, properties = {}, additionalProperties, required = []) {
125
+ for (const name of Object.keys(properties)) {
126
+ const { type, nullable, items } = properties[name]
127
+ addedProps.add(name)
128
+ if (required.indexOf(name) !== -1) {
129
+ writer.write(property(null, name))
130
+ } else {
131
+ writer.write(property(null, name))
132
+ writer.write('?')
133
+ }
134
+
135
+ let types
136
+ if (Array.isArray(type)) {
137
+ types = type
138
+ } else {
139
+ types = [type]
140
+ }
141
+
142
+ if (nullable && types.indexOf('null') === -1) {
143
+ types.push('null')
144
+ }
145
+
146
+ let first = true
147
+ writer.write(': ')
148
+ for (const type of types) {
149
+ if (!first) {
150
+ writer.write(' | ')
151
+ }
152
+ first = false
153
+ if (type === 'null') {
154
+ writer.write('null')
155
+ } else if (type === 'array') {
156
+ switch (items.type) {
157
+ case 'object':
158
+ writer.inlineBlock(() => {
159
+ const current = items
160
+ renderProperties(writer, addedProps, current.properties, current.additionalProperties, current.required)
161
+ })
162
+ writer.write('[]')
163
+ break
164
+ // TODO support arrays in arrays
165
+ default:
166
+ writer.write(`${JSONSchemaToTsType(items.type)}[]`)
167
+ }
168
+ } else if (type === 'object') {
169
+ writer.inlineBlock(() => {
170
+ const current = properties[name]
171
+ renderProperties(writer, addedProps, current.properties, current.additionalProperties, current.required)
172
+ })
173
+ } else {
174
+ writer.write(JSONSchemaToTsType(type))
175
+ }
176
+ }
177
+
178
+ writer.write(';')
179
+ writer.newLine()
180
+ }
181
+
182
+ if (additionalProperties) {
183
+ writer.write('[name: string]: any;')
184
+ }
185
+ }
186
+
187
+ function JSONSchemaToTsType (type) {
188
+ switch (type) {
189
+ case 'string':
190
+ return 'string'
191
+ case 'integer':
192
+ return 'number'
193
+ case 'number':
194
+ return 'number'
195
+ /* istanbul ignore next */
196
+ case 'boolean':
197
+ return 'boolean'
198
+ // TODO what other types should we support here?
199
+ /* istanbul ignore next */
200
+ default:
201
+ return 'any'
202
+ }
102
203
  }
103
204
 
104
- module.exports = { mapSQLTypeToOpenAPIType, mapSQLEntityToJSONSchema }
205
+ module.exports = { mapSQLTypeToOpenAPIType, mapSQLEntityToJSONSchema, mapOpenAPItoTypes }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-json-schema-mapper",
3
- "version": "0.19.0",
3
+ "version": "0.19.2",
4
4
  "description": "Map SQL entity to JSON schema",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -18,7 +18,12 @@
18
18
  "snazzy": "^9.0.0",
19
19
  "standard": "^17.0.0",
20
20
  "tap": "^16.3.4",
21
- "@platformatic/sql-mapper": "0.19.0"
21
+ "@platformatic/sql-mapper": "0.19.2"
22
+ },
23
+ "dependencies": {
24
+ "code-block-writer": "^12.0.0",
25
+ "dtsgenerator": "^3.18.0",
26
+ "safe-identifier": "^0.4.2"
22
27
  },
23
28
  "scripts": {
24
29
  "lint": "standard | snazzy",
@@ -0,0 +1,189 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const dtsgenerator = require('dtsgenerator')
5
+ const { mapOpenAPItoTypes } = require('..')
6
+
7
+ let structuredClone = globalThis.structuredClone
8
+ if (structuredClone === undefined) {
9
+ structuredClone = (obj) => JSON.parse(JSON.stringify(obj))
10
+ }
11
+
12
+ function referenceTest (name, obj, opts = {}) {
13
+ const { only } = opts
14
+ test(name, { only }, async t => {
15
+ const reference = await dtsgenerator.default({ contents: [dtsgenerator.parseSchema(structuredClone(obj))] })
16
+ const cloned = structuredClone(obj)
17
+ const ours = mapOpenAPItoTypes(cloned)
18
+ t.not(cloned, obj)
19
+ t.same(cloned, obj)
20
+ t.same(ours.trim(), reference.trim())
21
+ })
22
+ }
23
+
24
+ referenceTest('p1', {
25
+ id: 'Page',
26
+ title: 'Page',
27
+ description: 'A Page',
28
+ type: 'object',
29
+ properties: {
30
+ id: {
31
+ type: 'integer'
32
+ },
33
+ title: {
34
+ type: 'string'
35
+ },
36
+ metadata: {
37
+ type: 'object',
38
+ additionalProperties: true,
39
+ nullable: true
40
+ },
41
+ section: {
42
+ type: 'number',
43
+ nullable: true
44
+ },
45
+ description: {
46
+ type: 'string',
47
+ nullable: true
48
+ }
49
+ },
50
+ required: [
51
+ 'title'
52
+ ]
53
+ })
54
+
55
+ referenceTest('p2', {
56
+ id: 'Page',
57
+ title: 'Page',
58
+ description: 'A Page',
59
+ type: 'object',
60
+ properties: {
61
+ id: {
62
+ type: 'integer'
63
+ },
64
+ metadata: {
65
+ type: 'object',
66
+ additionalProperties: true,
67
+ nullable: true
68
+ },
69
+ section: {
70
+ type: 'number',
71
+ nullable: true
72
+ },
73
+ description: {
74
+ type: 'string',
75
+ nullable: true
76
+ }
77
+ },
78
+ required: []
79
+ })
80
+
81
+ referenceTest('p3', {
82
+ id: 'GeneratedTest',
83
+ title: 'GeneratedTest',
84
+ description: 'A GeneratedTest',
85
+ type: 'object',
86
+ properties: {
87
+ id: {
88
+ type: 'integer'
89
+ },
90
+ test: {
91
+ type: 'integer',
92
+ nullable: true
93
+ },
94
+ testStored: {
95
+ type: 'integer',
96
+ nullable: true,
97
+ readOnly: true
98
+ },
99
+ testVirtual: {
100
+ type: 'integer',
101
+ nullable: true,
102
+ readOnly: true
103
+ }
104
+ },
105
+ required: []
106
+ })
107
+
108
+ referenceTest('multiple types', {
109
+ id: 'Page',
110
+ title: 'Page',
111
+ description: 'A Page',
112
+ type: 'object',
113
+ properties: {
114
+ id: {
115
+ type: ['integer', 'string']
116
+ },
117
+ title: {
118
+ type: 'string'
119
+ },
120
+ metadata: {
121
+ type: 'object',
122
+ additionalProperties: true,
123
+ nullable: true
124
+ },
125
+ section: {
126
+ type: ['number', 'null']
127
+ },
128
+ description: {
129
+ type: 'string',
130
+ nullable: true
131
+ }
132
+ },
133
+ required: [
134
+ 'title'
135
+ ]
136
+ })
137
+
138
+ referenceTest('arrays', {
139
+ id: 'Page',
140
+ title: 'Page',
141
+ description: 'A Page',
142
+ type: 'object',
143
+ properties: {
144
+ id: {
145
+ type: 'integer'
146
+ },
147
+ title: {
148
+ type: 'string'
149
+ },
150
+ tags: {
151
+ type: 'array',
152
+ items: {
153
+ type: 'string'
154
+ }
155
+ }
156
+ },
157
+ required: [
158
+ 'title'
159
+ ]
160
+ })
161
+
162
+ referenceTest('objects in arrays', {
163
+ id: 'Page',
164
+ title: 'Page',
165
+ description: 'A Page',
166
+ type: 'object',
167
+ properties: {
168
+ id: {
169
+ type: 'integer'
170
+ },
171
+ title: {
172
+ type: 'string'
173
+ },
174
+ tags: {
175
+ type: 'array',
176
+ items: {
177
+ type: 'object',
178
+ properties: {
179
+ foo: {
180
+ type: 'string'
181
+ }
182
+ }
183
+ }
184
+ }
185
+ },
186
+ required: [
187
+ 'title'
188
+ ]
189
+ })