@contember/bindx-generator 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 (46) hide show
  1. package/README.md +258 -0
  2. package/dist/BindxGenerator.d.ts +41 -0
  3. package/dist/BindxGenerator.d.ts.map +1 -0
  4. package/dist/BindxGenerator.js +124 -0
  5. package/dist/BindxGenerator.js.map +1 -0
  6. package/dist/EntityTypeSchemaGenerator.d.ts +13 -0
  7. package/dist/EntityTypeSchemaGenerator.d.ts.map +1 -0
  8. package/dist/EntityTypeSchemaGenerator.js +64 -0
  9. package/dist/EntityTypeSchemaGenerator.js.map +1 -0
  10. package/dist/EnumTypeSchemaGenerator.d.ts +9 -0
  11. package/dist/EnumTypeSchemaGenerator.d.ts.map +1 -0
  12. package/dist/EnumTypeSchemaGenerator.js +19 -0
  13. package/dist/EnumTypeSchemaGenerator.js.map +1 -0
  14. package/dist/NameSchemaGenerator.d.ts +34 -0
  15. package/dist/NameSchemaGenerator.d.ts.map +1 -0
  16. package/dist/NameSchemaGenerator.js +39 -0
  17. package/dist/NameSchemaGenerator.js.map +1 -0
  18. package/dist/RoleNameSchemaGenerator.d.ts +24 -0
  19. package/dist/RoleNameSchemaGenerator.d.ts.map +1 -0
  20. package/dist/RoleNameSchemaGenerator.js +194 -0
  21. package/dist/RoleNameSchemaGenerator.js.map +1 -0
  22. package/dist/RoleSchemaGenerator.d.ts +41 -0
  23. package/dist/RoleSchemaGenerator.d.ts.map +1 -0
  24. package/dist/RoleSchemaGenerator.js +169 -0
  25. package/dist/RoleSchemaGenerator.js.map +1 -0
  26. package/dist/generate.d.ts +12 -0
  27. package/dist/generate.d.ts.map +1 -0
  28. package/dist/generate.js +45 -0
  29. package/dist/generate.js.map +1 -0
  30. package/dist/index.d.ts +17 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +14 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/utils.d.ts +17 -0
  35. package/dist/utils.d.ts.map +1 -0
  36. package/dist/utils.js +51 -0
  37. package/dist/utils.js.map +1 -0
  38. package/package.json +31 -0
  39. package/src/BindxGenerator.ts +154 -0
  40. package/src/EntityTypeSchemaGenerator.ts +77 -0
  41. package/src/EnumTypeSchemaGenerator.ts +24 -0
  42. package/src/NameSchemaGenerator.ts +66 -0
  43. package/src/RoleSchemaGenerator.ts +219 -0
  44. package/src/generate.ts +55 -0
  45. package/src/index.ts +19 -0
  46. package/src/utils.ts +54 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Main bindx schema generator
