@open-mercato/shared 0.7.1-develop.7193.1.910a5b0a1e → 0.7.1-develop.7194.1.ab4fc81f82
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/ai/opencode-tool-parts.js +48 -0
- package/dist/lib/ai/opencode-tool-parts.js.map +7 -0
- package/dist/lib/ai/token-count.js +11 -0
- package/dist/lib/ai/token-count.js.map +7 -0
- package/dist/lib/bootstrap/dynamicLoader.js +14 -1
- package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
- package/dist/lib/commands/command-bus.js +9 -1
- package/dist/lib/commands/command-bus.js.map +2 -2
- package/dist/lib/commands/registry.js +9 -0
- package/dist/lib/commands/registry.js.map +2 -2
- package/dist/lib/commands/types.js.map +2 -2
- package/dist/lib/openapi/generator.js +3 -2
- package/dist/lib/openapi/generator.js.map +2 -2
- package/dist/lib/openapi/index.js +3 -2
- package/dist/lib/openapi/index.js.map +2 -2
- package/dist/lib/seed/crypto.js +73 -0
- package/dist/lib/seed/crypto.js.map +7 -0
- package/dist/lib/seed/index.js +4 -0
- package/dist/lib/seed/index.js.map +7 -0
- package/dist/lib/seed/loader.js +73 -0
- package/dist/lib/seed/loader.js.map +7 -0
- package/dist/lib/seed/types.js +33 -0
- package/dist/lib/seed/types.js.map +7 -0
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/events/factory.js +28 -9
- package/dist/modules/events/factory.js.map +2 -2
- package/package.json +3 -2
- package/src/lib/ai/__tests__/opencode-tool-parts.test.ts +81 -0
- package/src/lib/ai/__tests__/token-count.test.ts +20 -0
- package/src/lib/ai/opencode-tool-parts.ts +80 -0
- package/src/lib/ai/token-count.ts +21 -0
- package/src/lib/bootstrap/dynamicLoader.ts +24 -1
- package/src/lib/commands/__tests__/command-bus.test.ts +64 -0
- package/src/lib/commands/__tests__/registry.test.ts +35 -0
- package/src/lib/commands/command-bus.ts +16 -1
- package/src/lib/commands/registry.ts +11 -0
- package/src/lib/commands/types.ts +31 -0
- package/src/lib/openapi/__tests__/generator-response-fallback.test.ts +73 -0
- package/src/lib/openapi/generator.ts +3 -3
- package/src/lib/openapi/index.ts +1 -1
- package/src/lib/seed/__tests__/seed-crypto.test.ts +64 -0
- package/src/lib/seed/crypto.ts +87 -0
- package/src/lib/seed/index.ts +3 -0
- package/src/lib/seed/loader.ts +124 -0
- package/src/lib/seed/types.ts +48 -0
- package/src/modules/events/__tests__/factory.test.ts +96 -0
- package/src/modules/events/factory.ts +41 -10
- package/src/modules/events/types.ts +44 -0
|
@@ -1,3 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events factory — payloadSchema declaration and the generated CRUD default.
|
|
3
|
+
*
|
|
4
|
+
* Verifies the additive `payloadSchema` contract on `EventDefinition`:
|
|
5
|
+
* platform CRUD after-events (`*.created`/`*.updated`/`*.deleted`, category
|
|
6
|
+
* `crud`) receive the generated `{ id, organizationId, tenantId, syncOrigin }`
|
|
7
|
+
* default; explicit declarations are preserved untouched; lifecycle/system
|
|
8
|
+
* events and CRUD "before" events get no default; and `getDeclaredEvents()`
|
|
9
|
+
* exposes the same definitions the module config carries.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
createModuleEvents,
|
|
14
|
+
getDeclaredEvents,
|
|
15
|
+
DEFAULT_CRUD_PAYLOAD_SCHEMA,
|
|
16
|
+
} from '../factory'
|
|
17
|
+
import type { EventPayloadSchema } from '../types'
|
|
18
|
+
|
|
19
|
+
const explicitSchema: EventPayloadSchema = {
|
|
20
|
+
fields: [
|
|
21
|
+
{ path: 'id', type: 'text' },
|
|
22
|
+
{ path: 'amount', type: 'number', optional: true },
|
|
23
|
+
{ path: 'meta', type: 'object' },
|
|
24
|
+
],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const config = createModuleEvents({
|
|
28
|
+
moduleId: 'factory_test',
|
|
29
|
+
events: [
|
|
30
|
+
{ id: 'factory_test.widget.created', label: 'Widget Created', entity: 'widget', category: 'crud' },
|
|
31
|
+
{ id: 'factory_test.widget.updated', label: 'Widget Updated', entity: 'widget', category: 'crud' },
|
|
32
|
+
{ id: 'factory_test.widget.deleted', label: 'Widget Deleted', entity: 'widget', category: 'crud' },
|
|
33
|
+
{ id: 'factory_test.widget.creating', label: 'Widget Creating', entity: 'widget', category: 'crud', excludeFromTriggers: true },
|
|
34
|
+
{ id: 'factory_test.widget.archived', label: 'Widget Archived', entity: 'widget', category: 'lifecycle' },
|
|
35
|
+
{ id: 'factory_test.widget.scored', label: 'Widget Scored', entity: 'widget', category: 'lifecycle', payloadSchema: explicitSchema },
|
|
36
|
+
{ id: 'factory_test.widget.replaced', label: 'Widget Replaced', entity: 'widget', category: 'crud', payloadSchema: explicitSchema },
|
|
37
|
+
] as const,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
function findEvent(id: string) {
|
|
41
|
+
const event = config.events.find(e => e.id === id)
|
|
42
|
+
if (!event) throw new Error(`[internal] event ${id} missing from config`)
|
|
43
|
+
return event
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('createModuleEvents payloadSchema', () => {
|
|
47
|
+
it('attaches the generated CRUD default to platform CRUD after-events', () => {
|
|
48
|
+
for (const suffix of ['created', 'updated', 'deleted']) {
|
|
49
|
+
expect(findEvent(`factory_test.widget.${suffix}`).payloadSchema).toEqual(DEFAULT_CRUD_PAYLOAD_SCHEMA)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('generated default mirrors the engine emit payload fields', () => {
|
|
54
|
+
expect(DEFAULT_CRUD_PAYLOAD_SCHEMA.fields.map(f => f.path)).toEqual([
|
|
55
|
+
'id',
|
|
56
|
+
'organizationId',
|
|
57
|
+
'tenantId',
|
|
58
|
+
'syncOrigin',
|
|
59
|
+
])
|
|
60
|
+
const idField = DEFAULT_CRUD_PAYLOAD_SCHEMA.fields.find(f => f.path === 'id')
|
|
61
|
+
expect(idField).toEqual({ path: 'id', type: 'text' })
|
|
62
|
+
expect(DEFAULT_CRUD_PAYLOAD_SCHEMA.fields.filter(f => f.optional).map(f => f.path)).toEqual([
|
|
63
|
+
'organizationId',
|
|
64
|
+
'tenantId',
|
|
65
|
+
'syncOrigin',
|
|
66
|
+
])
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('does not default CRUD "before" lifecycle suffixes or non-crud categories', () => {
|
|
70
|
+
expect(findEvent('factory_test.widget.creating').payloadSchema).toBeUndefined()
|
|
71
|
+
expect(findEvent('factory_test.widget.archived').payloadSchema).toBeUndefined()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('preserves an explicit payloadSchema over the generated default', () => {
|
|
75
|
+
expect(findEvent('factory_test.widget.replaced').payloadSchema).toBe(explicitSchema)
|
|
76
|
+
expect(findEvent('factory_test.widget.scored').payloadSchema).toBe(explicitSchema)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('exposes the same definitions through getDeclaredEvents()', () => {
|
|
80
|
+
const declared = getDeclaredEvents().filter(e => e.module === 'factory_test')
|
|
81
|
+
expect(declared).toHaveLength(config.events.length)
|
|
82
|
+
const declaredCreated = declared.find(e => e.id === 'factory_test.widget.created')
|
|
83
|
+
expect(declaredCreated?.payloadSchema).toEqual(DEFAULT_CRUD_PAYLOAD_SCHEMA)
|
|
84
|
+
const declaredScored = declared.find(e => e.id === 'factory_test.widget.scored')
|
|
85
|
+
expect(declaredScored?.payloadSchema).toBe(explicitSchema)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('keeps unrelated declaration fields intact when defaulting', () => {
|
|
89
|
+
const creating = findEvent('factory_test.widget.creating')
|
|
90
|
+
expect(creating.excludeFromTriggers).toBe(true)
|
|
91
|
+
const created = findEvent('factory_test.widget.created')
|
|
92
|
+
expect(created.module).toBe('factory_test')
|
|
93
|
+
expect(created.entity).toBe('widget')
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
1
97
|
const GLOBAL_EVENT_REGISTRY_KEY = '__openMercatoEventDefinitionRegistry__'
|
|
2
98
|
|
|
3
99
|
type EventFactoryModule = typeof import('../factory')
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
EventDefinition,
|
|
10
10
|
EventModuleConfig,
|
|
11
11
|
EventPayload,
|
|
12
|
+
EventPayloadSchema,
|
|
12
13
|
EmitOptions,
|
|
13
14
|
CreateModuleEventsOptions,
|
|
14
15
|
ModuleEventEmitter,
|
|
@@ -101,18 +102,44 @@ function getEventRegistryState(): EventRegistryState {
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
const CRUD_AFTER_EVENT_SUFFIXES = ['.created', '.updated', '.deleted'] as const
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Generated payload schema for platform-emitted CRUD after-events. Mirrors the
|
|
109
|
+
* default payload built by the data engine's `emitOrmEntityEvent` when no
|
|
110
|
+
* `buildPayload` override is configured: `{ id, organizationId, tenantId }`
|
|
111
|
+
* plus `syncOrigin` when the write originated from a sync. organizationId and
|
|
112
|
+
* tenantId keys are always present but may be null, so they are `optional`.
|
|
113
|
+
*/
|
|
114
|
+
export const DEFAULT_CRUD_PAYLOAD_SCHEMA: EventPayloadSchema = {
|
|
115
|
+
fields: [
|
|
116
|
+
{ path: 'id', type: 'text' },
|
|
117
|
+
{ path: 'organizationId', type: 'text', optional: true },
|
|
118
|
+
{ path: 'tenantId', type: 'text', optional: true },
|
|
119
|
+
{ path: 'syncOrigin', type: 'text', optional: true },
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function applyDefaultCrudPayloadSchema(event: EventDefinition): EventDefinition {
|
|
124
|
+
if (event.payloadSchema) return event
|
|
125
|
+
if (event.category !== 'crud') return event
|
|
126
|
+
if (!CRUD_AFTER_EVENT_SUFFIXES.some(suffix => event.id.endsWith(suffix))) return event
|
|
127
|
+
return { ...event, payloadSchema: DEFAULT_CRUD_PAYLOAD_SCHEMA }
|
|
128
|
+
}
|
|
129
|
+
|
|
104
130
|
function addDeclaredEvent(event: EventDefinition): void {
|
|
131
|
+
const declared = applyDefaultCrudPayloadSchema(event)
|
|
105
132
|
const state = getEventRegistryState()
|
|
106
|
-
state.declaredEventIds.add(
|
|
107
|
-
const existingIndex = state.declaredEvents.findIndex((candidate) => candidate.id ===
|
|
133
|
+
state.declaredEventIds.add(declared.id)
|
|
134
|
+
const existingIndex = state.declaredEvents.findIndex((candidate) => candidate.id === declared.id)
|
|
108
135
|
if (existingIndex < 0) {
|
|
109
|
-
state.declaredEvents.push(
|
|
136
|
+
state.declaredEvents.push(declared)
|
|
110
137
|
return
|
|
111
138
|
}
|
|
112
139
|
// Refresh a module's own definition in place during HMR without allowing a
|
|
113
140
|
// duplicate declaration from another module to take over the event id.
|
|
114
|
-
if (state.declaredEvents[existingIndex]?.module ===
|
|
115
|
-
state.declaredEvents[existingIndex] =
|
|
141
|
+
if (state.declaredEvents[existingIndex]?.module === declared.module) {
|
|
142
|
+
state.declaredEvents[existingIndex] = declared
|
|
116
143
|
}
|
|
117
144
|
}
|
|
118
145
|
|
|
@@ -271,11 +298,15 @@ export function createModuleEvents<
|
|
|
271
298
|
// Build set of valid event IDs for runtime validation
|
|
272
299
|
const validEventIds = new Set(events.map(e => e.id))
|
|
273
300
|
|
|
274
|
-
// Build full event definitions with module added
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
301
|
+
// Build full event definitions with module added and the generated CRUD
|
|
302
|
+
// payload-schema default applied, so config consumers and the global
|
|
303
|
+
// registry see the same definitions.
|
|
304
|
+
const fullEvents: EventDefinition[] = events.map(e =>
|
|
305
|
+
applyDefaultCrudPayloadSchema({
|
|
306
|
+
...e,
|
|
307
|
+
module: moduleId,
|
|
308
|
+
}),
|
|
309
|
+
)
|
|
279
310
|
|
|
280
311
|
// Register all event IDs and definitions in the global registry.
|
|
281
312
|
for (const event of fullEvents) {
|
|
@@ -14,6 +14,43 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export type EventCategory = 'crud' | 'lifecycle' | 'system' | 'custom'
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Type vocabulary for declared event payload fields. Mirrors the flat
|
|
19
|
+
* path/type contract consumed by editor surfaces (path pickers, typed value
|
|
20
|
+
* controls, the workflows context ledger), so declarations flow into those
|
|
21
|
+
* consumers without translation.
|
|
22
|
+
*/
|
|
23
|
+
export type EventPayloadFieldType =
|
|
24
|
+
| 'text'
|
|
25
|
+
| 'number'
|
|
26
|
+
| 'boolean'
|
|
27
|
+
| 'select'
|
|
28
|
+
| 'date'
|
|
29
|
+
| 'object'
|
|
30
|
+
| 'unknown'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* One declared payload field: a flat dot-path into the emitted payload plus
|
|
34
|
+
* its type. `optional` marks fields whose key may be absent or whose value may
|
|
35
|
+
* be null on some emits.
|
|
36
|
+
*/
|
|
37
|
+
export interface EventPayloadSchemaField {
|
|
38
|
+
path: string
|
|
39
|
+
type: EventPayloadFieldType
|
|
40
|
+
label?: string
|
|
41
|
+
optional?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Optional payload typing for an event declaration. A flat field list (not a
|
|
46
|
+
* nested JSON Schema): nested payload shapes are declared as dot-paths
|
|
47
|
+
* (`customer.id`) or a single `object`-typed entry. Declare only fields the
|
|
48
|
+
* emitter verifiably sends.
|
|
49
|
+
*/
|
|
50
|
+
export interface EventPayloadSchema {
|
|
51
|
+
fields: ReadonlyArray<EventPayloadSchemaField>
|
|
52
|
+
}
|
|
53
|
+
|
|
17
54
|
/**
|
|
18
55
|
* Event definition structure
|
|
19
56
|
*/
|
|
@@ -38,6 +75,13 @@ export interface EventDefinition {
|
|
|
38
75
|
crossProcessBroadcast?: boolean
|
|
39
76
|
/** When true, this event is bridged to the customer portal via SSE (Portal Event Bridge). Default: false */
|
|
40
77
|
portalBroadcast?: boolean
|
|
78
|
+
/**
|
|
79
|
+
* Optional declared payload typing. Platform-emitted CRUD events
|
|
80
|
+
* (`*.created`/`*.updated`/`*.deleted` with category `crud`) receive a
|
|
81
|
+
* generated default at registration time; other events carry it only when
|
|
82
|
+
* the declaring module opts in.
|
|
83
|
+
*/
|
|
84
|
+
payloadSchema?: EventPayloadSchema
|
|
41
85
|
}
|
|
42
86
|
|
|
43
87
|
// =============================================================================
|