@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.
Files changed (234) hide show
  1. package/dist/adapter/ContemberAdapter.d.ts +40 -0
  2. package/dist/adapter/ContemberAdapter.d.ts.map +1 -0
  3. package/dist/adapter/MockAdapter.d.ts +81 -0
  4. package/dist/adapter/MockAdapter.d.ts.map +1 -0
  5. package/dist/adapter/MockQueryEngine.d.ts +43 -0
  6. package/dist/adapter/MockQueryEngine.d.ts.map +1 -0
  7. package/dist/adapter/index.d.ts +5 -0
  8. package/dist/adapter/index.d.ts.map +1 -0
  9. package/dist/adapter/types.d.ts +184 -0
  10. package/dist/adapter/types.d.ts.map +1 -0
  11. package/dist/brand/ComponentBrand.d.ts +25 -0
  12. package/dist/brand/ComponentBrand.d.ts.map +1 -0
  13. package/dist/contember/index.d.ts +10 -0
  14. package/dist/contember/index.d.ts.map +1 -0
  15. package/dist/core/ActionDispatcher.d.ts +64 -0
  16. package/dist/core/ActionDispatcher.d.ts.map +1 -0
  17. package/dist/core/Disposable.d.ts +57 -0
  18. package/dist/core/Disposable.d.ts.map +1 -0
  19. package/dist/core/EntityLoader.d.ts +80 -0
  20. package/dist/core/EntityLoader.d.ts.map +1 -0
  21. package/dist/core/PersistenceManager.d.ts +126 -0
  22. package/dist/core/PersistenceManager.d.ts.map +1 -0
  23. package/dist/core/SelectionResolver.d.ts +21 -0
  24. package/dist/core/SelectionResolver.d.ts.map +1 -0
  25. package/dist/core/actionClassification.d.ts +48 -0
  26. package/dist/core/actionClassification.d.ts.map +1 -0
  27. package/dist/core/actions.d.ts +301 -0
  28. package/dist/core/actions.d.ts.map +1 -0
  29. package/dist/core/index.d.ts +4 -0
  30. package/dist/core/index.d.ts.map +1 -0
  31. package/dist/dataview/filterHandlers.d.ts +18 -0
  32. package/dist/dataview/filterHandlers.d.ts.map +1 -0
  33. package/dist/dataview/index.d.ts +3 -0
  34. package/dist/dataview/index.d.ts.map +1 -0
  35. package/dist/dataview/types.d.ts +164 -0
  36. package/dist/dataview/types.d.ts.map +1 -0
  37. package/dist/errors/UnfetchedFieldError.d.ts +11 -0
  38. package/dist/errors/UnfetchedFieldError.d.ts.map +1 -0
  39. package/dist/errors/errorPathResolver.d.ts +52 -0
  40. package/dist/errors/errorPathResolver.d.ts.map +1 -0
  41. package/dist/errors/index.d.ts +5 -0
  42. package/dist/errors/index.d.ts.map +1 -0
  43. package/dist/errors/pathMapper.d.ts +42 -0
  44. package/dist/errors/pathMapper.d.ts.map +1 -0
  45. package/dist/errors/types.d.ts +131 -0
  46. package/dist/errors/types.d.ts.map +1 -0
  47. package/dist/events/EventEmitter.d.ts +97 -0
  48. package/dist/events/EventEmitter.d.ts.map +1 -0
  49. package/dist/events/eventFactory.d.ts +40 -0
  50. package/dist/events/eventFactory.d.ts.map +1 -0
  51. package/dist/events/index.d.ts +15 -0
  52. package/dist/events/index.d.ts.map +1 -0
  53. package/dist/events/types.d.ts +264 -0
  54. package/dist/events/types.d.ts.map +1 -0
  55. package/dist/handles/BaseHandle.d.ts +64 -0
  56. package/dist/handles/BaseHandle.d.ts.map +1 -0
  57. package/dist/handles/EntityHandle.d.ts +208 -0
  58. package/dist/handles/EntityHandle.d.ts.map +1 -0
  59. package/dist/handles/FieldHandle.d.ts +111 -0
  60. package/dist/handles/FieldHandle.d.ts.map +1 -0
  61. package/dist/handles/HasManyListHandle.d.ts +166 -0
  62. package/dist/handles/HasManyListHandle.d.ts.map +1 -0
  63. package/dist/handles/HasOneHandle.d.ts +168 -0
  64. package/dist/handles/HasOneHandle.d.ts.map +1 -0
  65. package/dist/handles/PlaceholderHandle.d.ts +117 -0
  66. package/dist/handles/PlaceholderHandle.d.ts.map +1 -0
  67. package/dist/handles/index.d.ts +8 -0
  68. package/dist/handles/index.d.ts.map +1 -0
  69. package/dist/handles/proxyFactory.d.ts +66 -0
  70. package/dist/handles/proxyFactory.d.ts.map +1 -0
  71. package/dist/handles/types.d.ts +430 -0
  72. package/dist/handles/types.d.ts.map +1 -0
  73. package/dist/index.d.ts +53 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/internal.d.ts +11 -0
  76. package/dist/internal.d.ts.map +1 -0
  77. package/dist/persistence/BatchPersister.d.ts +142 -0
  78. package/dist/persistence/BatchPersister.d.ts.map +1 -0
  79. package/dist/persistence/ChangeRegistry.d.ts +87 -0
  80. package/dist/persistence/ChangeRegistry.d.ts.map +1 -0
  81. package/dist/persistence/ContemberSchemaMutationAdapter.d.ts +16 -0
  82. package/dist/persistence/ContemberSchemaMutationAdapter.d.ts.map +1 -0
  83. package/dist/persistence/MutationCollector.d.ts +96 -0
  84. package/dist/persistence/MutationCollector.d.ts.map +1 -0
  85. package/dist/persistence/MutationSchemaProvider.d.ts +30 -0
  86. package/dist/persistence/MutationSchemaProvider.d.ts.map +1 -0
  87. package/dist/persistence/index.d.ts +7 -0
  88. package/dist/persistence/index.d.ts.map +1 -0
  89. package/dist/persistence/types.d.ts +189 -0
  90. package/dist/persistence/types.d.ts.map +1 -0
  91. package/dist/roles/RoleSchemaRegistry.d.ts +77 -0
  92. package/dist/roles/RoleSchemaRegistry.d.ts.map +1 -0
  93. package/dist/roles/index.d.ts +11 -0
  94. package/dist/roles/index.d.ts.map +1 -0
  95. package/dist/roles/types.d.ts +242 -0
  96. package/dist/roles/types.d.ts.map +1 -0
  97. package/dist/schema/ContemberSchema.d.ts +98 -0
  98. package/dist/schema/ContemberSchema.d.ts.map +1 -0
  99. package/dist/schema/SchemaLoader.d.ts +28 -0
  100. package/dist/schema/SchemaLoader.d.ts.map +1 -0
  101. package/dist/schema/SchemaRegistry.d.ts +95 -0
  102. package/dist/schema/SchemaRegistry.d.ts.map +1 -0
  103. package/dist/schema/index.d.ts +6 -0
  104. package/dist/schema/index.d.ts.map +1 -0
  105. package/dist/schema/roles.d.ts +62 -0
  106. package/dist/schema/roles.d.ts.map +1 -0
  107. package/dist/schema/types.d.ts +179 -0
  108. package/dist/schema/types.d.ts.map +1 -0
  109. package/dist/selection/SelectionMetaCollector.d.ts +32 -0
  110. package/dist/selection/SelectionMetaCollector.d.ts.map +1 -0
  111. package/dist/selection/SelectionScope.d.ts +77 -0
  112. package/dist/selection/SelectionScope.d.ts.map +1 -0
  113. package/dist/selection/buildQuery.d.ts +45 -0
  114. package/dist/selection/buildQuery.d.ts.map +1 -0
  115. package/dist/selection/createFragment.d.ts +29 -0
  116. package/dist/selection/createFragment.d.ts.map +1 -0
  117. package/dist/selection/createSelectionBuilder.d.ts +18 -0
  118. package/dist/selection/createSelectionBuilder.d.ts.map +1 -0
  119. package/dist/selection/index.d.ts +8 -0
  120. package/dist/selection/index.d.ts.map +1 -0
  121. package/dist/selection/queryTypes.d.ts +84 -0
  122. package/dist/selection/queryTypes.d.ts.map +1 -0
  123. package/dist/selection/types.d.ts +238 -0
  124. package/dist/selection/types.d.ts.map +1 -0
  125. package/dist/store/DirtyTracker.d.ts +26 -0
  126. package/dist/store/DirtyTracker.d.ts.map +1 -0
  127. package/dist/store/EntityMetaStore.d.ts +52 -0
  128. package/dist/store/EntityMetaStore.d.ts.map +1 -0
  129. package/dist/store/EntitySnapshotStore.d.ts +63 -0
  130. package/dist/store/EntitySnapshotStore.d.ts.map +1 -0
  131. package/dist/store/ErrorStore.d.ts +82 -0
  132. package/dist/store/ErrorStore.d.ts.map +1 -0
  133. package/dist/store/RelationStore.d.ts +175 -0
  134. package/dist/store/RelationStore.d.ts.map +1 -0
  135. package/dist/store/SnapshotStore.d.ts +143 -0
  136. package/dist/store/SnapshotStore.d.ts.map +1 -0
  137. package/dist/store/SubscriptionManager.d.ts +90 -0
  138. package/dist/store/SubscriptionManager.d.ts.map +1 -0
  139. package/dist/store/TouchedStore.d.ts +24 -0
  140. package/dist/store/TouchedStore.d.ts.map +1 -0
  141. package/dist/store/entityId.d.ts +29 -0
  142. package/dist/store/entityId.d.ts.map +1 -0
  143. package/dist/store/index.d.ts +1 -0
  144. package/dist/store/index.d.ts.map +1 -0
  145. package/dist/store/snapshots.d.ts +144 -0
  146. package/dist/store/snapshots.d.ts.map +1 -0
  147. package/dist/undo/UndoManager.d.ts +120 -0
  148. package/dist/undo/UndoManager.d.ts.map +1 -0
  149. package/dist/undo/index.d.ts +3 -0
  150. package/dist/undo/index.d.ts.map +1 -0
  151. package/dist/undo/types.d.ts +74 -0
  152. package/dist/undo/types.d.ts.map +1 -0
  153. package/dist/utils/aliasGenerator.d.ts +25 -0
  154. package/dist/utils/aliasGenerator.d.ts.map +1 -0
  155. package/dist/utils/deepEqual.d.ts +6 -0
  156. package/dist/utils/deepEqual.d.ts.map +1 -0
  157. package/dist/utils/index.d.ts +3 -0
  158. package/dist/utils/index.d.ts.map +1 -0
  159. package/package.json +29 -0
  160. package/src/adapter/ContemberAdapter.ts +239 -0
  161. package/src/adapter/MockAdapter.ts +394 -0
  162. package/src/adapter/MockQueryEngine.ts +236 -0
  163. package/src/adapter/index.ts +21 -0
  164. package/src/adapter/types.ts +199 -0
  165. package/src/brand/ComponentBrand.ts +28 -0
  166. package/src/contember/index.ts +17 -0
  167. package/src/core/ActionDispatcher.ts +450 -0
  168. package/src/core/Disposable.ts +126 -0
  169. package/src/core/EntityLoader.ts +169 -0
  170. package/src/core/SelectionResolver.ts +78 -0
  171. package/src/core/actionClassification.ts +196 -0
  172. package/src/core/actions.ts +491 -0
  173. package/src/core/index.ts +20 -0
  174. package/src/dataview/filterHandlers.ts +443 -0
  175. package/src/dataview/index.ts +36 -0
  176. package/src/dataview/types.ts +220 -0
  177. package/src/errors/UnfetchedFieldError.ts +18 -0
  178. package/src/errors/errorPathResolver.ts +239 -0
  179. package/src/errors/index.ts +40 -0
  180. package/src/errors/pathMapper.ts +40 -0
  181. package/src/errors/types.ts +223 -0
  182. package/src/events/EventEmitter.ts +447 -0
  183. package/src/events/eventFactory.ts +430 -0
  184. package/src/events/index.ts +89 -0
  185. package/src/events/types.ts +374 -0
  186. package/src/handles/BaseHandle.ts +100 -0
  187. package/src/handles/EntityHandle.ts +538 -0
  188. package/src/handles/FieldHandle.ts +268 -0
  189. package/src/handles/HasManyListHandle.ts +500 -0
  190. package/src/handles/HasOneHandle.ts +500 -0
  191. package/src/handles/PlaceholderHandle.ts +321 -0
  192. package/src/handles/index.ts +37 -0
  193. package/src/handles/proxyFactory.ts +218 -0
  194. package/src/handles/types.ts +743 -0
  195. package/src/index.ts +384 -0
  196. package/src/internal.ts +19 -0
  197. package/src/persistence/BatchPersister.ts +911 -0
  198. package/src/persistence/ChangeRegistry.ts +150 -0
  199. package/src/persistence/ContemberSchemaMutationAdapter.ts +57 -0
  200. package/src/persistence/MutationCollector.ts +567 -0
  201. package/src/persistence/MutationSchemaProvider.ts +33 -0
  202. package/src/persistence/index.ts +23 -0
  203. package/src/persistence/types.ts +220 -0
  204. package/src/schema/ContemberSchema.ts +132 -0
  205. package/src/schema/SchemaLoader.ts +93 -0
  206. package/src/schema/SchemaRegistry.ts +286 -0
  207. package/src/schema/index.ts +51 -0
  208. package/src/schema/roles.ts +83 -0
  209. package/src/schema/types.ts +238 -0
  210. package/src/selection/SelectionMetaCollector.ts +122 -0
  211. package/src/selection/SelectionScope.ts +208 -0
  212. package/src/selection/buildQuery.ts +134 -0
  213. package/src/selection/createFragment.ts +50 -0
  214. package/src/selection/createSelectionBuilder.ts +283 -0
  215. package/src/selection/index.ts +31 -0
  216. package/src/selection/queryTypes.ts +135 -0
  217. package/src/selection/types.ts +320 -0
  218. package/src/store/DirtyTracker.ts +117 -0
  219. package/src/store/EntityMetaStore.ts +143 -0
  220. package/src/store/EntitySnapshotStore.ts +260 -0
  221. package/src/store/ErrorStore.ts +251 -0
  222. package/src/store/RelationStore.ts +696 -0
  223. package/src/store/SnapshotStore.ts +765 -0
  224. package/src/store/SubscriptionManager.ts +241 -0
  225. package/src/store/TouchedStore.ts +42 -0
  226. package/src/store/entityId.ts +43 -0
  227. package/src/store/index.ts +2 -0
  228. package/src/store/snapshots.ts +203 -0
  229. package/src/undo/UndoManager.ts +418 -0
  230. package/src/undo/index.ts +9 -0
  231. package/src/undo/types.ts +84 -0
  232. package/src/utils/aliasGenerator.ts +56 -0
  233. package/src/utils/deepEqual.ts +33 -0
  234. 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
+ }