@contember/bindx 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.
- package/dist/adapter/ContemberAdapter.d.ts +40 -0
- package/dist/adapter/ContemberAdapter.d.ts.map +1 -0
- package/dist/adapter/MockAdapter.d.ts +81 -0
- package/dist/adapter/MockAdapter.d.ts.map +1 -0
- package/dist/adapter/MockQueryEngine.d.ts +43 -0
- package/dist/adapter/MockQueryEngine.d.ts.map +1 -0
- package/dist/adapter/index.d.ts +5 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/types.d.ts +184 -0
- package/dist/adapter/types.d.ts.map +1 -0
- package/dist/brand/ComponentBrand.d.ts +25 -0
- package/dist/brand/ComponentBrand.d.ts.map +1 -0
- package/dist/contember/index.d.ts +10 -0
- package/dist/contember/index.d.ts.map +1 -0
- package/dist/core/ActionDispatcher.d.ts +64 -0
- package/dist/core/ActionDispatcher.d.ts.map +1 -0
- package/dist/core/Disposable.d.ts +57 -0
- package/dist/core/Disposable.d.ts.map +1 -0
- package/dist/core/EntityLoader.d.ts +80 -0
- package/dist/core/EntityLoader.d.ts.map +1 -0
- package/dist/core/PersistenceManager.d.ts +126 -0
- package/dist/core/PersistenceManager.d.ts.map +1 -0
- package/dist/core/SelectionResolver.d.ts +21 -0
- package/dist/core/SelectionResolver.d.ts.map +1 -0
- package/dist/core/actionClassification.d.ts +48 -0
- package/dist/core/actionClassification.d.ts.map +1 -0
- package/dist/core/actions.d.ts +301 -0
- package/dist/core/actions.d.ts.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/dataview/filterHandlers.d.ts +18 -0
- package/dist/dataview/filterHandlers.d.ts.map +1 -0
- package/dist/dataview/index.d.ts +3 -0
- package/dist/dataview/index.d.ts.map +1 -0
- package/dist/dataview/types.d.ts +164 -0
- package/dist/dataview/types.d.ts.map +1 -0
- package/dist/errors/UnfetchedFieldError.d.ts +11 -0
- package/dist/errors/UnfetchedFieldError.d.ts.map +1 -0
- package/dist/errors/errorPathResolver.d.ts +52 -0
- package/dist/errors/errorPathResolver.d.ts.map +1 -0
- package/dist/errors/index.d.ts +5 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/pathMapper.d.ts +42 -0
- package/dist/errors/pathMapper.d.ts.map +1 -0
- package/dist/errors/types.d.ts +131 -0
- package/dist/errors/types.d.ts.map +1 -0
- package/dist/events/EventEmitter.d.ts +97 -0
- package/dist/events/EventEmitter.d.ts.map +1 -0
- package/dist/events/eventFactory.d.ts +40 -0
- package/dist/events/eventFactory.d.ts.map +1 -0
- package/dist/events/index.d.ts +15 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/types.d.ts +264 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/handles/BaseHandle.d.ts +64 -0
- package/dist/handles/BaseHandle.d.ts.map +1 -0
- package/dist/handles/EntityHandle.d.ts +208 -0
- package/dist/handles/EntityHandle.d.ts.map +1 -0
- package/dist/handles/FieldHandle.d.ts +111 -0
- package/dist/handles/FieldHandle.d.ts.map +1 -0
- package/dist/handles/HasManyListHandle.d.ts +166 -0
- package/dist/handles/HasManyListHandle.d.ts.map +1 -0
- package/dist/handles/HasOneHandle.d.ts +168 -0
- package/dist/handles/HasOneHandle.d.ts.map +1 -0
- package/dist/handles/PlaceholderHandle.d.ts +117 -0
- package/dist/handles/PlaceholderHandle.d.ts.map +1 -0
- package/dist/handles/index.d.ts +8 -0
- package/dist/handles/index.d.ts.map +1 -0
- package/dist/handles/proxyFactory.d.ts +66 -0
- package/dist/handles/proxyFactory.d.ts.map +1 -0
- package/dist/handles/types.d.ts +430 -0
- package/dist/handles/types.d.ts.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/internal.d.ts +11 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/persistence/BatchPersister.d.ts +142 -0
- package/dist/persistence/BatchPersister.d.ts.map +1 -0
- package/dist/persistence/ChangeRegistry.d.ts +87 -0
- package/dist/persistence/ChangeRegistry.d.ts.map +1 -0
- package/dist/persistence/ContemberSchemaMutationAdapter.d.ts +16 -0
- package/dist/persistence/ContemberSchemaMutationAdapter.d.ts.map +1 -0
- package/dist/persistence/MutationCollector.d.ts +96 -0
- package/dist/persistence/MutationCollector.d.ts.map +1 -0
- package/dist/persistence/MutationSchemaProvider.d.ts +30 -0
- package/dist/persistence/MutationSchemaProvider.d.ts.map +1 -0
- package/dist/persistence/index.d.ts +7 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/types.d.ts +189 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/roles/RoleSchemaRegistry.d.ts +77 -0
- package/dist/roles/RoleSchemaRegistry.d.ts.map +1 -0
- package/dist/roles/index.d.ts +11 -0
- package/dist/roles/index.d.ts.map +1 -0
- package/dist/roles/types.d.ts +242 -0
- package/dist/roles/types.d.ts.map +1 -0
- package/dist/schema/ContemberSchema.d.ts +98 -0
- package/dist/schema/ContemberSchema.d.ts.map +1 -0
- package/dist/schema/SchemaLoader.d.ts +28 -0
- package/dist/schema/SchemaLoader.d.ts.map +1 -0
- package/dist/schema/SchemaRegistry.d.ts +95 -0
- package/dist/schema/SchemaRegistry.d.ts.map +1 -0
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/roles.d.ts +62 -0
- package/dist/schema/roles.d.ts.map +1 -0
- package/dist/schema/types.d.ts +179 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/selection/SelectionMetaCollector.d.ts +32 -0
- package/dist/selection/SelectionMetaCollector.d.ts.map +1 -0
- package/dist/selection/SelectionScope.d.ts +77 -0
- package/dist/selection/SelectionScope.d.ts.map +1 -0
- package/dist/selection/buildQuery.d.ts +45 -0
- package/dist/selection/buildQuery.d.ts.map +1 -0
- package/dist/selection/createFragment.d.ts +29 -0
- package/dist/selection/createFragment.d.ts.map +1 -0
- package/dist/selection/createSelectionBuilder.d.ts +18 -0
- package/dist/selection/createSelectionBuilder.d.ts.map +1 -0
- package/dist/selection/index.d.ts +8 -0
- package/dist/selection/index.d.ts.map +1 -0
- package/dist/selection/queryTypes.d.ts +84 -0
- package/dist/selection/queryTypes.d.ts.map +1 -0
- package/dist/selection/types.d.ts +238 -0
- package/dist/selection/types.d.ts.map +1 -0
- package/dist/store/DirtyTracker.d.ts +26 -0
- package/dist/store/DirtyTracker.d.ts.map +1 -0
- package/dist/store/EntityMetaStore.d.ts +52 -0
- package/dist/store/EntityMetaStore.d.ts.map +1 -0
- package/dist/store/EntitySnapshotStore.d.ts +63 -0
- package/dist/store/EntitySnapshotStore.d.ts.map +1 -0
- package/dist/store/ErrorStore.d.ts +82 -0
- package/dist/store/ErrorStore.d.ts.map +1 -0
- package/dist/store/RelationStore.d.ts +175 -0
- package/dist/store/RelationStore.d.ts.map +1 -0
- package/dist/store/SnapshotStore.d.ts +143 -0
- package/dist/store/SnapshotStore.d.ts.map +1 -0
- package/dist/store/SubscriptionManager.d.ts +90 -0
- package/dist/store/SubscriptionManager.d.ts.map +1 -0
- package/dist/store/TouchedStore.d.ts +24 -0
- package/dist/store/TouchedStore.d.ts.map +1 -0
- package/dist/store/entityId.d.ts +29 -0
- package/dist/store/entityId.d.ts.map +1 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/snapshots.d.ts +144 -0
- package/dist/store/snapshots.d.ts.map +1 -0
- package/dist/undo/UndoManager.d.ts +120 -0
- package/dist/undo/UndoManager.d.ts.map +1 -0
- package/dist/undo/index.d.ts +3 -0
- package/dist/undo/index.d.ts.map +1 -0
- package/dist/undo/types.d.ts +74 -0
- package/dist/undo/types.d.ts.map +1 -0
- package/dist/utils/aliasGenerator.d.ts +25 -0
- package/dist/utils/aliasGenerator.d.ts.map +1 -0
- package/dist/utils/deepEqual.d.ts +6 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/package.json +29 -0
- package/src/adapter/ContemberAdapter.ts +239 -0
- package/src/adapter/MockAdapter.ts +394 -0
- package/src/adapter/MockQueryEngine.ts +236 -0
- package/src/adapter/index.ts +21 -0
- package/src/adapter/types.ts +199 -0
- package/src/brand/ComponentBrand.ts +28 -0
- package/src/contember/index.ts +17 -0
- package/src/core/ActionDispatcher.ts +450 -0
- package/src/core/Disposable.ts +126 -0
- package/src/core/EntityLoader.ts +169 -0
- package/src/core/SelectionResolver.ts +78 -0
- package/src/core/actionClassification.ts +196 -0
- package/src/core/actions.ts +491 -0
- package/src/core/index.ts +20 -0
- package/src/dataview/filterHandlers.ts +443 -0
- package/src/dataview/index.ts +36 -0
- package/src/dataview/types.ts +220 -0
- package/src/errors/UnfetchedFieldError.ts +18 -0
- package/src/errors/errorPathResolver.ts +239 -0
- package/src/errors/index.ts +40 -0
- package/src/errors/pathMapper.ts +40 -0
- package/src/errors/types.ts +223 -0
- package/src/events/EventEmitter.ts +447 -0
- package/src/events/eventFactory.ts +430 -0
- package/src/events/index.ts +89 -0
- package/src/events/types.ts +374 -0
- package/src/handles/BaseHandle.ts +100 -0
- package/src/handles/EntityHandle.ts +538 -0
- package/src/handles/FieldHandle.ts +268 -0
- package/src/handles/HasManyListHandle.ts +500 -0
- package/src/handles/HasOneHandle.ts +500 -0
- package/src/handles/PlaceholderHandle.ts +321 -0
- package/src/handles/index.ts +37 -0
- package/src/handles/proxyFactory.ts +218 -0
- package/src/handles/types.ts +743 -0
- package/src/index.ts +384 -0
- package/src/internal.ts +19 -0
- package/src/persistence/BatchPersister.ts +911 -0
- package/src/persistence/ChangeRegistry.ts +150 -0
- package/src/persistence/ContemberSchemaMutationAdapter.ts +57 -0
- package/src/persistence/MutationCollector.ts +567 -0
- package/src/persistence/MutationSchemaProvider.ts +33 -0
- package/src/persistence/index.ts +23 -0
- package/src/persistence/types.ts +220 -0
- package/src/schema/ContemberSchema.ts +132 -0
- package/src/schema/SchemaLoader.ts +93 -0
- package/src/schema/SchemaRegistry.ts +286 -0
- package/src/schema/index.ts +51 -0
- package/src/schema/roles.ts +83 -0
- package/src/schema/types.ts +238 -0
- package/src/selection/SelectionMetaCollector.ts +122 -0
- package/src/selection/SelectionScope.ts +208 -0
- package/src/selection/buildQuery.ts +134 -0
- package/src/selection/createFragment.ts +50 -0
- package/src/selection/createSelectionBuilder.ts +283 -0
- package/src/selection/index.ts +31 -0
- package/src/selection/queryTypes.ts +135 -0
- package/src/selection/types.ts +320 -0
- package/src/store/DirtyTracker.ts +117 -0
- package/src/store/EntityMetaStore.ts +143 -0
- package/src/store/EntitySnapshotStore.ts +260 -0
- package/src/store/ErrorStore.ts +251 -0
- package/src/store/RelationStore.ts +696 -0
- package/src/store/SnapshotStore.ts +765 -0
- package/src/store/SubscriptionManager.ts +241 -0
- package/src/store/TouchedStore.ts +42 -0
- package/src/store/entityId.ts +43 -0
- package/src/store/index.ts +2 -0
- package/src/store/snapshots.ts +203 -0
- package/src/undo/UndoManager.ts +418 -0
- package/src/undo/index.ts +9 -0
- package/src/undo/types.ts +84 -0
- package/src/utils/aliasGenerator.ts +56 -0
- package/src/utils/deepEqual.ts +33 -0
- package/src/utils/index.ts +2 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import type { ContemberSchema } from './ContemberSchema.js'
|
|
2
|
+
import type {
|
|
3
|
+
EntitySchemaDef,
|
|
4
|
+
FieldDef,
|
|
5
|
+
HasManyRelationDef,
|
|
6
|
+
HasOneRelationDef,
|
|
7
|
+
RelationType,
|
|
8
|
+
SchemaDefinition,
|
|
9
|
+
} from './types.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Runtime registry for entity schemas.
|
|
13
|
+
* Provides lookups for entity definitions, relation targets, and field types.
|
|
14
|
+
*/
|
|
15
|
+
export class SchemaRegistry<TModels extends Record<string, object> = Record<string, object>> {
|
|
16
|
+
private readonly entityDefs: Map<string, EntitySchemaDef>
|
|
17
|
+
|
|
18
|
+
constructor(definition: SchemaDefinition<TModels>) {
|
|
19
|
+
this.entityDefs = new Map()
|
|
20
|
+
|
|
21
|
+
for (const [entityName, entityDef] of Object.entries(definition.entities)) {
|
|
22
|
+
this.entityDefs.set(entityName, normalizeEntityDef(entityDef as EntitySchemaDef))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a SchemaRegistry from a ContemberSchema (loaded from API or injected).
|
|
28
|
+
* This is the preferred way to create a SchemaRegistry at runtime.
|
|
29
|
+
*/
|
|
30
|
+
static fromContemberSchema(schema: ContemberSchema): SchemaRegistry {
|
|
31
|
+
const entities: Record<string, EntitySchemaDef> = {}
|
|
32
|
+
|
|
33
|
+
for (const entityName of schema.getEntityNames()) {
|
|
34
|
+
const entity = schema.getEntity(entityName)
|
|
35
|
+
if (!entity) continue
|
|
36
|
+
|
|
37
|
+
const fields: Record<string, FieldDef> = {}
|
|
38
|
+
|
|
39
|
+
for (const [fieldName, field] of entity.fields) {
|
|
40
|
+
if (field.__typename === '_Column') {
|
|
41
|
+
fields[fieldName] = { type: 'scalar' }
|
|
42
|
+
} else if (field.__typename === '_Relation') {
|
|
43
|
+
const isMany = field.type === 'OneHasMany' || field.type === 'ManyHasMany'
|
|
44
|
+
if (isMany) {
|
|
45
|
+
fields[fieldName] = {
|
|
46
|
+
type: 'hasMany',
|
|
47
|
+
target: field.targetEntity,
|
|
48
|
+
inverse: field.side === 'owning' ? field.inversedBy ?? undefined : field.ownedBy,
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
fields[fieldName] = {
|
|
52
|
+
type: 'hasOne',
|
|
53
|
+
target: field.targetEntity,
|
|
54
|
+
inverse: field.side === 'owning' ? field.inversedBy ?? undefined : field.ownedBy,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
entities[entityName] = { fields }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return new SchemaRegistry({ entities } as SchemaDefinition<Record<string, object>>)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Gets the entity names defined in the schema
|
|
68
|
+
*/
|
|
69
|
+
getEntityNames(): string[] {
|
|
70
|
+
return Array.from(this.entityDefs.keys())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Checks if an entity type exists in the schema
|
|
75
|
+
*/
|
|
76
|
+
hasEntity(entityType: string): boolean {
|
|
77
|
+
return this.entityDefs.has(entityType)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets the schema definition for an entity type
|
|
82
|
+
*/
|
|
83
|
+
getEntityDef(entityType: string): EntitySchemaDef | undefined {
|
|
84
|
+
return this.entityDefs.get(entityType)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Gets the field definition for a specific field on an entity
|
|
89
|
+
*/
|
|
90
|
+
getFieldDef(entityType: string, fieldName: string): FieldDef | undefined {
|
|
91
|
+
const entityDef = this.entityDefs.get(entityType)
|
|
92
|
+
if (!entityDef) return undefined
|
|
93
|
+
return entityDef.fields[fieldName]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gets the enum values for an enum field.
|
|
98
|
+
* Returns undefined if the field is not an enum or doesn't exist.
|
|
99
|
+
*/
|
|
100
|
+
getEnumValues(entityType: string, fieldName: string): readonly string[] | undefined {
|
|
101
|
+
const fieldDef = this.getFieldDef(entityType, fieldName)
|
|
102
|
+
if (!fieldDef || fieldDef.type !== 'enum') return undefined
|
|
103
|
+
return fieldDef.values
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gets the enum name for an enum field.
|
|
108
|
+
* Returns undefined if the field is not an enum or doesn't exist.
|
|
109
|
+
*/
|
|
110
|
+
getEnumName(entityType: string, fieldName: string): string | undefined {
|
|
111
|
+
const fieldDef = this.getFieldDef(entityType, fieldName)
|
|
112
|
+
if (!fieldDef || fieldDef.type !== 'enum') return undefined
|
|
113
|
+
return fieldDef.enumName
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Gets the column type for a scalar field (e.g. 'String', 'Integer', 'Date').
|
|
118
|
+
* Returns undefined if the field is not a scalar or doesn't exist.
|
|
119
|
+
*/
|
|
120
|
+
getColumnType(entityType: string, fieldName: string): string | undefined {
|
|
121
|
+
const fieldDef = this.getFieldDef(entityType, fieldName)
|
|
122
|
+
if (!fieldDef || fieldDef.type !== 'scalar') return undefined
|
|
123
|
+
return fieldDef.columnType
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Gets the target entity type for a relation field.
|
|
128
|
+
* Returns undefined if the field is not a relation or doesn't exist.
|
|
129
|
+
*/
|
|
130
|
+
getRelationTarget(entityType: string, fieldName: string): string | undefined {
|
|
131
|
+
const fieldDef = this.getFieldDef(entityType, fieldName)
|
|
132
|
+
if (!fieldDef) return undefined
|
|
133
|
+
|
|
134
|
+
if (fieldDef.type === 'hasOne' || fieldDef.type === 'hasMany') {
|
|
135
|
+
return fieldDef.target
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return undefined
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Gets the relation type for a field (hasOne or hasMany).
|
|
143
|
+
* Returns undefined if the field is not a relation.
|
|
144
|
+
*/
|
|
145
|
+
getRelationType(entityType: string, fieldName: string): RelationType | undefined {
|
|
146
|
+
const fieldDef = this.getFieldDef(entityType, fieldName)
|
|
147
|
+
if (!fieldDef) return undefined
|
|
148
|
+
|
|
149
|
+
if (fieldDef.type === 'hasOne' || fieldDef.type === 'hasMany') {
|
|
150
|
+
return fieldDef.type
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return undefined
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Checks if a field is a relation (hasOne or hasMany)
|
|
158
|
+
*/
|
|
159
|
+
isRelation(entityType: string, fieldName: string): boolean {
|
|
160
|
+
return this.getRelationType(entityType, fieldName) !== undefined
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Checks if a field is a has-one relation
|
|
165
|
+
*/
|
|
166
|
+
isHasOne(entityType: string, fieldName: string): boolean {
|
|
167
|
+
return this.getRelationType(entityType, fieldName) === 'hasOne'
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Checks if a field is a has-many relation
|
|
172
|
+
*/
|
|
173
|
+
isHasMany(entityType: string, fieldName: string): boolean {
|
|
174
|
+
return this.getRelationType(entityType, fieldName) === 'hasMany'
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Checks if a field is a scalar (non-relation) field
|
|
179
|
+
*/
|
|
180
|
+
isScalar(entityType: string, fieldName: string): boolean {
|
|
181
|
+
const fieldDef = this.getFieldDef(entityType, fieldName)
|
|
182
|
+
return fieldDef?.type === 'scalar'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Gets the inverse field name for a relation.
|
|
187
|
+
* Returns undefined if no inverse is defined.
|
|
188
|
+
*/
|
|
189
|
+
getInverseField(entityType: string, fieldName: string): string | undefined {
|
|
190
|
+
const fieldDef = this.getFieldDef(entityType, fieldName)
|
|
191
|
+
if (!fieldDef) return undefined
|
|
192
|
+
if (fieldDef.type === 'scalar') return undefined
|
|
193
|
+
|
|
194
|
+
return (fieldDef as HasOneRelationDef | HasManyRelationDef).inverse
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Gets all scalar field names for an entity
|
|
199
|
+
*/
|
|
200
|
+
getScalarFields(entityType: string): string[] {
|
|
201
|
+
const entityDef = this.entityDefs.get(entityType)
|
|
202
|
+
if (!entityDef) return []
|
|
203
|
+
|
|
204
|
+
return Object.entries(entityDef.fields)
|
|
205
|
+
.filter(([_, def]) => def.type === 'scalar')
|
|
206
|
+
.map(([name]) => name)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Gets all relation field names for an entity
|
|
211
|
+
*/
|
|
212
|
+
getRelationFields(entityType: string): string[] {
|
|
213
|
+
const entityDef = this.entityDefs.get(entityType)
|
|
214
|
+
if (!entityDef) return []
|
|
215
|
+
|
|
216
|
+
return Object.entries(entityDef.fields)
|
|
217
|
+
.filter(([_, def]) => def.type === 'hasOne' || def.type === 'hasMany')
|
|
218
|
+
.map(([name]) => name)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Gets all field names for an entity
|
|
223
|
+
*/
|
|
224
|
+
getAllFields(entityType: string): string[] {
|
|
225
|
+
const entityDef = this.entityDefs.get(entityType)
|
|
226
|
+
if (!entityDef) return []
|
|
227
|
+
|
|
228
|
+
return Object.keys(entityDef.fields)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Validates that all relation targets exist in the schema.
|
|
233
|
+
* Throws an error if any target is missing.
|
|
234
|
+
*/
|
|
235
|
+
validate(): void {
|
|
236
|
+
for (const [entityName, entityDef] of this.entityDefs) {
|
|
237
|
+
for (const [fieldName, fieldDef] of Object.entries(entityDef.fields)) {
|
|
238
|
+
if (fieldDef.type === 'hasOne' || fieldDef.type === 'hasMany') {
|
|
239
|
+
if (!this.entityDefs.has(fieldDef.target)) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`Invalid schema: Entity "${entityName}" field "${fieldName}" references ` +
|
|
242
|
+
`unknown entity "${fieldDef.target}"`,
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Normalizes field definitions from Contember SchemaNames format
|
|
253
|
+
* (column/one/many with entity) to bindx FieldDef format (scalar/hasOne/hasMany with target).
|
|
254
|
+
*/
|
|
255
|
+
function normalizeEntityDef(entityDef: EntitySchemaDef): EntitySchemaDef {
|
|
256
|
+
const fields: Record<string, FieldDef> = {}
|
|
257
|
+
let needsNormalization = false
|
|
258
|
+
|
|
259
|
+
for (const [fieldName, fieldDef] of Object.entries(entityDef.fields)) {
|
|
260
|
+
const raw = fieldDef as unknown as Record<string, unknown>
|
|
261
|
+
const type = raw['type'] as string
|
|
262
|
+
|
|
263
|
+
if (type === 'column') {
|
|
264
|
+
fields[fieldName] = { type: 'scalar' }
|
|
265
|
+
needsNormalization = true
|
|
266
|
+
} else if (type === 'one') {
|
|
267
|
+
fields[fieldName] = {
|
|
268
|
+
type: 'hasOne',
|
|
269
|
+
target: (raw['entity'] as string) ?? (raw['target'] as string),
|
|
270
|
+
inverse: raw['inverse'] as string | undefined,
|
|
271
|
+
}
|
|
272
|
+
needsNormalization = true
|
|
273
|
+
} else if (type === 'many') {
|
|
274
|
+
fields[fieldName] = {
|
|
275
|
+
type: 'hasMany',
|
|
276
|
+
target: (raw['entity'] as string) ?? (raw['target'] as string),
|
|
277
|
+
inverse: raw['inverse'] as string | undefined,
|
|
278
|
+
}
|
|
279
|
+
needsNormalization = true
|
|
280
|
+
} else {
|
|
281
|
+
fields[fieldName] = fieldDef
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return needsNormalization ? { fields } : entityDef
|
|
286
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type RelationType,
|
|
3
|
+
type ScalarFieldDef,
|
|
4
|
+
type HasOneRelationDef,
|
|
5
|
+
type HasManyRelationDef,
|
|
6
|
+
type FieldDef,
|
|
7
|
+
type EntitySchemaDef,
|
|
8
|
+
type SchemaDefinition,
|
|
9
|
+
type InferModel,
|
|
10
|
+
type InferEntityNames,
|
|
11
|
+
type EntityDef,
|
|
12
|
+
type InferEntityDef,
|
|
13
|
+
scalar,
|
|
14
|
+
enumScalar,
|
|
15
|
+
type EnumFieldDef,
|
|
16
|
+
hasOne,
|
|
17
|
+
hasMany,
|
|
18
|
+
defineSchema,
|
|
19
|
+
entityDef,
|
|
20
|
+
roleEntityDef,
|
|
21
|
+
} from './types.js'
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
type UnionToIntersection,
|
|
25
|
+
type RoleNames,
|
|
26
|
+
type CommonEntity,
|
|
27
|
+
type EntityForRoles,
|
|
28
|
+
type ResolveEntity,
|
|
29
|
+
type SingleRoleMap,
|
|
30
|
+
type DefaultRole,
|
|
31
|
+
DEFAULT_ROLE,
|
|
32
|
+
} from './roles.js'
|
|
33
|
+
|
|
34
|
+
export { SchemaRegistry } from './SchemaRegistry.js'
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
type SchemaColumnType,
|
|
38
|
+
type SchemaColumn,
|
|
39
|
+
type SchemaRelationOrderBy,
|
|
40
|
+
type OwningRelation,
|
|
41
|
+
type InverseRelation,
|
|
42
|
+
type SchemaRelation,
|
|
43
|
+
type SchemaField,
|
|
44
|
+
type SchemaEntity,
|
|
45
|
+
type SchemaEnum,
|
|
46
|
+
type ContemberSchemaStore,
|
|
47
|
+
type RawContemberSchema,
|
|
48
|
+
ContemberSchema,
|
|
49
|
+
} from './ContemberSchema.js'
|
|
50
|
+
|
|
51
|
+
export { SchemaLoader, type SchemaLoaderClient } from './SchemaLoader.js'
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role-aware type utilities for bindx.
|
|
3
|
+
*
|
|
4
|
+
* The role system allows per-role field access control at the type level.
|
|
5
|
+
* Default (no role) = narrowest scope (fields ALL roles can access).
|
|
6
|
+
* Specifying roles = expanding scope (union of capabilities for those roles).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Core Utility Types
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Converts a union type to an intersection type.
|
|
15
|
+
* Used to merge field sets from multiple roles.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* UnionToIntersection<{ a: 1 } | { b: 2 }> = { a: 1 } & { b: 2 }
|
|
19
|
+
*/
|
|
20
|
+
export type UnionToIntersection<U> =
|
|
21
|
+
(U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Role Map Types
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extracts all role names from a role map.
|
|
29
|
+
*/
|
|
30
|
+
export type RoleNames<TRoleMap extends Record<string, object>> = keyof TRoleMap & string
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Default entity type — the narrowest scope.
|
|
34
|
+
* Returns a union of all role entity types, so only common fields are accessible via keyof.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* CommonEntity<{ admin: { id: string; secret: string }; public: { id: string } }>
|
|
38
|
+
* // = { id: string; secret: string } | { id: string }
|
|
39
|
+
* // Only `id` is accessible (common to all roles)
|
|
40
|
+
*/
|
|
41
|
+
export type CommonEntity<TRoleMap extends Record<string, object>> = TRoleMap[keyof TRoleMap]
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Entity type for specific roles — expanded scope.
|
|
45
|
+
* Returns an intersection of the specified roles' entity types,
|
|
46
|
+
* giving access to all fields from any of those roles.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* EntityForRoles<{ admin: { id: string; secret: string }; editor: { id: string; notes: string } }, 'admin'>
|
|
50
|
+
* // = { id: string; secret: string }
|
|
51
|
+
*
|
|
52
|
+
* EntityForRoles<{ admin: { id: string; secret: string }; editor: { id: string; notes: string } }, 'admin' | 'editor'>
|
|
53
|
+
* // = { id: string; secret: string } & { id: string; notes: string }
|
|
54
|
+
* // = { id: string; secret: string; notes: string }
|
|
55
|
+
*/
|
|
56
|
+
export type EntityForRoles<
|
|
57
|
+
TRoleMap extends Record<string, object>,
|
|
58
|
+
TRoles extends keyof TRoleMap,
|
|
59
|
+
> = UnionToIntersection<TRoleMap[TRoles]> & object
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolves the entity type based on whether roles are specified.
|
|
63
|
+
* - No roles (TRoles = never): returns CommonEntity (narrowest)
|
|
64
|
+
* - With roles: returns EntityForRoles (expanded)
|
|
65
|
+
*/
|
|
66
|
+
export type ResolveEntity<
|
|
67
|
+
TRoleMap extends Record<string, object>,
|
|
68
|
+
TRoles extends string,
|
|
69
|
+
> = [TRoles] extends [never]
|
|
70
|
+
? CommonEntity<TRoleMap>
|
|
71
|
+
: EntityForRoles<TRoleMap, TRoles & keyof TRoleMap>
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Sentinel role name used when no ACL roles are defined.
|
|
75
|
+
* entityDef<Article>('Article') wraps as { _default: Article }.
|
|
76
|
+
*/
|
|
77
|
+
export const DEFAULT_ROLE = '_default' as const
|
|
78
|
+
export type DefaultRole = typeof DEFAULT_ROLE
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Wraps a single entity type as a single-role map with the _default role.
|
|
82
|
+
*/
|
|
83
|
+
export type SingleRoleMap<TEntity extends object> = { readonly [K in DefaultRole]: TEntity }
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema type definitions for bindx.
|
|
3
|
+
* Provides type-safe entity and relation definitions.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CommonEntity, SingleRoleMap } from './roles.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Relation type discriminator
|
|
10
|
+
*/
|
|
11
|
+
export type RelationType = 'hasOne' | 'hasMany'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Scalar field definition
|
|
15
|
+
*/
|
|
16
|
+
export interface ScalarFieldDef {
|
|
17
|
+
readonly type: 'scalar'
|
|
18
|
+
readonly columnType?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Enum scalar field definition — carries the allowed values at runtime
|
|
23
|
+
*/
|
|
24
|
+
export interface EnumFieldDef {
|
|
25
|
+
readonly type: 'enum'
|
|
26
|
+
readonly enumName: string
|
|
27
|
+
readonly values: readonly string[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Has-one relation definition
|
|
32
|
+
*/
|
|
33
|
+
export interface HasOneRelationDef<TTarget extends string = string> {
|
|
34
|
+
readonly type: 'hasOne'
|
|
35
|
+
readonly target: TTarget
|
|
36
|
+
/** Inverse field name on the target entity (for bidirectional relations) */
|
|
37
|
+
readonly inverse?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Has-many relation definition
|
|
42
|
+
*/
|
|
43
|
+
export interface HasManyRelationDef<TTarget extends string = string> {
|
|
44
|
+
readonly type: 'hasMany'
|
|
45
|
+
readonly target: TTarget
|
|
46
|
+
/** Inverse field name on the target entity (for bidirectional relations) */
|
|
47
|
+
readonly inverse?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Union of all field definition types
|
|
52
|
+
*/
|
|
53
|
+
export type FieldDef<TTarget extends string = string> =
|
|
54
|
+
| ScalarFieldDef
|
|
55
|
+
| EnumFieldDef
|
|
56
|
+
| HasOneRelationDef<TTarget>
|
|
57
|
+
| HasManyRelationDef<TTarget>
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Entity schema definition - maps field names to their definitions
|
|
61
|
+
*/
|
|
62
|
+
export interface EntitySchemaDef<TEntityNames extends string = string> {
|
|
63
|
+
readonly fields: Record<string, FieldDef<TEntityNames>>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Complete schema definition mapping entity names to their schemas.
|
|
68
|
+
* TModels provides the TypeScript types for each entity.
|
|
69
|
+
*/
|
|
70
|
+
export interface SchemaDefinition<
|
|
71
|
+
TModels extends { [K in keyof TModels]: object },
|
|
72
|
+
TEntityNames extends string = keyof TModels & string,
|
|
73
|
+
> {
|
|
74
|
+
readonly entities: {
|
|
75
|
+
[K in TEntityNames]: EntitySchemaDef<TEntityNames>
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Helper function to create a scalar field definition
|
|
81
|
+
*/
|
|
82
|
+
export function scalar(): ScalarFieldDef {
|
|
83
|
+
return { type: 'scalar' }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Helper function to create an enum scalar field definition with allowed values.
|
|
88
|
+
*/
|
|
89
|
+
export function enumScalar<T extends string>(enumName: string, values: readonly T[]): EnumFieldDef {
|
|
90
|
+
return { type: 'enum', enumName, values }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Helper function to create a has-one relation definition
|
|
95
|
+
*/
|
|
96
|
+
export function hasOne<TTarget extends string>(
|
|
97
|
+
target: TTarget,
|
|
98
|
+
options?: { inverse?: string },
|
|
99
|
+
): HasOneRelationDef<TTarget> {
|
|
100
|
+
return {
|
|
101
|
+
type: 'hasOne',
|
|
102
|
+
target,
|
|
103
|
+
inverse: options?.inverse,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Helper function to create a has-many relation definition
|
|
109
|
+
*/
|
|
110
|
+
export function hasMany<TTarget extends string>(
|
|
111
|
+
target: TTarget,
|
|
112
|
+
options?: { inverse?: string },
|
|
113
|
+
): HasManyRelationDef<TTarget> {
|
|
114
|
+
return {
|
|
115
|
+
type: 'hasMany',
|
|
116
|
+
target,
|
|
117
|
+
inverse: options?.inverse,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Helper function to define a complete schema with full type inference.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* interface Author {
|
|
127
|
+
* id: string
|
|
128
|
+
* name: string
|
|
129
|
+
* articles: Article[]
|
|
130
|
+
* }
|
|
131
|
+
*
|
|
132
|
+
* interface Article {
|
|
133
|
+
* id: string
|
|
134
|
+
* title: string
|
|
135
|
+
* author: Author
|
|
136
|
+
* }
|
|
137
|
+
*
|
|
138
|
+
* const schema = defineSchema<{
|
|
139
|
+
* Author: Author
|
|
140
|
+
* Article: Article
|
|
141
|
+
* }>({
|
|
142
|
+
* entities: {
|
|
143
|
+
* Author: {
|
|
144
|
+
* fields: {
|
|
145
|
+
* id: scalar(),
|
|
146
|
+
* name: scalar(),
|
|
147
|
+
* articles: hasMany('Article', { inverse: 'author' })
|
|
148
|
+
* }
|
|
149
|
+
* },
|
|
150
|
+
* Article: {
|
|
151
|
+
* fields: {
|
|
152
|
+
* id: scalar(),
|
|
153
|
+
* title: scalar(),
|
|
154
|
+
* author: hasOne('Author', { inverse: 'articles' })
|
|
155
|
+
* }
|
|
156
|
+
* }
|
|
157
|
+
* }
|
|
158
|
+
* })
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function defineSchema<TModels extends { [K in keyof TModels]: object }>(
|
|
162
|
+
definition: SchemaDefinition<TModels>,
|
|
163
|
+
): SchemaDefinition<TModels> {
|
|
164
|
+
return definition
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Infers the model type for a given entity name from a schema
|
|
169
|
+
*/
|
|
170
|
+
export type InferModel<
|
|
171
|
+
TSchema extends SchemaDefinition<any>,
|
|
172
|
+
TEntityName extends keyof TSchema['entities'],
|
|
173
|
+
> = TSchema extends SchemaDefinition<infer TModels>
|
|
174
|
+
? TEntityName extends keyof TModels
|
|
175
|
+
? TModels[TEntityName]
|
|
176
|
+
: never
|
|
177
|
+
: never
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Infers all entity names from a schema
|
|
181
|
+
*/
|
|
182
|
+
export type InferEntityNames<TSchema extends SchemaDefinition<any>> = keyof TSchema['entities'] & string
|
|
183
|
+
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// Entity Definitions (type-safe entity references)
|
|
186
|
+
// ============================================================================
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* A type-safe reference to an entity in the schema.
|
|
190
|
+
* Carries a role map as a phantom type parameter: Record<roleName, entityType>.
|
|
191
|
+
*
|
|
192
|
+
* For schemas without ACL, the role map is `{ _default: TEntity }`.
|
|
193
|
+
* For schemas with ACL, each role maps to a per-role entity type.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* // Without roles (convenience):
|
|
198
|
+
* const Article = entityDef<ArticleType>('Article') // → EntityDef<{ _default: ArticleType }>
|
|
199
|
+
*
|
|
200
|
+
* // With roles (generator):
|
|
201
|
+
* const Article = roleEntityDef<{ admin: Article$admin; public: Article$public }>('Article')
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export interface EntityDef<TRoleMap extends Record<string, object> = Record<string, object>> {
|
|
205
|
+
readonly $name: string
|
|
206
|
+
/** @internal phantom type for role map — not present at runtime */
|
|
207
|
+
readonly $roleMap?: TRoleMap
|
|
208
|
+
/** @internal reference to the schema definition for collection-time field lookups */
|
|
209
|
+
readonly $schema?: SchemaDefinition<Record<string, object>>
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Infers the common (default) entity type from an EntityDef.
|
|
214
|
+
* Returns the union of all role entity types — only common fields are accessible.
|
|
215
|
+
*/
|
|
216
|
+
export type InferEntityDef<T> = T extends EntityDef<infer TRoleMap> ? CommonEntity<TRoleMap> : never
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Creates a type-safe entity definition reference.
|
|
220
|
+
* Wraps the entity type in a single-role map: `{ _default: TEntity }`.
|
|
221
|
+
*
|
|
222
|
+
* @param name - The entity name as used in the schema
|
|
223
|
+
* @param schema - Optional schema definition for collection-time field lookups
|
|
224
|
+
*/
|
|
225
|
+
export function entityDef<TEntity extends object>(name: string, schema?: SchemaDefinition<Record<string, object>>): EntityDef<SingleRoleMap<TEntity>> {
|
|
226
|
+
return schema ? { $name: name, $schema: schema } : { $name: name }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Creates a role-aware entity definition reference.
|
|
231
|
+
* Used by the generator when ACL roles are present.
|
|
232
|
+
*
|
|
233
|
+
* @param name - The entity name as used in the schema
|
|
234
|
+
* @param schema - Optional schema definition for collection-time field lookups
|
|
235
|
+
*/
|
|
236
|
+
export function roleEntityDef<TRoleMap extends Record<string, object>>(name: string, schema?: SchemaDefinition<Record<string, object>>): EntityDef<TRoleMap> {
|
|
237
|
+
return schema ? { $name: name, $schema: schema } : { $name: name }
|
|
238
|
+
}
|