3
+ *
4
+ * Generates TypeScript schema files from Contember Model.Schema.
5
+ */
6
+
7
+ import { Model, Acl } from '@contember/schema'
8
+ import { EntityTypeSchemaGenerator } from './EntityTypeSchemaGenerator'
9
+ import { EnumTypeSchemaGenerator } from './EnumTypeSchemaGenerator'
10
+ import { NameSchemaGenerator } from './NameSchemaGenerator'
11
+ import { RoleSchemaGenerator, type RoleSchemaGeneratorOptions } from './RoleSchemaGenerator'
12
+
13
+ export interface BindxGeneratorOptions extends RoleSchemaGeneratorOptions {
14
+ // Reserved for future options
15
+ }
16
+
17
+ export interface GeneratedFiles {
18
+ 'entities.ts': string
19
+ 'names.ts'?: string
20
+ 'enums.ts': string
21
+ 'types.ts': string
22
+ 'schema.ts': string
23
+ 'index.ts': string
24
+ }
25
+
26
+ export class BindxGenerator {
27
+ private readonly entityTypeSchemaGenerator: EntityTypeSchemaGenerator
28
+ private readonly enumTypeSchemaGenerator: EnumTypeSchemaGenerator
29
+ private readonly nameSchemaGenerator: NameSchemaGenerator
30
+ private readonly roleSchemaGenerator: RoleSchemaGenerator
31
+
32
+ constructor(private readonly options: BindxGeneratorOptions = {}) {
33
+ this.entityTypeSchemaGenerator = new EntityTypeSchemaGenerator()
34
+ this.enumTypeSchemaGenerator = new EnumTypeSchemaGenerator()
35
+ this.nameSchemaGenerator = new NameSchemaGenerator()
36
+ this.roleSchemaGenerator = new RoleSchemaGenerator(options)
37
+ }
38
+
39
+ /**
40
+ * Generate schema files
41
+ */
42
+ generate(model: Model.Schema, acl?: Acl.Schema): GeneratedFiles {
43
+ const enumsCode = this.enumTypeSchemaGenerator.generate(model)
44
+ let entitiesCode = this.entityTypeSchemaGenerator.generate(model)
45
+ const namesSchema = this.nameSchemaGenerator.generate(model)
46
+
47
+ // Append per-role entity types if ACL is provided
48
+ if (acl) {
49
+ entitiesCode += '\n' + this.roleSchemaGenerator.generateRoleEntities(model, acl)
50
+ }
51
+
52
+ const namesCode = `import type { BindxSchemaNames } from './types'
53
+
54
+ export const schemaNames: BindxSchemaNames = ${JSON.stringify(namesSchema, null, '\t')}
55
+ `
56
+
57
+ const typesCode = this.generateTypesFile()
58
+ const schemaCode = acl
59
+ ? this.roleSchemaGenerator.generateSchemaFile(model, acl)
60
+ : this.generateSchemaFile(model)
61
+
62
+ const indexCode = `export * from './enums'
63
+ export * from './entities'
64
+ export * from './names'
65
+ export * from './types'
66
+ export * from './schema'
67
+ `
68
+
69
+ return {
70
+ 'entities.ts': entitiesCode,
71
+ 'names.ts': namesCode,
72
+ 'enums.ts': enumsCode,
73
+ 'types.ts': typesCode,
74
+ 'schema.ts': schemaCode,
75
+ 'index.ts': indexCode,
76
+ }
77
+ }
78
+
79
+ private generateSchemaFile(model: Model.Schema): string {
80
+ const entityNames = Object.values(model.entities).map(e => e.name).sort()
81
+
82
+ const imports = entityNames.join(', ')
83
+ const entries = entityNames
84
+ .map(name => `\t${name}: entityDef<${name}>('${name}', schemaDef),`)
85
+ .join('\n')
86
+
87
+ return `import { entityDef } from '@contember/bindx'
88
+ import { schemaNamesToDef } from '@contember/bindx-react'
89
+ import type { ${imports} } from './entities'
90
+ import { schemaNames } from './names'
91
+
92
+ const schemaDef = schemaNamesToDef(schemaNames)
93
+
94
+ export const schema = {
95
+ ${entries}
96
+ } as const
97
+ `
98
+ }
99
+
100
+ private generateTypesFile(): string {
101
+ return `/**
102
+ * Shared types for bindx schema
103
+ */
104
+
105
+ export interface BindxSchemaEntityNames {
106
+ readonly name: string
107
+ readonly scalars: readonly string[]
108
+ readonly fields: {
109
+ readonly [fieldName: string]:
110
+ | { readonly type: 'column'; readonly enumName?: string }
111
+ | { readonly type: 'one'; readonly entity: string }
112
+ | { readonly type: 'many'; readonly entity: string }
113
+ }
114
+ }
115
+
116
+ export interface BindxSchemaNames {
117
+ readonly entities: {
118
+ readonly [entityName: string]: BindxSchemaEntityNames
119
+ }
120
+ readonly enums: {
121
+ readonly [enumName: string]: readonly string[]
122
+ }
123
+ }
124
+ `
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Generate bindx schema files from Contember model
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * const files = generate(model)
134
+ * ```
135
+ */
136
+ export function generate(
137
+ model: Model.Schema,
138
+ aclOrOptions?: Acl.Schema | BindxGeneratorOptions,
139
+ options?: BindxGeneratorOptions,
140
+ ): GeneratedFiles {
141
+ // Support both generate(model, acl, options) and generate(model, options)
142
+ let acl: Acl.Schema | undefined
143
+ let opts: BindxGeneratorOptions | undefined
144
+
145
+ if (aclOrOptions && 'roles' in aclOrOptions) {
146
+ acl = aclOrOptions
147
+ opts = options
148
+ } else {
149
+ opts = aclOrOptions as BindxGeneratorOptions | undefined
150
+ }
151
+
152
+ const generator = new BindxGenerator(opts)
153
+ return generator.generate(model, acl)
154
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Entity type schema generator for bindx
3
+ * Generates TypeScript entity types from Contember model
4
+ *
5
+ * Output format is designed to work with bindx's type system,
6
+ * separating columns, hasOne, and hasMany for proper type inference.
7
+ */
8
+
9
+ import { Model } from '@contember/schema'
10
+ import { acceptEveryFieldVisitor } from '@contember/schema-utils'
11
+ import { columnToTsType, getEnumTypeName } from './utils'
12
+
13
+ export class EntityTypeSchemaGenerator {
14
+ generate(model: Model.Schema): string {
15
+ let code = ''
16
+
17
+ // Import enum types
18
+ for (const enumName of Object.keys(model.enums)) {
19
+ code += `import type { ${getEnumTypeName(enumName)} } from './enums'\n`
20
+ }
21
+
22
+ // Add JSON type definitions
23
+ code += `
24
+ export type JSONPrimitive = string | number | boolean | null
25
+ export type JSONValue = JSONPrimitive | JSONObject | JSONArray
26
+ export type JSONObject = { readonly [K in string]?: JSONValue }
27
+ export type JSONArray = readonly JSONValue[]
28
+
29
+ `
30
+
31
+ // Generate entity types
32
+ for (const entity of Object.values(model.entities)) {
33
+ code += this.generateEntityTypeCode(model, entity)
34
+ }
35
+
36
+ // Generate schema type
37
+ code += '\n'
38
+ code += `export interface BindxEntities {\n`
39
+ for (const entity of Object.values(model.entities)) {
40
+ code += `\t${entity.name}: ${entity.name}\n`
41
+ }
42
+ code += '}\n\n'
43
+
44
+ code += `export interface BindxSchema {\n`
45
+ code += '\tentities: BindxEntities\n'
46
+ code += '}\n'
47
+
48
+ return code
49
+ }
50
+
51
+ private generateEntityTypeCode(model: Model.Schema, entity: Model.Entity): string {
52
+ let code = `export interface ${entity.name} {\n`
53
+
54
+ let columnsCode = ''
55
+ let hasOneCode = ''
56
+ let hasManyCode = ''
57
+
58
+ acceptEveryFieldVisitor(model, entity, {
59
+ visitHasMany: ctx => {
60
+ hasManyCode += `\t\t${ctx.relation.name}: ${ctx.targetEntity.name}[]\n`
61
+ },
62
+ visitHasOne: ctx => {
63
+ hasOneCode += `\t\t${ctx.relation.name}: ${ctx.targetEntity.name}\n`
64
+ },
65
+ visitColumn: ctx => {
66
+ columnsCode += `\t\t${ctx.column.name}: ${columnToTsType(ctx.column)}${ctx.column.nullable ? ' | null' : ''}\n`
67
+ },
68
+ })
69
+
70
+ code += columnsCode
71
+ code += hasOneCode
72
+ code += hasManyCode
73
+ code += '}\n\n'
74
+
75
+ return code
76
+ }
77
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Enum type schema generator for bindx
3
+ * Generates TypeScript enum types from Contember model enums
4
+ */
5
+
6
+ import { Model } from '@contember/schema'
7
+ import { getEnumTypeName } from './utils'
8
+
9
+ export class EnumTypeSchemaGenerator {
10
+ generate(model: Model.Schema): string {
11
+ let code = ''
12
+
13
+ for (const [enumName, values] of Object.entries(model.enums)) {
14
+ code += `export type ${getEnumTypeName(enumName)} = ${values.map(v => `'${v}'`).join(' | ')}\n\n`
15
+ }
16
+
17
+ // Export enum values as const arrays for runtime use
18
+ for (const [enumName, values] of Object.entries(model.enums)) {
19
+ code += `export const ${enumName}Values = [${values.map(v => `'${v}'`).join(', ')}] as const\n`
20
+ }
21
+
22
+ return code
23
+ }
24
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Name schema generator for bindx
3
+ * Generates runtime schema names (JSON structure) for query building
4
+ */
5
+
6
+ import { Model } from '@contember/schema'
7
+ import { acceptEveryFieldVisitor } from '@contember/schema-utils'
8
+
9
+ export interface BindxSchemaEntityNames {
10
+ readonly name: string
11
+ readonly scalars: readonly string[]
12
+ readonly fields: {
13
+ readonly [fieldName: string]:
14
+ | { readonly type: 'column'; readonly columnType?: string; readonly enumName?: string }
15
+ | { readonly type: 'one'; readonly entity: string }
16
+ | { readonly type: 'many'; readonly entity: string }
17
+ }
18
+ }
19
+
20
+ export interface BindxSchemaNames {
21
+ readonly entities: {
22
+ readonly [entityName: string]: BindxSchemaEntityNames
23
+ }
24
+ readonly enums: {
25
+ readonly [enumName: string]: readonly string[]
26
+ }
27
+ }
28
+
29
+ export class NameSchemaGenerator {
30
+ generate(model: Model.Schema): BindxSchemaNames {
31
+ return {
32
+ entities: Object.fromEntries(
33
+ Object.values(model.entities).map(entity => {
34
+ const fields: Record<string, BindxSchemaEntityNames['fields'][string]> = {}
35
+ const scalars: string[] = []
36
+
37
+ acceptEveryFieldVisitor(model, entity, {
38
+ visitHasOne: ctx => {
39
+ fields[ctx.relation.name] = {
40
+ type: 'one',
41
+ entity: ctx.targetEntity.name,
42
+ }
43
+ },
44
+ visitHasMany: ctx => {
45
+ fields[ctx.relation.name] = {
46
+ type: 'many',
47
+ entity: ctx.targetEntity.name,
48
+ }
49
+ },
50
+ visitColumn: ctx => {
51
+ scalars.push(ctx.column.name)
52
+ fields[ctx.column.name] = ctx.column.type === Model.ColumnType.Enum
53
+ ? { type: 'column', columnType: ctx.column.columnType, enumName: ctx.column.columnType }
54
+ : { type: 'column', columnType: ctx.column.columnType }
55
+ },
56
+ })
57
+
58
+ return [entity.name, { name: entity.name, fields, scalars }]
59
+ }),
60
+ ),
61
+ enums: Object.fromEntries(
62
+ Object.entries(model.enums).map(([name, values]) => [name, values]),
63
+ ),
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Role-aware schema generator for bindx.
3
+ *
4
+ * Generates per-role entity interfaces filtered by Contember ACL read permissions.
5
+ * For each role, only fields with read access (true or predicate) are included.
6
+ */
7
+
8
+ import { Model, Acl } from '@contember/schema'
9
+ import { acceptEveryFieldVisitor } from '@contember/schema-utils'
10
+ import { columnToTsType, getEnumTypeName } from './utils'
11
+
12
+ export interface RoleSchemaGeneratorOptions {
13
+ /**
14
+ * Whether to treat predicate-based permissions as allowed.
15
+ * When true, any non-false permission allows access.
16
+ * When false, only explicit `true` permissions are allowed.
17
+ * Default: true
18
+ */
19
+ allowPredicateAccess?: boolean
20
+ }
21
+
22
+ interface RoleFieldAccess {
23
+ /** role name → set of readable field names */
24
+ [role: string]: Set<string>
25
+ }
26
+
27
+ export class RoleSchemaGenerator {
28
+ private readonly allowPredicateAccess: boolean
29
+
30
+ constructor(options: RoleSchemaGeneratorOptions = {}) {
31
+ this.allowPredicateAccess = options.allowPredicateAccess ?? true
32
+ }
33
+
34
+ /**
35
+ * Generates per-role entity types + role map types.
36
+ * Returns code to be appended to entities.ts.
37
+ */
38
+ generateRoleEntities(model: Model.Schema, acl: Acl.Schema): string {
39
+ const roles = this.resolveRoles(acl)
40
+ const roleNames = Object.keys(roles)
41
+
42
+ let code = ''
43
+
44
+ // Generate per-role entity interfaces
45
+ for (const roleName of roleNames) {
46
+ const roleAccess = roles[roleName]!
47
+ code += this.generateRoleEntityTypes(model, roleName, roleAccess)
48
+ }
49
+
50
+ // Generate RoleSchemas type map per entity
51
+ for (const entity of Object.values(model.entities)) {
52
+ const roleEntries = roleNames
53
+ .filter(role => roles[role]!.has(entity.name))
54
+ .map(role => `\treadonly ${role}: ${this.roleEntityName(role, entity.name)}`)
55
+ .join('\n')
56
+
57
+ if (roleEntries) {
58
+ code += `export interface ${entity.name}$Roles {\n${roleEntries}\n}\n\n`
59
+ }
60
+ }
61
+
62
+ // Export available roles type
63
+ code += `export type AvailableRoles = ${roleNames.map(r => `'${r}'`).join(' | ')}\n`
64
+
65
+ return code
66
+ }
67
+
68
+ /**
69
+ * Generates schema.ts content using roleEntityDef.
70
+ */
71
+ generateSchemaFile(model: Model.Schema, acl: Acl.Schema): string {
72
+ const roles = this.resolveRoles(acl)
73
+ const roleNames = Object.keys(roles)
74
+ const entityNames = Object.values(model.entities).map(e => e.name).sort()
75
+
76
+ // Build imports for role entity types
77
+ const roleTypeImports: string[] = []
78
+ for (const name of entityNames) {
79
+ const hasRoles = roleNames.some(role => roles[role]!.has(name))
80
+ if (hasRoles) {
81
+ roleTypeImports.push(`${name}$Roles`)
82
+ }
83
+ }
84
+
85
+ const entries = entityNames.map(name => {
86
+ const hasRoles = roleNames.some(role => roles[role]!.has(name))
87
+ if (hasRoles) {
88
+ return `\t${name}: roleEntityDef<${name}$Roles>('${name}', schemaDef),`
89
+ }
90
+ return `\t${name}: entityDef<${name}>('${name}', schemaDef),`
91
+ }).join('\n')
92
+
93
+ const allImports = [...entityNames, ...roleTypeImports]
94
+
95
+ return `import { entityDef, roleEntityDef } from '@contember/bindx'
96
+ import { schemaNamesToDef } from '@contember/bindx-react'
97
+ import type { ${allImports.join(', ')} } from './entities'
98
+ import { schemaNames } from './names'
99
+
100
+ const schemaDef = schemaNamesToDef(schemaNames)
101
+
102
+ export const schema = {
103
+ ${entries}
104
+ } as const
105
+ `
106
+ }
107
+
108
+ /**
109
+ * Resolves role permissions, flattening inheritance.
110
+ * Returns a map: role → (entity → Set<fieldName>)
111
+ */
112
+ private resolveRoles(acl: Acl.Schema): Record<string, Map<string, Set<string>>> {
113
+ const result: Record<string, Map<string, Set<string>>> = {}
114
+
115
+ for (const [roleName, rolePerms] of Object.entries(acl.roles)) {
116
+ // Skip implicit roles
117
+ if (rolePerms.implicit) continue
118
+
119
+ const entityFieldMap = new Map<string, Set<string>>()
120
+
121
+ // Collect inherited fields first
122
+ if (rolePerms.inherits) {
123
+ for (const parentRole of rolePerms.inherits) {
124
+ const parentFields = result[parentRole]
125
+ if (parentFields) {
126
+ for (const [entityName, fields] of parentFields) {
127
+ const existing = entityFieldMap.get(entityName) ?? new Set()
128
+ for (const field of fields) {
129
+ existing.add(field)
130
+ }
131
+ entityFieldMap.set(entityName, existing)
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ // Add own permissions
138
+ for (const [entityName, entityPerms] of Object.entries(rolePerms.entities)) {
139
+ const readPerms = entityPerms.operations.read
140
+ if (!readPerms) continue
141
+
142
+ const fields = entityFieldMap.get(entityName) ?? new Set<string>()
143
+ for (const [fieldName, perm] of Object.entries(readPerms)) {
144
+ if (perm === true || (this.allowPredicateAccess && perm !== false)) {
145
+ fields.add(fieldName)
146
+ }
147
+ }
148
+ if (fields.size > 0) {
149
+ entityFieldMap.set(entityName, fields)
150
+ }
151
+ }
152
+
153
+ result[roleName] = entityFieldMap
154
+ }
155
+
156
+ return result
157
+ }
158
+
159
+ /**
160
+ * Generates per-role entity interfaces for a single role.
161
+ */
162
+ private generateRoleEntityTypes(
163
+ model: Model.Schema,
164
+ roleName: string,
165
+ roleAccess: Map<string, Set<string>>,
166
+ ): string {
167
+ let code = ''
168
+
169
+ for (const entity of Object.values(model.entities)) {
170
+ const accessibleFields = roleAccess.get(entity.name)
171
+ if (!accessibleFields || accessibleFields.size === 0) continue
172
+
173
+ code += this.generateRoleEntityType(model, entity, roleName, accessibleFields, roleAccess)
174
+ }
175
+
176
+ return code
177
+ }
178
+
179
+ private generateRoleEntityType(
180
+ model: Model.Schema,
181
+ entity: Model.Entity,
182
+ roleName: string,
183
+ accessibleFields: Set<string>,
184
+ roleAccess: Map<string, Set<string>>,
185
+ ): string {
186
+ const typeName = this.roleEntityName(roleName, entity.name)
187
+ let code = `export interface ${typeName} {\n`
188
+
189
+ acceptEveryFieldVisitor(model, entity, {
190
+ visitColumn: ctx => {
191
+ if (!accessibleFields.has(ctx.column.name)) return
192
+ code += `\t${ctx.column.name}: ${columnToTsType(ctx.column)}${ctx.column.nullable ? ' | null' : ''}\n`
193
+ },
194
+ visitHasOne: ctx => {
195
+ if (!accessibleFields.has(ctx.relation.name)) return
196
+ const targetFields = roleAccess.get(ctx.targetEntity.name)
197
+ const targetType = targetFields && targetFields.size > 0
198
+ ? this.roleEntityName(roleName, ctx.targetEntity.name)
199
+ : ctx.targetEntity.name
200
+ code += `\t${ctx.relation.name}: ${targetType}\n`
201
+ },
202
+ visitHasMany: ctx => {
203
+ if (!accessibleFields.has(ctx.relation.name)) return
204
+ const targetFields = roleAccess.get(ctx.targetEntity.name)
205
+ const targetType = targetFields && targetFields.size > 0
206
+ ? this.roleEntityName(roleName, ctx.targetEntity.name)
207
+ : ctx.targetEntity.name
208
+ code += `\t${ctx.relation.name}: ${targetType}[]\n`
209
+ },
210
+ })
211
+
212
+ code += '}\n\n'
213
+ return code
214
+ }
215
+
216
+ private roleEntityName(roleName: string, entityName: string): string {
217
+ return `${entityName}$${roleName}`
218
+ }
219
+ }
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Example script to generate bindx schema from Contember Model
4
+ *
5
+ * Usage:
6
+ * bun run packages/bindx-generator/scripts/generate.ts ./path/to/model.ts ./path/to/output/dir
7
+ *
8
+ * This script demonstrates how to use the bindx generator to create
9
+ * TypeScript schema files from a Contember model.
10
+ */
11
+
12
+ import { generate } from './index'
13
+ import { mkdir, writeFile } from 'node:fs/promises'
14
+ import { join } from 'node:path'
15
+
16
+ async function main(): Promise<void> {
17
+ console.log('Generating bindx schema...')
18
+
19
+ const schemaFile = process.argv[2]
20
+ const outputPath = process.argv[3]
21
+
22
+ if (!schemaFile || !outputPath) {
23
+ console.error('Usage: bun run generate.ts <schema-file> <output-dir>')
24
+ process.exit(1)
25
+ }
26
+
27
+ const absoluteSchemaFile = join(process.cwd(), schemaFile)
28
+ const schema = (await import(absoluteSchemaFile)).default
29
+
30
+ // Generate schema files
31
+ const files = generate(schema.model)
32
+
33
+ // Output directory
34
+ const outputDir = join(process.cwd(), outputPath)
35
+
36
+ // Create output directory
37
+ await mkdir(outputDir, { recursive: true })
38
+
39
+ // Write files
40
+ for (const [filename, content] of Object.entries(files)) {
41
+ const filePath = join(outputDir, filename)
42
+ await writeFile(filePath, String(content), 'utf-8')
43
+ console.log(`Generated: ${filePath}`)
44
+ }
45
+
46
+ console.log(`\nSchema generation complete!`)
47
+ console.log(`\nGenerated files in: ${outputDir}`)
48
+ console.log('\nTo use the generated schema:')
49
+ console.log(' import { useEntity, Entity } from "./generated"')
50
+ }
51
+
52
+ main().catch(error => {
53
+ console.error('Error generating schema:', error)
54
+ process.exit(1)
55
+ })
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @contember/bindx-generator
3
+ *
4
+ * Schema generator for @contember/bindx.
5
+ * Generates TypeScript types and runtime schema definitions from
6
+ * Contember Model.Schema.
7
+ */
8
+
9
+ export { BindxGenerator, generate } from './BindxGenerator'
10
+ export type { BindxGeneratorOptions, GeneratedFiles } from './BindxGenerator'
11
+
12
+ export { EntityTypeSchemaGenerator } from './EntityTypeSchemaGenerator'
13
+ export { EnumTypeSchemaGenerator } from './EnumTypeSchemaGenerator'
14
+ export { NameSchemaGenerator } from './NameSchemaGenerator'
15
+ export { RoleSchemaGenerator } from './RoleSchemaGenerator'
16
+ export type { RoleSchemaGeneratorOptions } from './RoleSchemaGenerator'
17
+ export type { BindxSchemaNames, BindxSchemaEntityNames } from './NameSchemaGenerator'
18
+
19
+ export { columnToTsType, getEnumTypeName, capitalizeFirstLetter } from './utils'
package/src/utils.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Utility functions for bindx schema generation
3
+ */
4
+
5
+ import { Model } from '@contember/schema'
6
+
7
+ /**
8
+ * Convert Contember column type to TypeScript type string
9
+ */
10
+ export const columnToTsType = (column: Model.AnyColumn): string => {
11
+ const baseType = (() => {
12
+ switch (column.type) {
13
+ case Model.ColumnType.Enum:
14
+ return getEnumTypeName(column.columnType)
15
+ case Model.ColumnType.String:
16
+ return 'string'
17
+ case Model.ColumnType.Int:
18
+ return 'number'
19
+ case Model.ColumnType.Double:
20
+ return 'number'
21
+ case Model.ColumnType.Bool:
22
+ return 'boolean'
23
+ case Model.ColumnType.DateTime:
24
+ return 'string'
25
+ case Model.ColumnType.Time:
26
+ return 'string'
27
+ case Model.ColumnType.Date:
28
+ return 'string'
29
+ case Model.ColumnType.Json:
30
+ return 'JSONValue'
31
+ case Model.ColumnType.Uuid:
32
+ return 'string'
33
+ default:
34
+ ((_: never) => {
35
+ throw new Error(`Unknown column type ${_}`)
36
+ })(column.type)
37
+ }
38
+ })()
39
+ return column.list ? `readonly ${baseType}[]` : baseType
40
+ }
41
+
42
+ /**
43
+ * Generate TypeScript enum type name from Contember enum name
44
+ */
45
+ export const getEnumTypeName = (enumName: string): string => {
46
+ return `${enumName}Enum`
47
+ }
48
+
49
+ /**
50
+ * Capitalize first letter of a string
51
+ */
52
+ export const capitalizeFirstLetter = (value: string): string => {
53
+ return value.charAt(0).toUpperCase() + value.slice(1)
54
+ }