@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.
Files changed (69) hide show
  1. package/dist/development/ContemberClientGenerator.cjs +45 -0
  2. package/dist/development/ContemberClientGenerator.cjs.map +1 -0
  3. package/dist/development/ContemberClientGenerator.js +45 -0
  4. package/dist/development/ContemberClientGenerator.js.map +1 -0
  5. package/dist/development/EntityTypeSchemaGenerator.cjs +171 -0
  6. package/dist/development/EntityTypeSchemaGenerator.cjs.map +1 -0
  7. package/dist/development/EntityTypeSchemaGenerator.js +171 -0
  8. package/dist/development/EntityTypeSchemaGenerator.js.map +1 -0
  9. package/dist/development/EnumTypeSchemaGenerator.cjs +14 -0
  10. package/dist/development/EnumTypeSchemaGenerator.cjs.map +1 -0
  11. package/dist/development/EnumTypeSchemaGenerator.js +14 -0
  12. package/dist/development/EnumTypeSchemaGenerator.js.map +1 -0
  13. package/dist/development/NameSchemaGenerator.cjs +38 -0
  14. package/dist/development/NameSchemaGenerator.cjs.map +1 -0
  15. package/dist/development/NameSchemaGenerator.js +38 -0
  16. package/dist/development/NameSchemaGenerator.js.map +1 -0
  17. package/dist/development/generate.cjs +41 -0
  18. package/dist/development/generate.cjs.map +1 -0
  19. package/dist/development/generate.js +23 -0
  20. package/dist/development/generate.js.map +1 -0
  21. package/dist/development/index.cjs +11 -0
  22. package/dist/development/index.cjs.map +1 -0
  23. package/dist/development/index.js +11 -0
  24. package/dist/development/index.js.map +1 -0
  25. package/dist/production/ContemberClientGenerator.cjs +45 -0
  26. package/dist/production/ContemberClientGenerator.cjs.map +1 -0
  27. package/dist/production/ContemberClientGenerator.js +45 -0
  28. package/dist/production/ContemberClientGenerator.js.map +1 -0
  29. package/dist/production/EntityTypeSchemaGenerator.cjs +171 -0
  30. package/dist/production/EntityTypeSchemaGenerator.cjs.map +1 -0
  31. package/dist/production/EntityTypeSchemaGenerator.js +171 -0
  32. package/dist/production/EntityTypeSchemaGenerator.js.map +1 -0
  33. package/dist/production/EnumTypeSchemaGenerator.cjs +14 -0
  34. package/dist/production/EnumTypeSchemaGenerator.cjs.map +1 -0
  35. package/dist/production/EnumTypeSchemaGenerator.js +14 -0
  36. package/dist/production/EnumTypeSchemaGenerator.js.map +1 -0
  37. package/dist/production/NameSchemaGenerator.cjs +38 -0
  38. package/dist/production/NameSchemaGenerator.cjs.map +1 -0
  39. package/dist/production/NameSchemaGenerator.js +38 -0
  40. package/dist/production/NameSchemaGenerator.js.map +1 -0
  41. package/dist/production/generate.cjs +41 -0
  42. package/dist/production/generate.cjs.map +1 -0
  43. package/dist/production/generate.js +23 -0
  44. package/dist/production/generate.js.map +1 -0
  45. package/dist/production/index.cjs +11 -0
  46. package/dist/production/index.cjs.map +1 -0
  47. package/dist/production/index.js +11 -0
  48. package/dist/production/index.js.map +1 -0
  49. package/dist/types/ContemberClientGenerator.d.ts +12 -0
  50. package/dist/types/ContemberClientGenerator.d.ts.map +1 -0
  51. package/dist/types/EntityTypeSchemaGenerator.d.ts +8 -0
  52. package/dist/types/EntityTypeSchemaGenerator.d.ts.map +1 -0
  53. package/dist/types/EnumTypeSchemaGenerator.d.ts +5 -0
  54. package/dist/types/EnumTypeSchemaGenerator.d.ts.map +1 -0
  55. package/dist/types/NameSchemaGenerator.d.ts +6 -0
  56. package/dist/types/NameSchemaGenerator.d.ts.map +1 -0
  57. package/dist/types/generate.d.ts +2 -0
  58. package/dist/types/generate.d.ts.map +1 -0
  59. package/dist/types/index.d.ts +5 -0
  60. package/dist/types/index.d.ts.map +1 -0
  61. package/dist/types/tsconfig.tsbuildinfo +1 -0
  62. package/package.json +55 -0
  63. package/src/ContemberClientGenerator.ts +48 -0
  64. package/src/EntityTypeSchemaGenerator.ts +182 -0
  65. package/src/EnumTypeSchemaGenerator.ts +12 -0
  66. package/src/NameSchemaGenerator.ts +41 -0
  67. package/src/generate.ts +26 -0
  68. package/src/index.ts +4 -0
  69. 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
+
@@ -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
@@ -0,0 +1,4 @@
1
+ export * from './ContemberClientGenerator'
2
+ export * from './EntityTypeSchemaGenerator'
3
+ export * from './EnumTypeSchemaGenerator'
4
+ export * from './NameSchemaGenerator'
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../../tsconfig.settings.json",
3
+ "compilerOptions": {
4
+ "outDir": "../dist/types",
5
+ "types": ["node"]
6
+ },
7
+ "references": [
8
+ { "path": "../../client-content/src" }
9
+ ]
10
+ }