@contember/client-content-generator 1.3.0-alpha.3
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/dist/development/ContemberClientGenerator.cjs +45 -0
- package/dist/development/ContemberClientGenerator.cjs.map +1 -0
- package/dist/development/ContemberClientGenerator.js +45 -0
- package/dist/development/ContemberClientGenerator.js.map +1 -0
- package/dist/development/EntityTypeSchemaGenerator.cjs +171 -0
- package/dist/development/EntityTypeSchemaGenerator.cjs.map +1 -0
- package/dist/development/EntityTypeSchemaGenerator.js +171 -0
- package/dist/development/EntityTypeSchemaGenerator.js.map +1 -0
- package/dist/development/EnumTypeSchemaGenerator.cjs +14 -0
- package/dist/development/EnumTypeSchemaGenerator.cjs.map +1 -0
- package/dist/development/EnumTypeSchemaGenerator.js +14 -0
- package/dist/development/EnumTypeSchemaGenerator.js.map +1 -0
- package/dist/development/NameSchemaGenerator.cjs +38 -0
- package/dist/development/NameSchemaGenerator.cjs.map +1 -0
- package/dist/development/NameSchemaGenerator.js +38 -0
- package/dist/development/NameSchemaGenerator.js.map +1 -0
- package/dist/development/generate.cjs +41 -0
- package/dist/development/generate.cjs.map +1 -0
- package/dist/development/generate.js +23 -0
- package/dist/development/generate.js.map +1 -0
- package/dist/development/index.cjs +11 -0
- package/dist/development/index.cjs.map +1 -0
- package/dist/development/index.js +11 -0
- package/dist/development/index.js.map +1 -0
- package/dist/production/ContemberClientGenerator.cjs +45 -0
- package/dist/production/ContemberClientGenerator.cjs.map +1 -0
- package/dist/production/ContemberClientGenerator.js +45 -0
- package/dist/production/ContemberClientGenerator.js.map +1 -0
- package/dist/production/EntityTypeSchemaGenerator.cjs +171 -0
- package/dist/production/EntityTypeSchemaGenerator.cjs.map +1 -0
- package/dist/production/EntityTypeSchemaGenerator.js +171 -0
- package/dist/production/EntityTypeSchemaGenerator.js.map +1 -0
- package/dist/production/EnumTypeSchemaGenerator.cjs +14 -0
- package/dist/production/EnumTypeSchemaGenerator.cjs.map +1 -0
- package/dist/production/EnumTypeSchemaGenerator.js +14 -0
- package/dist/production/EnumTypeSchemaGenerator.js.map +1 -0
- package/dist/production/NameSchemaGenerator.cjs +38 -0
- package/dist/production/NameSchemaGenerator.cjs.map +1 -0
- package/dist/production/NameSchemaGenerator.js +38 -0
- package/dist/production/NameSchemaGenerator.js.map +1 -0
- package/dist/production/generate.cjs +41 -0
- package/dist/production/generate.cjs.map +1 -0
- package/dist/production/generate.js +23 -0
- package/dist/production/generate.js.map +1 -0
- package/dist/production/index.cjs +11 -0
- package/dist/production/index.cjs.map +1 -0
- package/dist/production/index.js +11 -0
- package/dist/production/index.js.map +1 -0
- package/dist/types/ContemberClientGenerator.d.ts +12 -0
- package/dist/types/ContemberClientGenerator.d.ts.map +1 -0
- package/dist/types/EntityTypeSchemaGenerator.d.ts +8 -0
- package/dist/types/EntityTypeSchemaGenerator.d.ts.map +1 -0
- package/dist/types/EnumTypeSchemaGenerator.d.ts +5 -0
- package/dist/types/EnumTypeSchemaGenerator.d.ts.map +1 -0
- package/dist/types/NameSchemaGenerator.d.ts +6 -0
- package/dist/types/NameSchemaGenerator.d.ts.map +1 -0
- package/dist/types/generate.d.ts +2 -0
- package/dist/types/generate.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +55 -0
- package/src/ContemberClientGenerator.ts +48 -0
- package/src/EntityTypeSchemaGenerator.ts +182 -0
- package/src/EnumTypeSchemaGenerator.ts +12 -0
- package/src/NameSchemaGenerator.ts +41 -0
- package/src/generate.ts +26 -0
- package/src/index.ts +4 -0
- package/src/tsconfig.json +10 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Model } from '@contember/schema'
|
|
2
|
+
import { acceptEveryFieldVisitor, acceptFieldVisitor } from '@contember/schema-utils'
|
|
3
|
+
|
|
4
|
+
export class EntityTypeSchemaGenerator {
|
|
5
|
+
generate(model: Model.Schema): string {
|
|
6
|
+
let code = ''
|
|
7
|
+
for (const enumName of Object.keys(model.enums)) {
|
|
8
|
+
code += `import type { ${enumName} } from './enums'\n`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
code += `
|
|
12
|
+
export type JSONPrimitive = string | number | boolean | null
|
|
13
|
+
export type JSONValue = JSONPrimitive | JSONObject | JSONArray
|
|
14
|
+
export type JSONObject = { readonly [K in string]?: JSONValue }
|
|
15
|
+
export type JSONArray = readonly JSONValue[]
|
|
16
|
+
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
for (const entity of Object.values(model.entities)) {
|
|
20
|
+
code += this.generateTypeEntityCode(model, entity)
|
|
21
|
+
}
|
|
22
|
+
code += '\n'
|
|
23
|
+
code += `export type ContemberClientEntities = {\n`
|
|
24
|
+
for (const entity of Object.values(model.entities)) {
|
|
25
|
+
code += `\t${entity.name}: ${entity.name}\n`
|
|
26
|
+
}
|
|
27
|
+
code += '}\n\n'
|
|
28
|
+
code += `export type ContemberClientSchema = {\n`
|
|
29
|
+
code += '\tentities: ContemberClientEntities\n'
|
|
30
|
+
code += '}\n'
|
|
31
|
+
return code
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private generateTypeEntityCode(model: Model.Schema, entity: Model.Entity): string {
|
|
35
|
+
let code = `export type ${entity.name} = {\n`
|
|
36
|
+
code += '\tname: \'' + entity.name + '\'\n'
|
|
37
|
+
code += '\tunique:\n'
|
|
38
|
+
code += this.formatUniqueFields(model, entity)
|
|
39
|
+
let columnsCode = ''
|
|
40
|
+
let hasOneCode = ''
|
|
41
|
+
let hasManyCode = ''
|
|
42
|
+
acceptEveryFieldVisitor(model, entity, {
|
|
43
|
+
visitHasMany: ctx => {
|
|
44
|
+
hasManyCode += `\t\t${ctx.relation.name}: ${ctx.targetEntity.name}\n`
|
|
45
|
+
},
|
|
46
|
+
visitHasOne: ctx => {
|
|
47
|
+
hasOneCode += `\t\t${ctx.relation.name}: ${ctx.targetEntity.name}\n`
|
|
48
|
+
},
|
|
49
|
+
visitColumn: ctx => {
|
|
50
|
+
columnsCode += `\t\t${ctx.column.name}: ${columnToTsType(ctx.column)}${ctx.column.nullable ? ` | null` : ''}\n`
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
code += '\tcolumns: {\n'
|
|
55
|
+
code += columnsCode
|
|
56
|
+
code += '\t}\n'
|
|
57
|
+
code += '\thasOne: {\n'
|
|
58
|
+
code += hasOneCode
|
|
59
|
+
code += '\t}\n'
|
|
60
|
+
code += '\thasMany: {\n'
|
|
61
|
+
code += hasManyCode
|
|
62
|
+
code += '\t}\n'
|
|
63
|
+
code += '\thasManyBy: {\n'
|
|
64
|
+
code += this.formatReducedFields(model, entity)
|
|
65
|
+
code += '\t}\n'
|
|
66
|
+
code += '}\n'
|
|
67
|
+
return code
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private formatReducedFields(model: Model.Schema, entity: Model.Entity): string {
|
|
71
|
+
let code = ''
|
|
72
|
+
acceptEveryFieldVisitor(model, entity, {
|
|
73
|
+
visitOneHasMany: ({ entity, relation, targetEntity, targetRelation }) => {
|
|
74
|
+
if (!targetRelation) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
const uniqueConstraints = getFieldsForUniqueWhere(model, targetEntity)
|
|
78
|
+
const composedUnique = uniqueConstraints
|
|
79
|
+
.filter(fields => fields.length === 2) //todo support all uniques
|
|
80
|
+
.filter(fields => fields.includes(targetRelation.name))
|
|
81
|
+
.map(fields => fields.filter(it => it !== targetRelation.name))
|
|
82
|
+
.map(fields => fields[0])
|
|
83
|
+
const singleUnique = uniqueConstraints
|
|
84
|
+
.filter(fields => fields.length === 1 && fields[0] !== targetEntity.primary)
|
|
85
|
+
.map(fields => fields[0])
|
|
86
|
+
.filter(it => it !== targetRelation.name)
|
|
87
|
+
|
|
88
|
+
;[...composedUnique, ...singleUnique].forEach(fieldName => {
|
|
89
|
+
const capitalizeFirstLetter = (value: string) => {
|
|
90
|
+
return value.charAt(0).toUpperCase() + value.slice(1)
|
|
91
|
+
}
|
|
92
|
+
const name = `${relation.name}By${capitalizeFirstLetter(fieldName)}`
|
|
93
|
+
|
|
94
|
+
const targetUnique = targetEntity.fields[fieldName]
|
|
95
|
+
|
|
96
|
+
code += `\t\t${name}: { entity: ${targetEntity.name}; by: {${fieldName}: ${uniqueType(model, targetEntity, targetUnique)}} }\n`
|
|
97
|
+
|
|
98
|
+
})
|
|
99
|
+
},
|
|
100
|
+
visitColumn: () => {
|
|
101
|
+
},
|
|
102
|
+
visitManyHasManyInverse: () => {
|
|
103
|
+
},
|
|
104
|
+
visitManyHasManyOwning: () => {
|
|
105
|
+
},
|
|
106
|
+
visitManyHasOne: () => {
|
|
107
|
+
},
|
|
108
|
+
visitOneHasOneInverse: () => {
|
|
109
|
+
},
|
|
110
|
+
visitOneHasOneOwning: () => {
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
return code
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private formatUniqueFields(model: Model.Schema, entity: Model.Entity): string {
|
|
117
|
+
const fields = getFieldsForUniqueWhere(model, entity)
|
|
118
|
+
let code = ''
|
|
119
|
+
for (const field of fields) {
|
|
120
|
+
code += '\t\t| { '
|
|
121
|
+
code += field.map(it => `${it}: ${uniqueType(model, entity, entity.fields[it])}`).join(', ')
|
|
122
|
+
code += ' }\n'
|
|
123
|
+
}
|
|
124
|
+
return code
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
const uniqueType = (model: Model.Schema, entity: Model.Entity, field: Model.AnyField): string => {
|
|
130
|
+
return acceptFieldVisitor(model, entity, field, {
|
|
131
|
+
visitColumn: ctx => {
|
|
132
|
+
return columnToTsType(ctx.column)
|
|
133
|
+
},
|
|
134
|
+
visitRelation: ctx => {
|
|
135
|
+
return ctx.targetEntity.name + `['unique']`
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
const columnToTsType = (column: Model.AnyColumn): string => {
|
|
142
|
+
switch (column.type) {
|
|
143
|
+
case Model.ColumnType.Enum:
|
|
144
|
+
return column.columnType
|
|
145
|
+
case Model.ColumnType.String:
|
|
146
|
+
return 'string'
|
|
147
|
+
case Model.ColumnType.Int:
|
|
148
|
+
return 'number'
|
|
149
|
+
case Model.ColumnType.Double:
|
|
150
|
+
return 'number'
|
|
151
|
+
case Model.ColumnType.Bool:
|
|
152
|
+
return 'boolean'
|
|
153
|
+
case Model.ColumnType.DateTime:
|
|
154
|
+
return 'string'
|
|
155
|
+
case Model.ColumnType.Date:
|
|
156
|
+
return 'string'
|
|
157
|
+
case Model.ColumnType.Json:
|
|
158
|
+
return 'JSONValue'
|
|
159
|
+
case Model.ColumnType.Uuid:
|
|
160
|
+
return 'string'
|
|
161
|
+
default:
|
|
162
|
+
((_: never) => {
|
|
163
|
+
throw new Error(`Unknown type ${_}`)
|
|
164
|
+
})(column.type)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const getFieldsForUniqueWhere = (schema: Model.Schema, entity: Model.Entity): readonly (readonly string[])[] => {
|
|
169
|
+
const relations = Object.values(
|
|
170
|
+
acceptEveryFieldVisitor<undefined | [string]>(schema, entity, {
|
|
171
|
+
visitColumn: () => undefined,
|
|
172
|
+
visitManyHasManyInverse: () => undefined,
|
|
173
|
+
visitManyHasManyOwning: () => undefined,
|
|
174
|
+
visitOneHasMany: ({ relation }) => [relation.name],
|
|
175
|
+
visitManyHasOne: () => undefined,
|
|
176
|
+
visitOneHasOneInverse: ({ relation }) => [relation.name],
|
|
177
|
+
visitOneHasOneOwning: ({ relation }) => [relation.name],
|
|
178
|
+
}),
|
|
179
|
+
).filter((it): it is [string] => !!it)
|
|
180
|
+
|
|
181
|
+
return [[entity.primary], ...Object.values(entity.unique).map(it => it.fields), ...relations]
|
|
182
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Model } from '@contember/schema'
|
|
2
|
+
|
|
3
|
+
export class EnumTypeSchemaGenerator {
|
|
4
|
+
generate(model: Model.Schema): string {
|
|
5
|
+
let code = ''
|
|
6
|
+
for (const [enumName, values] of Object.entries(model.enums)) {
|
|
7
|
+
code += `export type ${enumName} = ${values.map(it => '\n\t | ' + JSON.stringify(it)).join('')}\n`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return code || 'export {}\n'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SchemaNames, SchemaEntityNames } from '@contember/client-content'
|
|
2
|
+
import { Model } from '@contember/schema'
|
|
3
|
+
import { acceptEveryFieldVisitor } from '@contember/schema-utils'
|
|
4
|
+
|
|
5
|
+
export class NameSchemaGenerator {
|
|
6
|
+
generate(model: Model.Schema): SchemaNames {
|
|
7
|
+
return {
|
|
8
|
+
entities: Object.fromEntries(
|
|
9
|
+
Object.values(model.entities).map(entity => {
|
|
10
|
+
const fields: Record<string, SchemaEntityNames<any>['fields'][string]> = {}
|
|
11
|
+
const scalars: string[] = []
|
|
12
|
+
|
|
13
|
+
acceptEveryFieldVisitor(model, entity, {
|
|
14
|
+
visitHasOne: ctx => {
|
|
15
|
+
fields[ctx.relation.name] = {
|
|
16
|
+
type: 'one',
|
|
17
|
+
entity: ctx.targetEntity.name,
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
visitHasMany: ctx => {
|
|
21
|
+
fields[ctx.relation.name] = {
|
|
22
|
+
type: 'many',
|
|
23
|
+
entity: ctx.targetEntity.name,
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
visitColumn: ctx => {
|
|
27
|
+
scalars.push(ctx.column.name)
|
|
28
|
+
fields[ctx.column.name] = {
|
|
29
|
+
type: 'column',
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return [entity.name, { name: entity.name, fields, scalars }]
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
package/src/generate.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises'
|
|
2
|
+
import { resolve, join } from 'node:path'
|
|
3
|
+
import { ContemberClientGenerator } from './ContemberClientGenerator';
|
|
4
|
+
|
|
5
|
+
(async () => {
|
|
6
|
+
const schemaPath = process.argv[2]
|
|
7
|
+
const outDir = process.argv[3]
|
|
8
|
+
|
|
9
|
+
if (!schemaPath || !outDir) {
|
|
10
|
+
console.error(`Usage: yarn contember-client-generator <schema.json> <out-dir>`)
|
|
11
|
+
process.exit(1)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const source = JSON.parse(await fs.readFile(resolve(process.cwd(), process.argv[2]), 'utf8'))
|
|
15
|
+
const dir = resolve(process.cwd(), process.argv[3])
|
|
16
|
+
const generator = new ContemberClientGenerator()
|
|
17
|
+
const result = generator.generate(source.model)
|
|
18
|
+
await fs.mkdir(dir, { recursive: true })
|
|
19
|
+
for (const [name, content] of Object.entries(result)) {
|
|
20
|
+
await fs.writeFile(join(dir, name), content, 'utf8')
|
|
21
|
+
}
|
|
22
|
+
})().catch(e => {
|
|
23
|
+
console.error(e)
|
|
24
|
+
process.exit(1)
|
|
25
|
+
})
|
|
26
|
+
|
package/src/index.ts
ADDED