@contember/bindx 0.1.5 → 0.1.9
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 +11 -14
- package/dist/adapter/ContemberAdapter.d.ts.map +1 -1
- package/dist/brand/ComponentBrand.d.ts +1 -24
- package/dist/brand/ComponentBrand.d.ts.map +1 -1
- package/dist/contember/index.d.ts +1 -2
- package/dist/contember/index.d.ts.map +1 -1
- package/dist/handles/EntityHandle.d.ts +18 -29
- package/dist/handles/EntityHandle.d.ts.map +1 -1
- package/dist/handles/FieldHandle.d.ts +5 -3
- package/dist/handles/FieldHandle.d.ts.map +1 -1
- package/dist/handles/HasManyListHandle.d.ts +4 -3
- package/dist/handles/HasManyListHandle.d.ts.map +1 -1
- package/dist/handles/HasOneHandle.d.ts +33 -36
- package/dist/handles/HasOneHandle.d.ts.map +1 -1
- package/dist/handles/PlaceholderHandle.d.ts +9 -19
- package/dist/handles/PlaceholderHandle.d.ts.map +1 -1
- package/dist/handles/proxyFactory.d.ts +9 -37
- package/dist/handles/proxyFactory.d.ts.map +1 -1
- package/dist/handles/types.d.ts +4 -0
- package/dist/handles/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/persistence/ContemberSchemaMutationAdapter.d.ts +24 -1
- package/dist/persistence/ContemberSchemaMutationAdapter.d.ts.map +1 -1
- package/dist/schema/ContemberSchema.d.ts +1 -97
- package/dist/schema/ContemberSchema.d.ts.map +1 -1
- package/dist/schema/SchemaLoader.d.ts +1 -27
- package/dist/schema/SchemaLoader.d.ts.map +1 -1
- package/dist/schema/SchemaRegistry.d.ts +1 -94
- package/dist/schema/SchemaRegistry.d.ts.map +1 -1
- package/dist/schema/roles.d.ts +1 -61
- package/dist/schema/roles.d.ts.map +1 -1
- package/dist/schema/types.d.ts +1 -178
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/selection/SelectionMetaCollector.d.ts +1 -31
- package/dist/selection/SelectionMetaCollector.d.ts.map +1 -1
- package/dist/selection/SelectionScope.d.ts +1 -76
- package/dist/selection/SelectionScope.d.ts.map +1 -1
- package/dist/selection/buildQuery.d.ts +1 -44
- package/dist/selection/buildQuery.d.ts.map +1 -1
- package/dist/selection/createFragment.d.ts +1 -28
- package/dist/selection/createFragment.d.ts.map +1 -1
- package/dist/selection/createSelectionBuilder.d.ts +1 -17
- package/dist/selection/createSelectionBuilder.d.ts.map +1 -1
- package/dist/selection/queryTypes.d.ts +1 -83
- package/dist/selection/queryTypes.d.ts.map +1 -1
- package/dist/selection/types.d.ts +1 -237
- package/dist/selection/types.d.ts.map +1 -1
- package/dist/utils/aliasGenerator.d.ts +1 -24
- package/dist/utils/aliasGenerator.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/adapter/ContemberAdapter.ts +193 -123
- package/src/brand/ComponentBrand.ts +1 -28
- package/src/contember/index.ts +1 -8
- package/src/handles/EntityHandle.ts +131 -61
- package/src/handles/FieldHandle.ts +19 -3
- package/src/handles/HasManyListHandle.ts +15 -12
- package/src/handles/HasOneHandle.ts +98 -66
- package/src/handles/PlaceholderHandle.ts +26 -24
- package/src/handles/proxyFactory.ts +29 -128
- package/src/handles/types.ts +6 -0
- package/src/index.ts +22 -3
- package/src/persistence/ContemberSchemaMutationAdapter.ts +19 -1
- package/src/schema/ContemberSchema.ts +14 -132
- package/src/schema/SchemaLoader.ts +1 -93
- package/src/schema/SchemaRegistry.ts +1 -286
- package/src/schema/roles.ts +10 -83
- package/src/schema/types.ts +21 -238
- package/src/selection/SelectionMetaCollector.ts +1 -122
- package/src/selection/SelectionScope.ts +1 -208
- package/src/selection/buildQuery.ts +1 -134
- package/src/selection/createFragment.ts +1 -50
- package/src/selection/createSelectionBuilder.ts +1 -283
- package/src/selection/queryTypes.ts +8 -135
- package/src/selection/types.ts +13 -320
- package/src/utils/aliasGenerator.ts +1 -56
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { GraphQlClient } from '@contember/graphql-client'
|
|
2
1
|
import {
|
|
3
2
|
ContentClient,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
type ContentClientOptions,
|
|
4
|
+
ContentOperation,
|
|
5
|
+
type ContentQuery,
|
|
6
|
+
querySpecToGraphQl,
|
|
7
|
+
type QuerySpecContext,
|
|
8
|
+
buildGetArgs,
|
|
9
|
+
buildListArgs,
|
|
10
|
+
buildCreateArgs,
|
|
11
|
+
buildUpdateArgs,
|
|
12
|
+
buildDeleteArgs,
|
|
13
|
+
buildMutationSelection,
|
|
14
|
+
mutationFragments,
|
|
15
|
+
unwrapPaginateResult,
|
|
16
|
+
} from '@contember/bindx-client'
|
|
17
|
+
import type { GraphQlClient } from '@contember/graphql-client'
|
|
14
18
|
import type { QuerySpec, QueryFieldSpec } from '../selection/buildQuery.js'
|
|
15
19
|
import type { BackendAdapter, Query, QueryResult, QueryOptions, GetQuery, ListQuery, PersistResult, CreateResult, DeleteResult } from './types.js'
|
|
16
20
|
import type { ContemberMutationResult } from '../errors/pathMapper.js'
|
|
21
|
+
import type { SchemaRegistry } from '../schema/SchemaRegistry.js'
|
|
22
|
+
import { GraphQlField } from '@contember/graphql-builder'
|
|
17
23
|
|
|
18
24
|
/**
|
|
19
25
|
* Options for ContemberAdapter
|
|
@@ -21,19 +27,19 @@ import type { ContemberMutationResult } from '../errors/pathMapper.js'
|
|
|
21
27
|
export interface ContemberAdapterOptions {
|
|
22
28
|
/** GraphQL client instance */
|
|
23
29
|
client: GraphQlClient
|
|
24
|
-
/**
|
|
25
|
-
|
|
30
|
+
/** Schema registry for relation target resolution */
|
|
31
|
+
schemaRegistry: SchemaRegistry
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
/**
|
|
29
35
|
* Backend adapter for Contember Content API.
|
|
30
|
-
* Uses @contember/client
|
|
36
|
+
* Uses @contember/bindx-client for GraphQL operations.
|
|
31
37
|
*/
|
|
32
38
|
export class ContemberAdapter implements BackendAdapter {
|
|
33
39
|
|
|
34
40
|
constructor(
|
|
35
41
|
private readonly contentClient: ContentClient,
|
|
36
|
-
private readonly
|
|
42
|
+
private readonly schemaRegistry: SchemaRegistry,
|
|
37
43
|
) {
|
|
38
44
|
}
|
|
39
45
|
|
|
@@ -59,34 +65,52 @@ export class ContemberAdapter implements BackendAdapter {
|
|
|
59
65
|
signal: options?.signal,
|
|
60
66
|
})
|
|
61
67
|
|
|
62
|
-
// Map results back to QueryResult array
|
|
63
|
-
// Content client transforms already unwrap paginateRelation → flat arrays
|
|
68
|
+
// Map results back to QueryResult array, unwrapping paginate format
|
|
64
69
|
return queries.map((q, i) => {
|
|
65
70
|
const key = `q${i}`
|
|
66
71
|
const data = (results as Record<string, unknown>)[key]
|
|
67
72
|
|
|
68
73
|
if (q.type === 'get') {
|
|
69
|
-
|
|
74
|
+
const unwrapped = data ? unwrapPaginateFields(data as Record<string, unknown>, q.spec) : null
|
|
75
|
+
return { type: 'get' as const, data: unwrapped }
|
|
70
76
|
} else {
|
|
71
|
-
|
|
77
|
+
const items = (data ?? []) as readonly Record<string, unknown>[]
|
|
78
|
+
return { type: 'list' as const, data: items.map(item => unwrapPaginateFields(item, q.spec)) }
|
|
72
79
|
}
|
|
73
80
|
})
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
private buildGetQuery(query: GetQuery): ContentQuery<unknown> {
|
|
77
|
-
const
|
|
78
|
-
|
|
84
|
+
const context = this.createContext(query.entityType)
|
|
85
|
+
const selectionSet = querySpecToGraphQl(query.spec, context)
|
|
86
|
+
const args = buildGetArgs(query.entityType, { by: query.by })
|
|
87
|
+
|
|
88
|
+
return new ContentOperation(
|
|
89
|
+
'query',
|
|
90
|
+
`get${query.entityType}`,
|
|
91
|
+
args,
|
|
92
|
+
selectionSet,
|
|
93
|
+
value => value,
|
|
94
|
+
)
|
|
79
95
|
}
|
|
80
96
|
|
|
81
97
|
private buildListQuery(query: ListQuery): ContentQuery<unknown> {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
const context = this.createContext(query.entityType)
|
|
99
|
+
const selectionSet = querySpecToGraphQl(query.spec, context)
|
|
100
|
+
const args = buildListArgs(query.entityType, {
|
|
101
|
+
filter: query.filter,
|
|
102
|
+
orderBy: query.orderBy as unknown[],
|
|
86
103
|
limit: query.limit,
|
|
87
104
|
offset: query.offset,
|
|
88
|
-
}
|
|
89
|
-
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return new ContentOperation(
|
|
108
|
+
'query',
|
|
109
|
+
`list${query.entityType}`,
|
|
110
|
+
args,
|
|
111
|
+
selectionSet,
|
|
112
|
+
value => value,
|
|
113
|
+
)
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
async persist(
|
|
@@ -94,84 +118,117 @@ export class ContemberAdapter implements BackendAdapter {
|
|
|
94
118
|
id: string,
|
|
95
119
|
changes: Record<string, unknown>,
|
|
96
120
|
): Promise<PersistResult> {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
data: changes as Input.UpdateDataInput,
|
|
100
|
-
})
|
|
121
|
+
const args = buildUpdateArgs(entityType, { by: { id }, data: changes })
|
|
122
|
+
const selectionSet = buildMutationSelection('update')
|
|
101
123
|
|
|
102
|
-
const
|
|
124
|
+
const mutation = new ContentOperation(
|
|
125
|
+
'mutation',
|
|
126
|
+
`update${entityType}`,
|
|
127
|
+
args,
|
|
128
|
+
selectionSet,
|
|
129
|
+
value => value as { ok: boolean; errorMessage: string | null; errors: unknown[]; validation: unknown },
|
|
130
|
+
)
|
|
103
131
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
132
|
+
try {
|
|
133
|
+
const result = await this.contentClient.mutate(mutation)
|
|
134
|
+
return { ok: true }
|
|
135
|
+
} catch (e) {
|
|
136
|
+
if (e instanceof Error && 'result' in e) {
|
|
137
|
+
const mutResult = (e as { result: Record<string, unknown> }).result
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
errorMessage: (mutResult['errorMessage'] as string | null) ?? `Failed to update ${entityType}:${id}`,
|
|
141
|
+
mutationResult: this.toMutationResult(mutResult),
|
|
142
|
+
}
|
|
109
143
|
}
|
|
144
|
+
throw e
|
|
110
145
|
}
|
|
111
|
-
|
|
112
|
-
return { ok: true }
|
|
113
146
|
}
|
|
114
147
|
|
|
115
148
|
async create(
|
|
116
149
|
entityType: string,
|
|
117
150
|
data: Record<string, unknown>,
|
|
118
151
|
): Promise<CreateResult> {
|
|
119
|
-
|
|
120
|
-
|
|
152
|
+
const args = buildCreateArgs(entityType, { data })
|
|
153
|
+
// Select id on created node
|
|
154
|
+
const nodeSelection = [new GraphQlField(null, 'id')]
|
|
155
|
+
const selectionSet = buildMutationSelection('create', nodeSelection)
|
|
121
156
|
|
|
122
|
-
const mutation =
|
|
123
|
-
|
|
157
|
+
const mutation = new ContentOperation(
|
|
158
|
+
'mutation',
|
|
159
|
+
`create${entityType}`,
|
|
160
|
+
args,
|
|
161
|
+
selectionSet,
|
|
162
|
+
value => value as { ok: boolean; errorMessage: string | null; errors: unknown[]; validation: unknown; node: unknown },
|
|
163
|
+
)
|
|
124
164
|
|
|
125
|
-
|
|
165
|
+
try {
|
|
166
|
+
const result = await this.contentClient.mutate(mutation)
|
|
167
|
+
const typedResult = result as { node: Record<string, unknown> | null }
|
|
126
168
|
return {
|
|
127
|
-
ok:
|
|
128
|
-
|
|
129
|
-
mutationResult: this.toMutationResult(result),
|
|
169
|
+
ok: true,
|
|
170
|
+
data: typedResult.node as Record<string, unknown>,
|
|
130
171
|
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (e instanceof Error && 'result' in e) {
|
|
174
|
+
const mutResult = (e as { result: Record<string, unknown> }).result
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
errorMessage: (mutResult['errorMessage'] as string | null) ?? `Failed to create ${entityType}`,
|
|
178
|
+
mutationResult: this.toMutationResult(mutResult),
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
throw e
|
|
136
182
|
}
|
|
137
183
|
}
|
|
138
184
|
|
|
139
185
|
async delete(entityType: string, id: string): Promise<DeleteResult> {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
186
|
+
const args = buildDeleteArgs(entityType, { by: { id } })
|
|
187
|
+
const selectionSet = buildMutationSelection('delete')
|
|
142
188
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
189
|
+
const mutation = new ContentOperation(
|
|
190
|
+
'mutation',
|
|
191
|
+
`delete${entityType}`,
|
|
192
|
+
args,
|
|
193
|
+
selectionSet,
|
|
194
|
+
value => value as { ok: boolean; errorMessage: string | null; errors: unknown[] },
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const result = await this.contentClient.mutate(mutation)
|
|
199
|
+
return { ok: true }
|
|
200
|
+
} catch (e) {
|
|
201
|
+
if (e instanceof Error && 'result' in e) {
|
|
202
|
+
const mutResult = (e as { result: Record<string, unknown> }).result
|
|
203
|
+
return {
|
|
204
|
+
ok: false,
|
|
205
|
+
errorMessage: (mutResult['errorMessage'] as string | null) ?? `Failed to delete ${entityType}:${id}`,
|
|
206
|
+
mutationResult: this.toMutationResult(mutResult),
|
|
207
|
+
}
|
|
148
208
|
}
|
|
209
|
+
throw e
|
|
149
210
|
}
|
|
150
|
-
|
|
151
|
-
return { ok: true }
|
|
152
211
|
}
|
|
153
212
|
|
|
154
213
|
/**
|
|
155
|
-
* Converts
|
|
214
|
+
* Converts raw mutation result to ContemberMutationResult type.
|
|
156
215
|
*/
|
|
157
|
-
private toMutationResult(result: {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
validation: { valid: boolean; errors: Array<{ path: Array<{ field: string } | { index: number; alias: string | null }>; message: { text: string } }> }
|
|
162
|
-
}): ContemberMutationResult {
|
|
216
|
+
private toMutationResult(result: Record<string, unknown>): ContemberMutationResult {
|
|
217
|
+
const errors = (result['errors'] as Array<{ paths: unknown; message: string; type: string }>) ?? []
|
|
218
|
+
const validation = result['validation'] as { valid: boolean; errors: Array<{ path: unknown; message: { text: string } }> } | undefined
|
|
219
|
+
|
|
163
220
|
return {
|
|
164
|
-
ok: result
|
|
165
|
-
errorMessage: result
|
|
166
|
-
errors:
|
|
167
|
-
paths: e.paths
|
|
221
|
+
ok: result['ok'] as boolean,
|
|
222
|
+
errorMessage: result['errorMessage'] as string | null,
|
|
223
|
+
errors: errors.map(e => ({
|
|
224
|
+
paths: e.paths as Array<Array<{ field: string } | { index: number; alias: string | null }>>,
|
|
168
225
|
message: e.message,
|
|
169
226
|
type: e.type,
|
|
170
227
|
})),
|
|
171
228
|
validation: {
|
|
172
|
-
valid:
|
|
173
|
-
errors:
|
|
174
|
-
path: e.path
|
|
229
|
+
valid: validation?.valid ?? true,
|
|
230
|
+
errors: (validation?.errors ?? []).map(e => ({
|
|
231
|
+
path: e.path as Array<{ field: string } | { index: number; alias: string | null }>,
|
|
175
232
|
message: { text: e.message.text },
|
|
176
233
|
})),
|
|
177
234
|
},
|
|
@@ -179,61 +236,74 @@ export class ContemberAdapter implements BackendAdapter {
|
|
|
179
236
|
}
|
|
180
237
|
|
|
181
238
|
/**
|
|
182
|
-
*
|
|
239
|
+
* Creates a QuerySpecContext for an entity, wiring in SchemaRegistry for relation target resolution.
|
|
183
240
|
*/
|
|
184
|
-
private
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return this.applyFieldsToSelection(selection, query.fields)
|
|
241
|
+
private createContext(entityName: string): QuerySpecContext {
|
|
242
|
+
return {
|
|
243
|
+
entityName,
|
|
244
|
+
resolveRelationTarget: (entity, field) => this.schemaRegistry.getRelationTarget(entity, field),
|
|
189
245
|
}
|
|
190
246
|
}
|
|
247
|
+
}
|
|
191
248
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
} else {
|
|
223
|
-
// has-one relation
|
|
224
|
-
const oneArgs: EntitySelectionOneArgs = {
|
|
225
|
-
...(field.name !== fieldName ? { as: field.name } : undefined),
|
|
226
|
-
}
|
|
227
|
-
result = result.$(fieldName, oneArgs, nestedCallback)
|
|
228
|
-
}
|
|
249
|
+
/**
|
|
250
|
+
* Recursively unwraps paginate connection format in entity data.
|
|
251
|
+
*
|
|
252
|
+
* The GraphQL layer uses `paginateFieldName { edges { node { ... } } }` for has-many relations,
|
|
253
|
+
* but the store expects flat arrays keyed by the original field name.
|
|
254
|
+
* This function uses the QuerySpec to identify which fields need unwrapping.
|
|
255
|
+
*/
|
|
256
|
+
function unwrapPaginateFields(data: Record<string, unknown>, spec: QuerySpec): Record<string, unknown> {
|
|
257
|
+
const result: Record<string, unknown> = {}
|
|
258
|
+
|
|
259
|
+
// Copy all fields from data first
|
|
260
|
+
const handledKeys = new Set<string>()
|
|
261
|
+
|
|
262
|
+
for (const field of spec.fields) {
|
|
263
|
+
const fieldName = field.sourcePath[0]
|
|
264
|
+
if (!fieldName) continue
|
|
265
|
+
|
|
266
|
+
if (field.isArray && field.nested) {
|
|
267
|
+
// Has-many field — look for paginate key or alias
|
|
268
|
+
const paginateKey = `paginate${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`
|
|
269
|
+
const alias = field.name !== fieldName ? field.name : null
|
|
270
|
+
const lookupKey = alias ?? paginateKey
|
|
271
|
+
|
|
272
|
+
const value = data[lookupKey]
|
|
273
|
+
handledKeys.add(lookupKey)
|
|
274
|
+
|
|
275
|
+
if (value && typeof value === 'object' && 'edges' in value) {
|
|
276
|
+
const connection = value as { edges: { node: unknown }[]; pageInfo?: { totalCount: number } }
|
|
277
|
+
const items = unwrapPaginateResult(connection, !!field.totalCount) as Record<string, unknown>[]
|
|
278
|
+
result[field.name] = items.map(item => unwrapPaginateFields(item, field.nested!))
|
|
229
279
|
} else {
|
|
230
|
-
|
|
231
|
-
const columnArgs: EntitySelectionColumnArgs | undefined =
|
|
232
|
-
field.name !== fieldName ? { as: field.name } : undefined
|
|
233
|
-
result = result.$(fieldName, columnArgs)
|
|
280
|
+
result[field.name] = value
|
|
234
281
|
}
|
|
282
|
+
} else if (field.nested) {
|
|
283
|
+
// Has-one relation — recurse into nested
|
|
284
|
+
const lookupKey = field.name !== fieldName ? field.name : fieldName
|
|
285
|
+
const value = data[lookupKey]
|
|
286
|
+
handledKeys.add(lookupKey)
|
|
287
|
+
|
|
288
|
+
if (value && typeof value === 'object') {
|
|
289
|
+
result[field.name] = unwrapPaginateFields(value as Record<string, unknown>, field.nested)
|
|
290
|
+
} else {
|
|
291
|
+
result[field.name] = value
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
// Scalar field
|
|
295
|
+
const lookupKey = field.name !== fieldName ? field.name : fieldName
|
|
296
|
+
handledKeys.add(lookupKey)
|
|
297
|
+
result[field.name] = data[lookupKey]
|
|
235
298
|
}
|
|
299
|
+
}
|
|
236
300
|
|
|
237
|
-
|
|
301
|
+
// Preserve any extra fields not in the spec (e.g. 'id' returned by API)
|
|
302
|
+
for (const key of Object.keys(data)) {
|
|
303
|
+
if (!handledKeys.has(key) && !(key in result)) {
|
|
304
|
+
result[key] = data[key]
|
|
305
|
+
}
|
|
238
306
|
}
|
|
307
|
+
|
|
308
|
+
return result
|
|
239
309
|
}
|
|
@@ -1,28 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* ComponentBrand provides unique identity for each component created with createComponent.
|
|
3
|
-
* Each instance has a unique runtime Symbol for brand validation.
|
|
4
|
-
* The class structure itself provides nominal typing in TypeScript.
|
|
5
|
-
*/
|
|
6
|
-
export class ComponentBrand {
|
|
7
|
-
/**
|
|
8
|
-
* Private property to ensure nominal typing.
|
|
9
|
-
* Each class instance is considered a unique type.
|
|
10
|
-
*/
|
|
11
|
-
private readonly __nominal = true
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Runtime symbol for brand validation.
|
|
15
|
-
* Used to check that EntityRef includes required component brands.
|
|
16
|
-
*/
|
|
17
|
-
readonly brandSymbol: symbol
|
|
18
|
-
|
|
19
|
-
constructor(public readonly name: string) {
|
|
20
|
-
this.brandSymbol = Symbol(`component_${name}_${Math.random().toString(36).slice(2)}`)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Type alias for any ComponentBrand.
|
|
26
|
-
* Used as default/base type in generic constraints.
|
|
27
|
-
*/
|
|
28
|
-
export type AnyBrand = ComponentBrand
|
|
1
|
+
export { ComponentBrand, type AnyBrand } from '@contember/bindx-client'
|
package/src/contember/index.ts
CHANGED
|
@@ -6,12 +6,5 @@
|
|
|
6
6
|
|
|
7
7
|
// Re-export unified MutationCollector from persistence
|
|
8
8
|
export { MutationCollector, type EntityMutationResult } from '../persistence/MutationCollector.js'
|
|
9
|
-
export { ContemberSchemaMutationAdapter } from '../persistence/ContemberSchemaMutationAdapter.js'
|
|
9
|
+
export { ContemberSchemaMutationAdapter, type SchemaNames } from '../persistence/ContemberSchemaMutationAdapter.js'
|
|
10
10
|
export type { MutationSchemaProvider } from '../persistence/MutationSchemaProvider.js'
|
|
11
|
-
|
|
12
|
-
// Re-export commonly used types from @contember/client-content
|
|
13
|
-
export type {
|
|
14
|
-
SchemaNames,
|
|
15
|
-
SchemaEntityNames,
|
|
16
|
-
ContentClientInput,
|
|
17
|
-
} from '@contember/client-content'
|