@open-mercato/shared 0.6.7-develop.6758.1.697eade236 → 0.6.7-develop.6768.1.9d2c4efc43
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/.turbo/turbo-build.log +1 -1
- package/dist/lib/bootstrap/dynamicLoader.js +174 -5
- package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/widgets/extension-points.js +96 -0
- package/dist/modules/widgets/extension-points.js.map +7 -0
- package/package.json +2 -2
- package/src/lib/bootstrap/__tests__/dynamicLoader.cacheRecovery.test.ts +38 -5
- package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +37 -4
- package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +292 -0
- package/src/lib/bootstrap/dynamicLoader.ts +239 -7
- package/src/modules/widgets/__tests__/extension-points.test.ts +101 -0
- package/src/modules/widgets/extension-points.ts +421 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CRUD_FORM_EXTENSION_SURFACES,
|
|
3
|
+
CRUD_FORM_LIFECYCLE_PHASES,
|
|
4
|
+
CRUD_FORM_OPERATIONS,
|
|
5
|
+
DATA_TABLE_EXTENSION_SURFACES,
|
|
6
|
+
crudFormExtensionHost,
|
|
7
|
+
crudFormExtensionSpotId,
|
|
8
|
+
dataTableExtensionHost,
|
|
9
|
+
dataTableExtensionSpotId,
|
|
10
|
+
defineModuleExtensionPoints,
|
|
11
|
+
extensionSpotChildId,
|
|
12
|
+
injectionExtensionHost,
|
|
13
|
+
resolveExtensionPointPattern,
|
|
14
|
+
} from '@open-mercato/shared/modules/widgets/extension-points'
|
|
15
|
+
|
|
16
|
+
describe('module extension point declarations', () => {
|
|
17
|
+
it('preserves exact ids and immutable declarations', () => {
|
|
18
|
+
const extensionPoints = defineModuleExtensionPoints({
|
|
19
|
+
moduleId: 'catalog',
|
|
20
|
+
hosts: {
|
|
21
|
+
products: dataTableExtensionHost({
|
|
22
|
+
baseSpotId: 'data-table:catalog.products',
|
|
23
|
+
tableId: 'catalog.products.list',
|
|
24
|
+
source: 'components/products/ProductsDataTable.tsx',
|
|
25
|
+
}),
|
|
26
|
+
productForm: crudFormExtensionHost({
|
|
27
|
+
entityId: 'catalog.product',
|
|
28
|
+
spotId: 'crud-form:catalog.product',
|
|
29
|
+
source: 'backend/catalog/products/[id]/page.tsx',
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
expect(extensionPoints.moduleId).toBe('catalog')
|
|
35
|
+
expect(extensionPoints.hosts.products.tableId).toBe('catalog.products.list')
|
|
36
|
+
expect(Object.isFrozen(extensionPoints)).toBe(true)
|
|
37
|
+
expect(Object.isFrozen(extensionPoints.hosts)).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('requires named parameters for dynamic patterns', () => {
|
|
41
|
+
// @ts-expect-error patterned declarations require named parameters
|
|
42
|
+
expect(() => injectionExtensionHost({
|
|
43
|
+
family: 'integration',
|
|
44
|
+
pattern: 'integrations.detail:{integrationId}',
|
|
45
|
+
supported: ['render-widget'],
|
|
46
|
+
source: 'backend/integrations/[id]/page.tsx',
|
|
47
|
+
})).toThrow('patterned injection extension hosts require named parameters')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('describes only the surfaces bound by DataTable and CrudForm', () => {
|
|
51
|
+
expect(DATA_TABLE_EXTENSION_SURFACES.filter((surface) => surface.bound).map((surface) => surface.key)).toEqual([
|
|
52
|
+
'header',
|
|
53
|
+
'footer',
|
|
54
|
+
'toolbar',
|
|
55
|
+
'searchTrailing',
|
|
56
|
+
'columns',
|
|
57
|
+
'rowActions',
|
|
58
|
+
'bulkActions',
|
|
59
|
+
'filters',
|
|
60
|
+
'replacement',
|
|
61
|
+
])
|
|
62
|
+
expect(DATA_TABLE_EXTENSION_SURFACES.find((surface) => surface.key === 'emptyState')?.bound).toBe(false)
|
|
63
|
+
expect(CRUD_FORM_EXTENSION_SURFACES.filter((surface) => !surface.bound).map((surface) => surface.key)).toEqual([
|
|
64
|
+
'beforeFields',
|
|
65
|
+
'afterFields',
|
|
66
|
+
'footer',
|
|
67
|
+
'sidebar',
|
|
68
|
+
'group',
|
|
69
|
+
'fieldBefore',
|
|
70
|
+
'fieldAfter',
|
|
71
|
+
])
|
|
72
|
+
expect(CRUD_FORM_LIFECYCLE_PHASES).toEqual([
|
|
73
|
+
'transformValidation',
|
|
74
|
+
'transformDisplayData',
|
|
75
|
+
'onBeforeNavigate',
|
|
76
|
+
'onAppEvent',
|
|
77
|
+
'onVisibilityChange',
|
|
78
|
+
'onBeforeDelete',
|
|
79
|
+
'onDelete',
|
|
80
|
+
'onAfterDelete',
|
|
81
|
+
'onDeleteError',
|
|
82
|
+
'onFieldChange',
|
|
83
|
+
'transformFormData',
|
|
84
|
+
'onBeforeSave',
|
|
85
|
+
'onSave',
|
|
86
|
+
'onAfterSave',
|
|
87
|
+
])
|
|
88
|
+
expect(CRUD_FORM_OPERATIONS).toEqual(['create', 'update', 'delete'])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('builds standard family ids without changing punctuation', () => {
|
|
92
|
+
expect(dataTableExtensionSpotId('catalog.products.list', 'columns')).toBe(
|
|
93
|
+
'data-table:catalog.products.list:columns',
|
|
94
|
+
)
|
|
95
|
+
expect(crudFormExtensionSpotId('catalog.product', 'fields')).toBe('crud-form:catalog.product:fields')
|
|
96
|
+
expect(extensionSpotChildId('legacy:custom-host', 'header')).toBe('legacy:custom-host:header')
|
|
97
|
+
expect(resolveExtensionPointPattern('integrations.detail:{integrationId}', {
|
|
98
|
+
integrationId: 'stripe',
|
|
99
|
+
})).toBe('integrations.detail:stripe')
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
export type ExtensionHostFamily =
|
|
2
|
+
| 'generic'
|
|
3
|
+
| 'menu'
|
|
4
|
+
| 'data-table'
|
|
5
|
+
| 'crud-form'
|
|
6
|
+
| 'detail'
|
|
7
|
+
| 'portal-page'
|
|
8
|
+
| 'component-handle'
|
|
9
|
+
| 'entity'
|
|
10
|
+
| 'api-route'
|
|
11
|
+
| 'command'
|
|
12
|
+
| 'event'
|
|
13
|
+
| 'query-lifecycle'
|
|
14
|
+
| 'dashboard'
|
|
15
|
+
| 'notification'
|
|
16
|
+
| 'integration'
|
|
17
|
+
| 'specialized-registry'
|
|
18
|
+
| 'module-override'
|
|
19
|
+
|
|
20
|
+
export type ExtensionHostCapability =
|
|
21
|
+
| 'render-widget'
|
|
22
|
+
| 'headless-widget'
|
|
23
|
+
| 'menu-item'
|
|
24
|
+
| 'column-widget'
|
|
25
|
+
| 'row-action'
|
|
26
|
+
| 'bulk-action'
|
|
27
|
+
| 'filter-widget'
|
|
28
|
+
| 'toolbar-widget'
|
|
29
|
+
| 'field-widget'
|
|
30
|
+
| 'lifecycle-handler'
|
|
31
|
+
| 'component-replacement'
|
|
32
|
+
| 'response-enricher'
|
|
33
|
+
| 'query-enricher'
|
|
34
|
+
| 'api-interceptor'
|
|
35
|
+
| 'command-interceptor'
|
|
36
|
+
| 'mutation-guard'
|
|
37
|
+
| 'entity-extension'
|
|
38
|
+
| 'async-subscriber'
|
|
39
|
+
| 'sync-subscriber'
|
|
40
|
+
| 'browser-client'
|
|
41
|
+
| 'browser-portal'
|
|
42
|
+
| 'registry-contribution'
|
|
43
|
+
| 'module-override'
|
|
44
|
+
|
|
45
|
+
export type ExtensionHostActivation = 'always' | 'host-opt-in' | 'caller-opt-in' | 'feature-gated'
|
|
46
|
+
|
|
47
|
+
export type ExtensionPointPatternParameter = {
|
|
48
|
+
source: string
|
|
49
|
+
pattern?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type ExtensionHostDeclarationBase = {
|
|
53
|
+
source: string
|
|
54
|
+
contextContract?: string
|
|
55
|
+
dataContract?: string
|
|
56
|
+
scopeContract?: string
|
|
57
|
+
runtimeContract?: string
|
|
58
|
+
activation?: ExtensionHostActivation
|
|
59
|
+
aliases?: readonly string[]
|
|
60
|
+
fallbacks?: readonly string[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type InjectionExtensionHostDeclarationBase = ExtensionHostDeclarationBase & {
|
|
64
|
+
family: Exclude<ExtensionHostFamily, 'data-table' | 'crud-form' | 'component-handle'>
|
|
65
|
+
supported: readonly ExtensionHostCapability[]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type InjectionExtensionHostDeclaration = InjectionExtensionHostDeclarationBase & (
|
|
69
|
+
| {
|
|
70
|
+
spotId: string
|
|
71
|
+
pattern?: never
|
|
72
|
+
parameters?: never
|
|
73
|
+
}
|
|
74
|
+
| {
|
|
75
|
+
spotId?: never
|
|
76
|
+
pattern: string
|
|
77
|
+
parameters: Readonly<Record<string, ExtensionPointPatternParameter>>
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
export type DataTableExtensionHostDeclaration = ExtensionHostDeclarationBase & {
|
|
82
|
+
family: 'data-table'
|
|
83
|
+
tableId: string
|
|
84
|
+
baseSpotId?: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type CrudFormExtensionHostDeclaration = ExtensionHostDeclarationBase & {
|
|
88
|
+
family: 'crud-form'
|
|
89
|
+
entityId: string
|
|
90
|
+
spotId?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type ComponentExtensionHostDeclaration = ExtensionHostDeclarationBase & {
|
|
94
|
+
family: 'component-handle'
|
|
95
|
+
componentId: string
|
|
96
|
+
propsContract?: string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type ModuleExtensionHostDeclaration =
|
|
100
|
+
| InjectionExtensionHostDeclaration
|
|
101
|
+
| DataTableExtensionHostDeclaration
|
|
102
|
+
| CrudFormExtensionHostDeclaration
|
|
103
|
+
| ComponentExtensionHostDeclaration
|
|
104
|
+
|
|
105
|
+
export type ModuleExtensionPoints<
|
|
106
|
+
TModuleId extends string = string,
|
|
107
|
+
THosts extends Readonly<Record<string, ModuleExtensionHostDeclaration>> = Readonly<Record<string, ModuleExtensionHostDeclaration>>,
|
|
108
|
+
> = {
|
|
109
|
+
moduleId: TModuleId
|
|
110
|
+
hosts: THosts
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function defineModuleExtensionPoints<
|
|
114
|
+
const TModuleId extends string,
|
|
115
|
+
const THosts extends Readonly<Record<string, ModuleExtensionHostDeclaration>>,
|
|
116
|
+
>(declaration: ModuleExtensionPoints<TModuleId, THosts>): ModuleExtensionPoints<TModuleId, THosts> {
|
|
117
|
+
return Object.freeze({
|
|
118
|
+
...declaration,
|
|
119
|
+
hosts: Object.freeze({ ...declaration.hosts }),
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function injectionExtensionHost<const TDeclaration extends InjectionExtensionHostDeclaration>(
|
|
124
|
+
declaration: TDeclaration,
|
|
125
|
+
): Readonly<TDeclaration> {
|
|
126
|
+
const hasExactId = typeof declaration.spotId === 'string' && declaration.spotId.length > 0
|
|
127
|
+
const hasPattern = typeof declaration.pattern === 'string' && declaration.pattern.length > 0
|
|
128
|
+
if (hasExactId === hasPattern) {
|
|
129
|
+
throw new Error('[internal] injection extension hosts require exactly one of spotId or pattern')
|
|
130
|
+
}
|
|
131
|
+
if (hasPattern && (!declaration.parameters || Object.keys(declaration.parameters).length === 0)) {
|
|
132
|
+
throw new Error('[internal] patterned injection extension hosts require named parameters')
|
|
133
|
+
}
|
|
134
|
+
return Object.freeze({ ...declaration })
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function dataTableExtensionHost<
|
|
138
|
+
const TDeclaration extends Omit<DataTableExtensionHostDeclaration, 'family'>,
|
|
139
|
+
>(
|
|
140
|
+
declaration: TDeclaration,
|
|
141
|
+
): Readonly<TDeclaration & { family: 'data-table' }> {
|
|
142
|
+
return Object.freeze({ family: 'data-table', ...declaration })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function crudFormExtensionHost<
|
|
146
|
+
const TDeclaration extends Omit<CrudFormExtensionHostDeclaration, 'family'>,
|
|
147
|
+
>(
|
|
148
|
+
declaration: TDeclaration,
|
|
149
|
+
): Readonly<TDeclaration & { family: 'crud-form' }> {
|
|
150
|
+
return Object.freeze({ family: 'crud-form', ...declaration })
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function componentExtensionHost<
|
|
154
|
+
const TDeclaration extends Omit<ComponentExtensionHostDeclaration, 'family'>,
|
|
155
|
+
>(
|
|
156
|
+
declaration: TDeclaration,
|
|
157
|
+
): Readonly<TDeclaration & { family: 'component-handle' }> {
|
|
158
|
+
return Object.freeze({ family: 'component-handle', ...declaration })
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export type BoundExtensionSurface = {
|
|
162
|
+
key: string
|
|
163
|
+
suffix: string | null
|
|
164
|
+
capabilities: readonly ExtensionHostCapability[]
|
|
165
|
+
bound: boolean
|
|
166
|
+
phases?: readonly string[]
|
|
167
|
+
operations?: readonly string[]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const DATA_TABLE_EXTENSION_SURFACES = [
|
|
171
|
+
{ key: 'header', suffix: 'header', capabilities: ['render-widget'], bound: true },
|
|
172
|
+
{ key: 'footer', suffix: 'footer', capabilities: ['render-widget'], bound: true },
|
|
173
|
+
{ key: 'toolbar', suffix: 'toolbar', capabilities: ['toolbar-widget'], bound: true },
|
|
174
|
+
{ key: 'searchTrailing', suffix: 'search-trailing', capabilities: ['render-widget'], bound: true },
|
|
175
|
+
{ key: 'columns', suffix: 'columns', capabilities: ['column-widget'], bound: true },
|
|
176
|
+
{ key: 'rowActions', suffix: 'row-actions', capabilities: ['row-action'], bound: true },
|
|
177
|
+
{ key: 'bulkActions', suffix: 'bulk-actions', capabilities: ['bulk-action'], bound: true },
|
|
178
|
+
{ key: 'filters', suffix: 'filters', capabilities: ['filter-widget'], bound: true },
|
|
179
|
+
{ key: 'replacement', suffix: null, capabilities: ['component-replacement'], bound: true },
|
|
180
|
+
{ key: 'emptyState', suffix: 'empty-state', capabilities: ['render-widget'], bound: false },
|
|
181
|
+
] as const satisfies readonly BoundExtensionSurface[]
|
|
182
|
+
|
|
183
|
+
export const CRUD_FORM_EXTENSION_SURFACES = [
|
|
184
|
+
{ key: 'base', suffix: null, capabilities: ['render-widget', 'lifecycle-handler'], bound: true },
|
|
185
|
+
{ key: 'header', suffix: 'header', capabilities: ['render-widget'], bound: true },
|
|
186
|
+
{ key: 'fields', suffix: 'fields', capabilities: ['field-widget'], bound: true },
|
|
187
|
+
{ key: 'replacement', suffix: null, capabilities: ['component-replacement'], bound: true },
|
|
188
|
+
{ key: 'beforeFields', suffix: 'before-fields', capabilities: ['render-widget'], bound: false },
|
|
189
|
+
{ key: 'afterFields', suffix: 'after-fields', capabilities: ['render-widget'], bound: false },
|
|
190
|
+
{ key: 'footer', suffix: 'footer', capabilities: ['render-widget'], bound: false },
|
|
191
|
+
{ key: 'sidebar', suffix: 'sidebar', capabilities: ['render-widget'], bound: false },
|
|
192
|
+
{ key: 'group', suffix: 'group:{groupId}', capabilities: ['render-widget'], bound: false },
|
|
193
|
+
{ key: 'fieldBefore', suffix: 'field:{fieldId}:before', capabilities: ['render-widget'], bound: false },
|
|
194
|
+
{ key: 'fieldAfter', suffix: 'field:{fieldId}:after', capabilities: ['render-widget'], bound: false },
|
|
195
|
+
] as const satisfies readonly BoundExtensionSurface[]
|
|
196
|
+
|
|
197
|
+
export const CRUD_FORM_LIFECYCLE_PHASES = [
|
|
198
|
+
'transformValidation',
|
|
199
|
+
'transformDisplayData',
|
|
200
|
+
'onBeforeNavigate',
|
|
201
|
+
'onAppEvent',
|
|
202
|
+
'onVisibilityChange',
|
|
203
|
+
'onBeforeDelete',
|
|
204
|
+
'onDelete',
|
|
205
|
+
'onAfterDelete',
|
|
206
|
+
'onDeleteError',
|
|
207
|
+
'onFieldChange',
|
|
208
|
+
'transformFormData',
|
|
209
|
+
'onBeforeSave',
|
|
210
|
+
'onSave',
|
|
211
|
+
'onAfterSave',
|
|
212
|
+
] as const
|
|
213
|
+
|
|
214
|
+
export const CRUD_FORM_OPERATIONS = ['create', 'update', 'delete'] as const
|
|
215
|
+
|
|
216
|
+
export function dataTableExtensionSpotId(tableId: string, suffix?: string): string {
|
|
217
|
+
return suffix ? `data-table:${tableId}:${suffix}` : `data-table:${tableId}`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function crudFormExtensionSpotId(entityId: string, suffix?: string): string {
|
|
221
|
+
return suffix ? `crud-form:${entityId}:${suffix}` : `crud-form:${entityId}`
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function extensionSpotChildId(spotId: string, suffix: string): string {
|
|
225
|
+
return `${spotId}:${suffix}`
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function resolveExtensionPointPattern(
|
|
229
|
+
pattern: string,
|
|
230
|
+
parameters: Readonly<Record<string, string>>,
|
|
231
|
+
): string {
|
|
232
|
+
return pattern.replace(/\{([^}]+)\}/g, (token, parameterName: string) => parameters[parameterName] ?? token)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export type ModuleExtensionSurfaceFacts = {
|
|
236
|
+
hosts: ModuleExtensionHostFact[]
|
|
237
|
+
contributions: ModuleExtensionContributionFact[]
|
|
238
|
+
unresolved: ModuleExtensionUnresolvedFact[]
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export type ModuleExtensionHostFact = {
|
|
242
|
+
key: string
|
|
243
|
+
id: string
|
|
244
|
+
resolution: 'exact' | 'pattern' | 'framework' | 'fact-ref'
|
|
245
|
+
family: ExtensionHostFamily
|
|
246
|
+
ownerModule: string
|
|
247
|
+
capabilities: ExtensionHostCapability[]
|
|
248
|
+
phases?: string[]
|
|
249
|
+
operations?: string[]
|
|
250
|
+
contextContract?: string
|
|
251
|
+
dataContract?: string
|
|
252
|
+
scopeContract?: string
|
|
253
|
+
runtimeContract?: string
|
|
254
|
+
activation?: ExtensionHostActivation
|
|
255
|
+
bound: boolean
|
|
256
|
+
stability: 'frozen' | 'stable'
|
|
257
|
+
source:
|
|
258
|
+
| { kind: 'declaration'; path: string; symbol: string }
|
|
259
|
+
| { kind: 'fact-ref'; factSection: string; factKey: string }
|
|
260
|
+
| { kind: 'framework'; path: string; symbol: string }
|
|
261
|
+
aliases?: string[]
|
|
262
|
+
patternParameters?: Record<string, ExtensionPointPatternParameter>
|
|
263
|
+
fallbacks?: string[]
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export type ModuleExtensionTargetFact = {
|
|
267
|
+
id: string
|
|
268
|
+
resolution: 'exact' | 'pattern' | 'framework' | 'fact-ref' | 'optional-external' | 'unresolved'
|
|
269
|
+
factRef?: { factSection: string; factKey: string }
|
|
270
|
+
optionalOwnerPackage?: string
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export type ModuleExtensionContributionBase = {
|
|
274
|
+
id: string
|
|
275
|
+
targets: ModuleExtensionTargetFact[]
|
|
276
|
+
phases?: string[]
|
|
277
|
+
operations?: string[]
|
|
278
|
+
features?: string[]
|
|
279
|
+
scopeContract: string
|
|
280
|
+
activation?: ExtensionHostActivation
|
|
281
|
+
placement?: { relativeTo?: string; position?: 'first' | 'last' | 'before' | 'after'; priority?: number }
|
|
282
|
+
roundTripId?: string
|
|
283
|
+
override?: { domain: string; key: string; mode: 'disable-replace' | 'replace' | 'additive' }
|
|
284
|
+
source: { path: string; symbol?: string }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export type ModuleExtensionContributionFact = ModuleExtensionContributionBase & (
|
|
288
|
+
| {
|
|
289
|
+
kind: 'widget'
|
|
290
|
+
details: {
|
|
291
|
+
payload: 'render' | 'headless' | 'menu' | 'dashboard' | 'notification' | 'integration'
|
|
292
|
+
registryKey: string
|
|
293
|
+
itemIds?: string[]
|
|
294
|
+
labelKeys?: string[]
|
|
295
|
+
contextContract?: string
|
|
296
|
+
dataContract?: string
|
|
297
|
+
executionGuard: 'host' | 'contribution' | 'both'
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
| {
|
|
301
|
+
kind: 'data-table'
|
|
302
|
+
details: {
|
|
303
|
+
payload: 'column' | 'row-action' | 'bulk-action' | 'filter' | 'toolbar' | 'render'
|
|
304
|
+
tableId: string
|
|
305
|
+
executionGuard: 'host' | 'contribution' | 'both'
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
| {
|
|
309
|
+
kind: 'crud-form'
|
|
310
|
+
details: {
|
|
311
|
+
payload: 'render' | 'field' | 'lifecycle-handler'
|
|
312
|
+
entityId: string
|
|
313
|
+
fieldIds?: string[]
|
|
314
|
+
groupIds?: string[]
|
|
315
|
+
requestHeaderCapability: boolean
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
| {
|
|
319
|
+
kind: 'component-override'
|
|
320
|
+
details: { handle: string; mode: 'replace' | 'wrapper' | 'props'; propsContract: string }
|
|
321
|
+
}
|
|
322
|
+
| {
|
|
323
|
+
kind: 'response-enricher'
|
|
324
|
+
details: {
|
|
325
|
+
targetEntity: string
|
|
326
|
+
surfaces: Array<'list' | 'detail'>
|
|
327
|
+
timeoutMs: number
|
|
328
|
+
fallback: 'none' | 'configured'
|
|
329
|
+
critical: boolean
|
|
330
|
+
cachePosture: 'record-pure' | 'rerun-on-list-cache-hit'
|
|
331
|
+
queryEngine?: {
|
|
332
|
+
engines: string[]
|
|
333
|
+
applyOn: Array<'list' | 'detail'>
|
|
334
|
+
activation: 'caller-opt-in'
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
| {
|
|
339
|
+
kind: 'api-interceptor'
|
|
340
|
+
details: {
|
|
341
|
+
route: string
|
|
342
|
+
methods: string[]
|
|
343
|
+
phases: Array<'before' | 'after'>
|
|
344
|
+
activation: 'crud-pipeline' | 'custom-route-bridge'
|
|
345
|
+
timeoutMs: number
|
|
346
|
+
failurePosture: 'fail-closed' | 'fallback'
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
| {
|
|
350
|
+
kind: 'command-interceptor'
|
|
351
|
+
details: {
|
|
352
|
+
targetCommand: string
|
|
353
|
+
phases: Array<'before-execute' | 'after-execute' | 'before-undo' | 'after-undo'>
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
| {
|
|
357
|
+
kind: 'mutation-guard'
|
|
358
|
+
details: {
|
|
359
|
+
entityId: string
|
|
360
|
+
operations: Array<'create' | 'update' | 'delete'>
|
|
361
|
+
capabilities: Array<'block' | 'rewrite' | 'after-success'>
|
|
362
|
+
optimisticLock: 'preserved'
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
| {
|
|
366
|
+
kind: 'entity-extension'
|
|
367
|
+
details: {
|
|
368
|
+
hostEntityId: string
|
|
369
|
+
extensionEntityId: string
|
|
370
|
+
linkId: string
|
|
371
|
+
scopeContract: string
|
|
372
|
+
orphanContract: string
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
| {
|
|
376
|
+
kind: 'subscriber'
|
|
377
|
+
details: {
|
|
378
|
+
event: string
|
|
379
|
+
subscriberId: string
|
|
380
|
+
persistent: boolean
|
|
381
|
+
sync: boolean
|
|
382
|
+
priority?: number
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
| {
|
|
386
|
+
kind: 'browser-reaction'
|
|
387
|
+
details: {
|
|
388
|
+
transports: Array<'client' | 'portal' | 'notification-effect'>
|
|
389
|
+
hooks: string[]
|
|
390
|
+
audienceScopeContract: string
|
|
391
|
+
maxPayloadBytes?: number
|
|
392
|
+
dedupWindowMs?: number
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
| {
|
|
396
|
+
kind: 'specialized-registry'
|
|
397
|
+
details: {
|
|
398
|
+
registry: 'notification' | 'integration' | 'search' | 'vector' | 'ai' | 'payment' | 'shipping' | 'currency' | 'workflow'
|
|
399
|
+
registryId: string
|
|
400
|
+
specialistRoute: string
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
| {
|
|
404
|
+
kind: 'module-override'
|
|
405
|
+
details: {
|
|
406
|
+
domain: string
|
|
407
|
+
key: string
|
|
408
|
+
mode: 'disable-replace' | 'replace' | 'additive'
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
export type ModuleExtensionUnresolvedFact = {
|
|
414
|
+
key: string
|
|
415
|
+
source: { path: string; symbol?: string }
|
|
416
|
+
reason:
|
|
417
|
+
| 'unclassified-binding'
|
|
418
|
+
| 'unbound-declaration'
|
|
419
|
+
| 'dynamic-without-pattern'
|
|
420
|
+
| 'unresolved-first-party-target'
|
|
421
|
+
}
|