@open-mercato/core 0.4.7-develop-74069040de → 0.4.7-develop-bdeaa0fc10

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.
@@ -0,0 +1,251 @@
1
+ # Core Package — Standalone Developer Guide
2
+
3
+ `@open-mercato/core` contains all built-in business modules. This guide covers module development patterns for standalone apps that build on top of these modules.
4
+
5
+ ## Auto-Discovery Paths
6
+
7
+ Place files in your module directory (`src/modules/<module>/`) — the framework discovers them automatically:
8
+
9
+ | Path Pattern | Becomes |
10
+ |---|---|
11
+ | `frontend/<path>.tsx` | `/<path>` (public page) |
12
+ | `backend/<path>.tsx` | `/backend/<path>` (admin page) |
13
+ | `backend/page.tsx` | `/backend/<module>` (module root page) |
14
+ | `api/<method>/<path>.ts` | `/api/<path>` dispatched by HTTP method |
15
+ | `subscribers/*.ts` | Event subscriber (export `metadata` + default handler) |
16
+ | `workers/*.ts` | Background worker (export `metadata` + default handler) |
17
+
18
+ Run `yarn generate` after adding any auto-discovered file.
19
+
20
+ ## Module Files Reference
21
+
22
+ | File | Export | Purpose |
23
+ |------|--------|---------|
24
+ | `index.ts` | `metadata` | Module metadata |
25
+ | `di.ts` | `register(container)` | DI registrations (Awilix) |
26
+ | `acl.ts` | `features` | Permission features: `['mod.view', 'mod.create', ...]` |
27
+ | `setup.ts` | `setup: ModuleSetupConfig` | Tenant init, role features, seed data |
28
+ | `ce.ts` | `entities` | Custom entities / custom field sets |
29
+ | `events.ts` | `eventsConfig` | Typed event declarations |
30
+ | `search.ts` | `searchConfig` | Search indexing config |
31
+ | `translations.ts` | `translatableFields` | Translatable fields per entity |
32
+ | `notifications.ts` | `notificationTypes` | Notification type definitions |
33
+ | `notifications.client.ts` | — | Client-side notification renderers |
34
+ | `notifications.handlers.ts` | `notificationHandlers` | Reactive notification side-effects |
35
+ | `data/entities.ts` | — | MikroORM entity classes |
36
+ | `data/validators.ts` | — | Zod validation schemas |
37
+ | `data/extensions.ts` | `extensions` | Entity extensions (cross-module links) |
38
+ | `data/enrichers.ts` | `enrichers` | Response enrichers |
39
+ | `api/interceptors.ts` | `interceptors` | API route interception hooks |
40
+ | `widgets/injection/` | — | Injected UI widgets |
41
+ | `widgets/injection-table.ts` | — | Widget-to-slot mappings |
42
+ | `widgets/components.ts` | `componentOverrides` | Component replacement/wrapper definitions |
43
+
44
+ ## API Routes
45
+
46
+ Every API route file MUST export an `openApi` object:
47
+
48
+ ```typescript
49
+ import { createCrudOpenApiFactory } from '@open-mercato/shared/lib/openapi/crud'
50
+ const buildOpenApi = createCrudOpenApiFactory({ defaultTag: 'MyModule' })
51
+
52
+ export const openApi = buildOpenApi({
53
+ resourceName: 'Item',
54
+ querySchema: listQuerySchema,
55
+ listResponseSchema: createPagedListResponseSchema(itemSchema),
56
+ create: { schema: createSchema, description: 'Create item' },
57
+ update: { schema: updateSchema, responseSchema: okSchema },
58
+ del: { schema: deleteSchema, responseSchema: okSchema },
59
+ })
60
+ ```
61
+
62
+ ### CRUD Routes with makeCrudRoute
63
+
64
+ Always set `indexer: { entityType }` for query index coverage:
65
+
66
+ ```typescript
67
+ makeCrudRoute({
68
+ entity: MyEntity,
69
+ indexer: { entityType: 'my_module:my_entity' },
70
+ enrichers: { entityId: 'my_module.my_entity' }, // opt-in to enrichers
71
+ // ...
72
+ })
73
+ ```
74
+
75
+ ### Custom Write Routes
76
+
77
+ For non-CRUD write routes (`POST`/`PUT`/`PATCH`/`DELETE`), MUST wire mutation guards:
78
+ - Call `validateCrudMutationGuard` before mutation
79
+ - Call `runCrudMutationGuardAfterSuccess` after successful mutation
80
+
81
+ ## Module Setup (`setup.ts`)
82
+
83
+ Required for tenant initialization:
84
+
85
+ ```typescript
86
+ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
87
+
88
+ export const setup: ModuleSetupConfig = {
89
+ defaultRoleFeatures: {
90
+ superadmin: ['my_module.admin_only'],
91
+ admin: ['my_module.*'],
92
+ employee: ['my_module.view'],
93
+ },
94
+ async onTenantCreated({ em, tenantId, organizationId }) { /* settings, sequences */ },
95
+ async seedDefaults({ em, tenantId, container }) { /* dictionaries, statuses */ },
96
+ async seedExamples({ em, tenantId, container }) { /* demo data */ },
97
+ }
98
+ ```
99
+
100
+ ## Events
101
+
102
+ Declare events in the emitting module's `events.ts`:
103
+
104
+ ```typescript
105
+ import { createModuleEvents } from '@open-mercato/shared/modules/events'
106
+ const events = [
107
+ { id: 'my_mod.item.created', label: 'Item Created', entity: 'item', category: 'crud' },
108
+ { id: 'my_mod.item.updated', label: 'Item Updated', entity: 'item', category: 'crud' },
109
+ { id: 'my_mod.item.deleted', label: 'Item Deleted', entity: 'item', category: 'crud' },
110
+ ] as const
111
+ export const eventsConfig = createModuleEvents({ moduleId: 'my_mod', events })
112
+ export const emitMyModEvent = eventsConfig.emit
113
+ ```
114
+
115
+ MUST use `as const` for type safety. Run `yarn generate` after adding.
116
+
117
+ ### Subscribers
118
+
119
+ ```typescript
120
+ // subscribers/item-created-notify.ts
121
+ export const metadata = { event: 'my_mod.item.created', persistent: true, id: 'item-created-notify' }
122
+ export default async function handler(payload, ctx) { /* one side effect per subscriber */ }
123
+ ```
124
+
125
+ ## Widget Injection
126
+
127
+ The preferred way to extend other modules' UI without direct coupling.
128
+
129
+ ### Structure
130
+ - Widgets: `widgets/injection/<WidgetName>/widget.tsx` (or `widget.ts` for headless)
131
+ - Mapping: `widgets/injection-table.ts`
132
+
133
+ ### Spot IDs
134
+ - `crud-form:<entityId>` — inject into forms
135
+ - `crud-form:<entityId>:fields` — inject form fields
136
+ - `data-table:<tableId>:columns|row-actions|bulk-actions|filters` — inject into tables
137
+ - `menu:sidebar:main|settings|profile` — sidebar menu items
138
+ - `menu:topbar:profile-dropdown|actions` — topbar items
139
+
140
+ ### Menu Injection (Headless)
141
+ ```typescript
142
+ // widgets/injection/MyMenuItem/widget.ts
143
+ export const menuItems = [
144
+ { id: 'my-mod-dashboard', labelKey: 'my_mod.menu.dashboard', icon: 'lucide:layout-dashboard',
145
+ href: '/backend/my-module', placement: { position: InjectionPosition.After, relativeTo: 'customers' } }
146
+ ]
147
+ ```
148
+
149
+ Map in `injection-table.ts`:
150
+ ```typescript
151
+ export default [{ widgetId: 'MyMenuItem', spots: ['menu:sidebar:main'] }]
152
+ ```
153
+
154
+ ## Response Enrichers
155
+
156
+ Add computed fields to another module's CRUD responses:
157
+
158
+ ```typescript
159
+ // data/enrichers.ts
160
+ export const enrichers: ResponseEnricher[] = [{
161
+ id: 'my_mod.customer-stats',
162
+ targetEntity: 'customers.person',
163
+ features: ['my_mod.view'],
164
+ priority: 10,
165
+ timeout: 2000,
166
+ fallback: { _my_mod: { count: 0 } },
167
+ async enrichMany(records, ctx) {
168
+ return records.map(r => ({ ...r, _my_mod: { count: 42 } }))
169
+ },
170
+ }]
171
+ ```
172
+
173
+ MUST implement `enrichMany()` for batch endpoints. MUST namespace with `_moduleName` prefix.
174
+
175
+ ## API Interceptors
176
+
177
+ Hook into any route's before/after lifecycle:
178
+
179
+ ```typescript
180
+ // api/interceptors.ts
181
+ export const interceptors: ApiInterceptor[] = [{
182
+ id: 'my_mod.narrow-customers',
183
+ targetRoute: '/api/customers/people',
184
+ methods: ['GET'],
185
+ async before(ctx) { /* rewrite query.ids to narrow results */ },
186
+ async after(ctx, response) { /* transform response */ },
187
+ }]
188
+ ```
189
+
190
+ ## Access Control (RBAC)
191
+
192
+ Declare features in `acl.ts`, guard with metadata:
193
+
194
+ ```typescript
195
+ // acl.ts
196
+ export const features = ['my_module.view', 'my_module.create', 'my_module.edit', 'my_module.delete']
197
+ ```
198
+
199
+ Always add matching `defaultRoleFeatures` in `setup.ts`.
200
+
201
+ Use declarative guards in page metadata: `requireAuth`, `requireRoles`, `requireFeatures`.
202
+
203
+ ## Custom Fields & Entities
204
+
205
+ Declare in `ce.ts` using DSL helpers:
206
+
207
+ ```typescript
208
+ import { defineFields, cf } from '@open-mercato/shared/modules/dsl'
209
+ export const entities = [{
210
+ entityId: 'my_module:my_entity',
211
+ fields: defineFields({ fields: [cf.text('notes'), cf.number('priority')] }),
212
+ }]
213
+ ```
214
+
215
+ ## Entity Extensions (Cross-Module Links)
216
+
217
+ Extend another module's data without mutating their entities:
218
+
219
+ ```typescript
220
+ // data/extensions.ts
221
+ import { defineLink, entityId, linkable } from '@open-mercato/shared/modules/dsl'
222
+ export const extensions = [
223
+ defineLink({ source: entityId('my_module:my_entity'), target: linkable('customers:person') })
224
+ ]
225
+ ```
226
+
227
+ ## Component Replacement
228
+
229
+ Override or wrap existing UI components:
230
+
231
+ ```typescript
232
+ // widgets/components.ts
233
+ export const componentOverrides = [{
234
+ handle: 'page:customers:detail',
235
+ mode: 'wrapper', // or 'replace', 'props'
236
+ component: MyCustomerDetailWrapper,
237
+ }]
238
+ ```
239
+
240
+ Prefer `wrapper`/`props` modes over full `replace`.
241
+
242
+ ## Command Pattern (Write Operations)
243
+
244
+ Implement writes via commands for undo/redo support:
245
+
246
+ ```typescript
247
+ import { registerCommand } from '@open-mercato/shared/lib/commands'
248
+ // Reference: @open-mercato/core customers/commands/people.ts
249
+ ```
250
+
251
+ Include `indexer: { entityType, cacheAliases }` in `emitCrudSideEffects` for query index refresh.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.4.7-develop-74069040de",
3
+ "version": "0.4.7-develop-bdeaa0fc10",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -217,10 +217,10 @@
217
217
  "semver": "^7.6.3"
218
218
  },
219
219
  "peerDependencies": {
220
- "@open-mercato/shared": "0.4.7-develop-74069040de"
220
+ "@open-mercato/shared": "0.4.7-develop-bdeaa0fc10"
221
221
  },
222
222
  "devDependencies": {
223
- "@open-mercato/shared": "0.4.7-develop-74069040de",
223
+ "@open-mercato/shared": "0.4.7-develop-bdeaa0fc10",
224
224
  "@testing-library/dom": "^10.4.1",
225
225
  "@testing-library/jest-dom": "^6.9.1",
226
226
  "@testing-library/react": "^16.3.1",