@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,220 @@
|
|
|
1
|
+
import type { ContemberMutationResult } from '../errors/pathMapper.js'
|
|
2
|
+
import type { FieldError } from '../errors/types.js'
|
|
3
|
+
|
|
4
|
+
// ==================== Mutation Data Collector ====================
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Interface for custom mutation data collectors.
|
|
8
|
+
* Used to build proper nested mutations for persistence operations.
|
|
9
|
+
*/
|
|
10
|
+
export interface MutationDataCollector {
|
|
11
|
+
/**
|
|
12
|
+
* Collects mutation data for updating an existing entity.
|
|
13
|
+
* Returns the mutation input or null if no changes.
|
|
14
|
+
*/
|
|
15
|
+
collectUpdateData(entityType: string, entityId: string): Record<string, unknown> | null
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Collects mutation data for creating a new entity.
|
|
19
|
+
* Returns the mutation input or null if no data.
|
|
20
|
+
*/
|
|
21
|
+
collectCreateData?(entityType: string, entityId: string): Record<string, unknown> | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ==================== Persist Scope ====================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Scope that encompasses all dirty entities
|
|
28
|
+
*/
|
|
29
|
+
export interface AllScope {
|
|
30
|
+
readonly type: 'all'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Scope for a single entity
|
|
35
|
+
*/
|
|
36
|
+
export interface EntityScope {
|
|
37
|
+
readonly type: 'entity'
|
|
38
|
+
readonly entityType: string
|
|
39
|
+
readonly entityId: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Scope for specific fields of an entity
|
|
44
|
+
*/
|
|
45
|
+
export interface FieldsScope {
|
|
46
|
+
readonly type: 'fields'
|
|
47
|
+
readonly entityType: string
|
|
48
|
+
readonly entityId: string
|
|
49
|
+
readonly fields: readonly string[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Scope for a specific relation of an entity
|
|
54
|
+
*/
|
|
55
|
+
export interface RelationScope {
|
|
56
|
+
readonly type: 'relation'
|
|
57
|
+
readonly entityType: string
|
|
58
|
+
readonly entityId: string
|
|
59
|
+
readonly relationName: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Custom scope with explicit entity list
|
|
64
|
+
*/
|
|
65
|
+
export interface CustomScope {
|
|
66
|
+
readonly type: 'custom'
|
|
67
|
+
readonly entities: ReadonlyArray<{
|
|
68
|
+
readonly entityType: string
|
|
69
|
+
readonly entityId: string
|
|
70
|
+
readonly fields?: readonly string[]
|
|
71
|
+
}>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Union of all persist scope types
|
|
76
|
+
*/
|
|
77
|
+
export type PersistScope = AllScope | EntityScope | FieldsScope | RelationScope | CustomScope
|
|
78
|
+
|
|
79
|
+
// ==================== Persistence Results ====================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Result for a single field persist operation
|
|
83
|
+
*/
|
|
84
|
+
export interface FieldPersistResult {
|
|
85
|
+
readonly entityType: string
|
|
86
|
+
readonly entityId: string
|
|
87
|
+
readonly fieldName: string
|
|
88
|
+
readonly success: boolean
|
|
89
|
+
readonly error?: FieldError
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Result for a single entity persist operation
|
|
94
|
+
*/
|
|
95
|
+
export interface EntityPersistResult {
|
|
96
|
+
readonly entityType: string
|
|
97
|
+
readonly entityId: string
|
|
98
|
+
readonly operation: 'create' | 'update' | 'delete'
|
|
99
|
+
readonly success: boolean
|
|
100
|
+
readonly error?: PersistError
|
|
101
|
+
readonly fieldResults?: readonly FieldPersistResult[]
|
|
102
|
+
/** Server-assigned ID for creates (when tempId was used) */
|
|
103
|
+
readonly persistedId?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Error information for failed persist operations
|
|
108
|
+
*/
|
|
109
|
+
export interface PersistError {
|
|
110
|
+
readonly message: string
|
|
111
|
+
readonly mutationResult?: ContemberMutationResult
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Result of a batch/transaction persist operation
|
|
116
|
+
*/
|
|
117
|
+
export interface PersistenceResult {
|
|
118
|
+
/** Whether the entire operation succeeded */
|
|
119
|
+
readonly success: boolean
|
|
120
|
+
/** Results for each entity in the operation */
|
|
121
|
+
readonly results: readonly EntityPersistResult[]
|
|
122
|
+
/** Number of successfully persisted entities */
|
|
123
|
+
readonly successCount: number
|
|
124
|
+
/** Number of failed entities */
|
|
125
|
+
readonly failedCount: number
|
|
126
|
+
/** Number of entities skipped (e.g., already in-flight) */
|
|
127
|
+
readonly skippedCount: number
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ==================== Transaction Types ====================
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A single mutation in a transaction
|
|
134
|
+
*/
|
|
135
|
+
export interface TransactionMutation {
|
|
136
|
+
readonly entityType: string
|
|
137
|
+
readonly entityId: string
|
|
138
|
+
readonly operation: 'create' | 'update' | 'delete'
|
|
139
|
+
readonly data?: Record<string, unknown>
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Result of a single mutation in a transaction
|
|
144
|
+
*/
|
|
145
|
+
export interface TransactionMutationResult {
|
|
146
|
+
readonly entityType: string
|
|
147
|
+
readonly entityId: string
|
|
148
|
+
readonly ok: boolean
|
|
149
|
+
/** Server-assigned ID for creates */
|
|
150
|
+
readonly persistedId?: string
|
|
151
|
+
readonly errorMessage?: string
|
|
152
|
+
readonly mutationResult?: ContemberMutationResult
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Result of a transaction persist operation
|
|
157
|
+
*/
|
|
158
|
+
export interface TransactionResult {
|
|
159
|
+
/** Whether all mutations succeeded */
|
|
160
|
+
readonly ok: boolean
|
|
161
|
+
/** Results per mutation, in same order as input */
|
|
162
|
+
readonly results: readonly TransactionMutationResult[]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ==================== Options ====================
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Update mode for persistence operations.
|
|
169
|
+
* - 'optimistic': Update UI immediately, revert on failure (default)
|
|
170
|
+
* - 'pessimistic': Wait for server confirmation before updating UI
|
|
171
|
+
*/
|
|
172
|
+
export type UpdateMode = 'optimistic' | 'pessimistic'
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Options for batch persist operations
|
|
176
|
+
*/
|
|
177
|
+
export interface BatchPersistOptions {
|
|
178
|
+
/**
|
|
179
|
+
* Skip entities that are currently in-flight
|
|
180
|
+
* @default true
|
|
181
|
+
*/
|
|
182
|
+
readonly skipInFlight?: boolean
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* AbortSignal for cancellation
|
|
186
|
+
*/
|
|
187
|
+
readonly signal?: AbortSignal
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Automatically rollback optimistic changes when persistence fails.
|
|
191
|
+
* When enabled, entity data, relations, and has-many lists are reverted
|
|
192
|
+
* to their server state on failure.
|
|
193
|
+
* @default false
|
|
194
|
+
*/
|
|
195
|
+
readonly rollbackOnError?: boolean
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Update mode for this operation.
|
|
199
|
+
* - 'optimistic': Changes shown in UI immediately (default)
|
|
200
|
+
* - 'pessimistic': Wait for server confirmation before showing in UI
|
|
201
|
+
*
|
|
202
|
+
* When using pessimistic mode:
|
|
203
|
+
* - Changes are staged but not visible in UI until server confirms
|
|
204
|
+
* - If server fails, staged changes are discarded (no UI change occurs)
|
|
205
|
+
* - Loading state is properly tracked via isPersisting
|
|
206
|
+
*
|
|
207
|
+
* @default Uses provider-level default or 'optimistic'
|
|
208
|
+
*/
|
|
209
|
+
readonly updateMode?: UpdateMode
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Called for each entity before persistence
|
|
213
|
+
*/
|
|
214
|
+
readonly onEntityPersisting?: (entityType: string, entityId: string) => void | Promise<void>
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Called for each entity after persistence (success or failure)
|
|
218
|
+
*/
|
|
219
|
+
readonly onEntityPersisted?: (result: EntityPersistResult) => void
|
|
220
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types matching the Contember API schema introspection format.
|
|
3
|
+
* These types are compatible with binding-common's Schema.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type SchemaColumnType = 'Bool' | 'Date' | 'DateTime' | 'Double' | 'Enum' | 'Integer' | 'String' | 'Uuid' | 'Json' | string
|
|
7
|
+
|
|
8
|
+
export interface SchemaColumn {
|
|
9
|
+
__typename: '_Column'
|
|
10
|
+
name: string
|
|
11
|
+
nullable: boolean
|
|
12
|
+
type: SchemaColumnType
|
|
13
|
+
enumName: string | null
|
|
14
|
+
defaultValue: string | number | boolean | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SchemaRelationOrderBy {
|
|
18
|
+
path: string[]
|
|
19
|
+
direction: 'asc' | 'desc'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface BaseRelation {
|
|
23
|
+
__typename: '_Relation'
|
|
24
|
+
name: string
|
|
25
|
+
nullable: boolean | null
|
|
26
|
+
onDelete: 'restrict' | 'cascade' | 'setNull' | null
|
|
27
|
+
orderBy: SchemaRelationOrderBy[] | null
|
|
28
|
+
orphanRemoval: boolean | null
|
|
29
|
+
targetEntity: string
|
|
30
|
+
type: 'OneHasOne' | 'OneHasMany' | 'ManyHasOne' | 'ManyHasMany'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface OwningRelation extends BaseRelation {
|
|
34
|
+
side: 'owning'
|
|
35
|
+
inversedBy: string | null
|
|
36
|
+
ownedBy?: never
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface InverseRelation extends BaseRelation {
|
|
40
|
+
side: 'inverse'
|
|
41
|
+
ownedBy: string
|
|
42
|
+
inversedBy?: never
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type SchemaRelation = OwningRelation | InverseRelation
|
|
46
|
+
|
|
47
|
+
export type SchemaField = SchemaColumn | SchemaRelation
|
|
48
|
+
|
|
49
|
+
export interface SchemaEntity {
|
|
50
|
+
name: string
|
|
51
|
+
customPrimaryAllowed: boolean
|
|
52
|
+
fields: Map<string, SchemaField>
|
|
53
|
+
unique: { fields: Set<string> }[]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SchemaEnum {
|
|
57
|
+
name: string
|
|
58
|
+
values: string[]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Processed schema store with Maps for efficient lookup.
|
|
63
|
+
* This is the format used internally after loading from API.
|
|
64
|
+
*/
|
|
65
|
+
export interface ContemberSchemaStore {
|
|
66
|
+
entities: Map<string, SchemaEntity>
|
|
67
|
+
enums: Map<string, Set<string>>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Raw schema format as returned by the Contember API.
|
|
72
|
+
*/
|
|
73
|
+
export interface RawContemberSchema {
|
|
74
|
+
enums: { name: string; values: string[] }[]
|
|
75
|
+
entities: {
|
|
76
|
+
name: string
|
|
77
|
+
customPrimaryAllowed: boolean
|
|
78
|
+
fields: (SchemaColumn | (Omit<SchemaRelation, 'side'> & { side: 'owning' | 'inverse'; ownedBy?: string; inversedBy?: string | null }))[]
|
|
79
|
+
unique: { fields: string[] }[]
|
|
80
|
+
}[]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Schema class compatible with binding-common's Schema.
|
|
85
|
+
* Can be used directly or created from API response.
|
|
86
|
+
*/
|
|
87
|
+
export class ContemberSchema {
|
|
88
|
+
constructor(private readonly store: ContemberSchemaStore) {}
|
|
89
|
+
|
|
90
|
+
getEntity(entityName: string): SchemaEntity | undefined {
|
|
91
|
+
return this.store.entities.get(entityName)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getEntityNames(): string[] {
|
|
95
|
+
return Array.from(this.store.entities.keys())
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getEntityField(entityName: string, fieldName: string): SchemaField | undefined {
|
|
99
|
+
return this.store.entities.get(entityName)?.fields.get(fieldName)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getEnumValues(enumName: string): string[] | undefined {
|
|
103
|
+
const values = this.store.enums.get(enumName)
|
|
104
|
+
return values ? Array.from(values) : undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates a ContemberSchema from raw API response.
|
|
109
|
+
*/
|
|
110
|
+
static fromRaw(raw: RawContemberSchema): ContemberSchema {
|
|
111
|
+
const enums = new Map<string, Set<string>>()
|
|
112
|
+
for (const { name, values } of raw.enums) {
|
|
113
|
+
enums.set(name, new Set(values))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const entities = new Map<string, SchemaEntity>()
|
|
117
|
+
for (const entity of raw.entities) {
|
|
118
|
+
const fields = new Map<string, SchemaField>()
|
|
119
|
+
for (const field of entity.fields) {
|
|
120
|
+
fields.set(field.name, field as SchemaField)
|
|
121
|
+
}
|
|
122
|
+
entities.set(entity.name, {
|
|
123
|
+
name: entity.name,
|
|
124
|
+
customPrimaryAllowed: entity.customPrimaryAllowed,
|
|
125
|
+
fields,
|
|
126
|
+
unique: entity.unique.map(u => ({ fields: new Set(u.fields) })),
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return new ContemberSchema({ entities, enums })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads schema from Contember Content API via GraphQL introspection.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ContemberSchema, type RawContemberSchema } from './ContemberSchema.js'
|
|
6
|
+
|
|
7
|
+
export interface SchemaLoaderClient {
|
|
8
|
+
execute<T>(query: string, options?: unknown): Promise<T>
|
|
9
|
+
apiUrl?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const SCHEMA_QUERY = `query {
|
|
13
|
+
schema {
|
|
14
|
+
enums {
|
|
15
|
+
name
|
|
16
|
+
values
|
|
17
|
+
}
|
|
18
|
+
entities {
|
|
19
|
+
name
|
|
20
|
+
customPrimaryAllowed
|
|
21
|
+
unique {
|
|
22
|
+
fields
|
|
23
|
+
}
|
|
24
|
+
fields {
|
|
25
|
+
__typename
|
|
26
|
+
name
|
|
27
|
+
type
|
|
28
|
+
nullable
|
|
29
|
+
... on _Column {
|
|
30
|
+
enumName
|
|
31
|
+
defaultValue
|
|
32
|
+
}
|
|
33
|
+
... on _Relation {
|
|
34
|
+
side
|
|
35
|
+
targetEntity
|
|
36
|
+
ownedBy
|
|
37
|
+
inversedBy
|
|
38
|
+
onDelete
|
|
39
|
+
orphanRemoval
|
|
40
|
+
orderBy {
|
|
41
|
+
path
|
|
42
|
+
direction
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}`
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Loads and caches schema from Contember Content API.
|
|
52
|
+
* Instance-level cache avoids cross-tenant collisions and test pollution.
|
|
53
|
+
*/
|
|
54
|
+
export class SchemaLoader {
|
|
55
|
+
private readonly cache = new Map<string, Promise<ContemberSchema>>()
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Loads schema from the API, with caching per API URL.
|
|
59
|
+
*/
|
|
60
|
+
async loadSchema(
|
|
61
|
+
client: SchemaLoaderClient,
|
|
62
|
+
options?: unknown,
|
|
63
|
+
): Promise<ContemberSchema> {
|
|
64
|
+
const cacheKey = client.apiUrl ?? 'default'
|
|
65
|
+
|
|
66
|
+
const existing = this.cache.get(cacheKey)
|
|
67
|
+
if (existing) {
|
|
68
|
+
return existing
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const promise = (async () => {
|
|
72
|
+
const response = await client.execute<{ schema: RawContemberSchema }>(SCHEMA_QUERY, options)
|
|
73
|
+
return ContemberSchema.fromRaw(response.schema)
|
|
74
|
+
})()
|
|
75
|
+
|
|
76
|
+
this.cache.set(cacheKey, promise)
|
|
77
|
+
return promise
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Clears the schema cache.
|
|
82
|
+
*/
|
|
83
|
+
clearCache(): void {
|
|
84
|
+
this.cache.clear()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Clears cache for a specific API URL.
|
|
89
|
+
*/
|
|
90
|
+
clearCacheFor(apiUrl: string): void {
|
|
91
|
+
this.cache.delete(apiUrl)
|
|
92
|
+
}
|
|
93
|
+
}
|