@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.
Files changed (50) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/lib/ai/opencode-tool-parts.js +48 -0
  3. package/dist/lib/ai/opencode-tool-parts.js.map +7 -0
  4. package/dist/lib/ai/token-count.js +11 -0
  5. package/dist/lib/ai/token-count.js.map +7 -0
  6. package/dist/lib/bootstrap/dynamicLoader.js +14 -1
  7. package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
  8. package/dist/lib/commands/command-bus.js +9 -1
  9. package/dist/lib/commands/command-bus.js.map +2 -2
  10. package/dist/lib/commands/registry.js +9 -0
  11. package/dist/lib/commands/registry.js.map +2 -2
  12. package/dist/lib/commands/types.js.map +2 -2
  13. package/dist/lib/openapi/generator.js +3 -2
  14. package/dist/lib/openapi/generator.js.map +2 -2
  15. package/dist/lib/openapi/index.js +3 -2
  16. package/dist/lib/openapi/index.js.map +2 -2
  17. package/dist/lib/seed/crypto.js +73 -0
  18. package/dist/lib/seed/crypto.js.map +7 -0
  19. package/dist/lib/seed/index.js +4 -0
  20. package/dist/lib/seed/index.js.map +7 -0
  21. package/dist/lib/seed/loader.js +73 -0
  22. package/dist/lib/seed/loader.js.map +7 -0
  23. package/dist/lib/seed/types.js +33 -0
  24. package/dist/lib/seed/types.js.map +7 -0
  25. package/dist/lib/version.js +1 -1
  26. package/dist/lib/version.js.map +1 -1
  27. package/dist/modules/events/factory.js +28 -9
  28. package/dist/modules/events/factory.js.map +2 -2
  29. package/package.json +3 -2
  30. package/src/lib/ai/__tests__/opencode-tool-parts.test.ts +81 -0
  31. package/src/lib/ai/__tests__/token-count.test.ts +20 -0
  32. package/src/lib/ai/opencode-tool-parts.ts +80 -0
  33. package/src/lib/ai/token-count.ts +21 -0
  34. package/src/lib/bootstrap/dynamicLoader.ts +24 -1
  35. package/src/lib/commands/__tests__/command-bus.test.ts +64 -0
  36. package/src/lib/commands/__tests__/registry.test.ts +35 -0
  37. package/src/lib/commands/command-bus.ts +16 -1
  38. package/src/lib/commands/registry.ts +11 -0
  39. package/src/lib/commands/types.ts +31 -0
  40. package/src/lib/openapi/__tests__/generator-response-fallback.test.ts +73 -0
  41. package/src/lib/openapi/generator.ts +3 -3
  42. package/src/lib/openapi/index.ts +1 -1
  43. package/src/lib/seed/__tests__/seed-crypto.test.ts +64 -0
  44. package/src/lib/seed/crypto.ts +87 -0
  45. package/src/lib/seed/index.ts +3 -0
  46. package/src/lib/seed/loader.ts +124 -0
  47. package/src/lib/seed/types.ts +48 -0
  48. package/src/modules/events/__tests__/factory.test.ts +96 -0
  49. package/src/modules/events/factory.ts +41 -10
  50. 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(event.id)
107
- const existingIndex = state.declaredEvents.findIndex((candidate) => candidate.id === event.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(event)
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 === event.module) {
115
- state.declaredEvents[existingIndex] = event
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
- const fullEvents: EventDefinition[] = events.map(e => ({
276
- ...e,
277
- module: moduleId,
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
  // =============================================================================