@open-mercato/shared 0.4.9-develop-fefbbe0979 → 0.4.9-develop-db9ecc46fc

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 (32) hide show
  1. package/dist/lib/auth/server.js +1 -1
  2. package/dist/lib/auth/server.js.map +1 -1
  3. package/dist/lib/crud/custom-route-interceptor.js +27 -0
  4. package/dist/lib/crud/custom-route-interceptor.js.map +7 -0
  5. package/dist/lib/crud/errors.js +8 -1
  6. package/dist/lib/crud/errors.js.map +2 -2
  7. package/dist/lib/crud/factory.js +2 -2
  8. package/dist/lib/crud/factory.js.map +2 -2
  9. package/dist/lib/middleware/page-executor.js +42 -0
  10. package/dist/lib/middleware/page-executor.js.map +7 -0
  11. package/dist/lib/version.js +1 -1
  12. package/dist/lib/version.js.map +1 -1
  13. package/dist/modules/generators/index.js +1 -0
  14. package/dist/modules/generators/index.js.map +7 -0
  15. package/dist/modules/generators/types.js +1 -0
  16. package/dist/modules/generators/types.js.map +7 -0
  17. package/dist/modules/middleware/page.js +15 -0
  18. package/dist/modules/middleware/page.js.map +7 -0
  19. package/dist/modules/widgets/component-registry.js +12 -1
  20. package/dist/modules/widgets/component-registry.js.map +2 -2
  21. package/package.json +5 -1
  22. package/src/lib/auth/server.ts +1 -1
  23. package/src/lib/crud/__tests__/custom-route-interceptor.test.ts +180 -0
  24. package/src/lib/crud/custom-route-interceptor.ts +47 -0
  25. package/src/lib/crud/errors.ts +14 -0
  26. package/src/lib/crud/factory.ts +2 -2
  27. package/src/lib/middleware/__tests__/page-executor.test.ts +161 -0
  28. package/src/lib/middleware/page-executor.ts +63 -0
  29. package/src/modules/generators/index.ts +1 -0
  30. package/src/modules/generators/types.ts +29 -0
  31. package/src/modules/middleware/page.ts +52 -0
  32. package/src/modules/widgets/component-registry.ts +15 -1
@@ -0,0 +1,63 @@
1
+ import type {
2
+ PageMiddlewareContext,
3
+ PageMiddlewareMode,
4
+ PageMiddlewareRegistryEntry,
5
+ PageMiddlewareResult,
6
+ PageRouteMiddleware,
7
+ } from '@open-mercato/shared/modules/middleware/page'
8
+ import { CONTINUE_PAGE_MIDDLEWARE, matchPageMiddlewareTarget } from '@open-mercato/shared/modules/middleware/page'
9
+
10
+ type ExecutePageMiddlewareArgs = {
11
+ entries: PageMiddlewareRegistryEntry[]
12
+ context: PageMiddlewareContext
13
+ onError?: (error: unknown, middleware: Pick<PageRouteMiddleware, 'id' | 'priority'>) => void
14
+ }
15
+
16
+ const DEFAULT_PRIORITY = 100
17
+
18
+ function shouldRunMiddleware(middleware: PageRouteMiddleware, mode: PageMiddlewareMode, pathname: string): boolean {
19
+ if (middleware.mode && middleware.mode !== mode) return false
20
+ return matchPageMiddlewareTarget(pathname, middleware.target)
21
+ }
22
+
23
+ function compareMiddleware(a: PageRouteMiddleware, b: PageRouteMiddleware): number {
24
+ const priorityDiff = (a.priority ?? DEFAULT_PRIORITY) - (b.priority ?? DEFAULT_PRIORITY)
25
+ if (priorityDiff !== 0) return priorityDiff
26
+ return a.id.localeCompare(b.id)
27
+ }
28
+
29
+ function flattenAndSortMiddleware(
30
+ entries: PageMiddlewareRegistryEntry[],
31
+ mode: PageMiddlewareMode,
32
+ pathname: string
33
+ ): PageRouteMiddleware[] {
34
+ return entries
35
+ .flatMap((entry) => entry.middleware)
36
+ .filter((middleware) => shouldRunMiddleware(middleware, mode, pathname))
37
+ .sort(compareMiddleware)
38
+ }
39
+
40
+ export async function executePageMiddleware(args: ExecutePageMiddlewareArgs): Promise<PageMiddlewareResult> {
41
+ const { entries, context, onError } = args
42
+ const matchedMiddleware = flattenAndSortMiddleware(entries, context.mode, context.pathname)
43
+ for (const middleware of matchedMiddleware) {
44
+ try {
45
+ const result = await middleware.run(context)
46
+ if (result.action === 'redirect') return result
47
+ } catch (error) {
48
+ if (onError) {
49
+ onError(error, { id: middleware.id, priority: middleware.priority })
50
+ } else {
51
+ console.error('[middleware:page] execution failed', { id: middleware.id, error })
52
+ }
53
+ throw error
54
+ }
55
+ }
56
+ return CONTINUE_PAGE_MIDDLEWARE
57
+ }
58
+
59
+ export async function resolvePageMiddlewareRedirect(args: ExecutePageMiddlewareArgs): Promise<string | null> {
60
+ const result = await executePageMiddleware(args)
61
+ if (result.action !== 'redirect') return null
62
+ return result.location
63
+ }
@@ -0,0 +1 @@
1
+ export type { GeneratorPlugin, GeneratorPluginBootstrapRegistration } from './types'
@@ -0,0 +1,29 @@
1
+ export interface GeneratorPluginBootstrapRegistration {
2
+ /** Name of the variable exported from this plugin's output file, e.g. 'securityMfaProviderEntries' */
3
+ entriesExportName: string
4
+ /** Full TypeScript import statements needed for the registration call (e.g. the register function) */
5
+ registrationImports: string[]
6
+ /** Build the registration call expression, receives the entries variable name */
7
+ buildCall: (entriesExportName: string) => string
8
+ }
9
+
10
+ export interface GeneratorPlugin {
11
+ /** Unique ID, e.g. 'security.mfa-providers' */
12
+ id: string
13
+ /** Convention file path relative to module root, e.g. 'security.mfa-providers.ts' */
14
+ conventionFile: string
15
+ /** Import variable prefix used in generated code, e.g. 'SECURITY_MFA_PROVIDERS' */
16
+ importPrefix: string
17
+ /** Build the TypeScript entry expression for each discovered module */
18
+ configExpr: (importName: string, moduleId: string) => string
19
+ /** Output filename relative to the generated output directory, e.g. 'security-mfa-providers.generated.ts' */
20
+ outputFileName: string
21
+ /** Generate the full TypeScript file content from collected imports and entry literals */
22
+ buildOutput: (params: { importSection: string; entriesLiteral: string }) => string
23
+ /**
24
+ * When present, contributes a registration call to the auto-generated
25
+ * `bootstrap-registrations.generated.ts` file. This lets modules inject
26
+ * bootstrap-time side effects without bootstrap.ts knowing about them.
27
+ */
28
+ bootstrapRegistration?: GeneratorPluginBootstrapRegistration
29
+ }
@@ -0,0 +1,52 @@
1
+ import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
2
+
3
+ export type PageMiddlewareMode = 'frontend' | 'backend'
4
+
5
+ export type PageRouteMeta = {
6
+ requireAuth?: boolean
7
+ requireRoles?: string[]
8
+ requireFeatures?: string[]
9
+ }
10
+
11
+ export type PageMiddlewareContainer = {
12
+ resolve: (name: string) => unknown
13
+ }
14
+
15
+ export type PageMiddlewareContext = {
16
+ pathname: string
17
+ mode: PageMiddlewareMode
18
+ routeMeta: PageRouteMeta
19
+ auth: AuthContext
20
+ ensureContainer: () => Promise<PageMiddlewareContainer>
21
+ }
22
+
23
+ export type PageMiddlewareResult =
24
+ | { action: 'continue' }
25
+ | { action: 'redirect'; location: string }
26
+
27
+ export type PageMiddlewareTarget = string | RegExp
28
+
29
+ export type PageRouteMiddleware = {
30
+ id: string
31
+ mode?: PageMiddlewareMode
32
+ target: PageMiddlewareTarget
33
+ priority?: number
34
+ run: (ctx: PageMiddlewareContext) => Promise<PageMiddlewareResult> | PageMiddlewareResult
35
+ }
36
+
37
+ export type PageMiddlewareRegistryEntry = {
38
+ moduleId: string
39
+ middleware: PageRouteMiddleware[]
40
+ }
41
+
42
+ export function matchPageMiddlewareTarget(pathname: string, target: PageMiddlewareTarget): boolean {
43
+ if (target instanceof RegExp) {
44
+ return target.test(pathname)
45
+ }
46
+ if (target.endsWith('*')) {
47
+ return pathname.startsWith(target.slice(0, -1))
48
+ }
49
+ return pathname === target
50
+ }
51
+
52
+ export const CONTINUE_PAGE_MIDDLEWARE: PageMiddlewareResult = { action: 'continue' }
@@ -40,6 +40,19 @@ type RuntimeState = {
40
40
 
41
41
  const GLOBAL_COMPONENT_REGISTRY_KEY = '__openMercatoComponentRegistry__'
42
42
 
43
+ function isComponentOverride(value: unknown): value is ComponentOverride {
44
+ if (!value || typeof value !== 'object') {
45
+ return false
46
+ }
47
+
48
+ const target = (value as { target?: { componentId?: unknown } }).target
49
+ if (!target || typeof target !== 'object') {
50
+ return false
51
+ }
52
+
53
+ return typeof target.componentId === 'string' && target.componentId.length > 0
54
+ }
55
+
43
56
  function getState(): RuntimeState {
44
57
  const globalValue = (globalThis as Record<string, unknown>)[GLOBAL_COMPONENT_REGISTRY_KEY]
45
58
  if (globalValue && typeof globalValue === 'object') {
@@ -63,7 +76,7 @@ export function registerComponent<TProps = unknown>(entry: ComponentRegistryEntr
63
76
 
64
77
  export function registerComponentOverrides(overrides: ComponentOverride[]) {
65
78
  const state = getState()
66
- state.overrides = [...overrides]
79
+ state.overrides = overrides.filter(isComponentOverride)
67
80
  }
68
81
 
69
82
  export function getComponentEntry(componentId: string): ComponentRegistryEntry | null {
@@ -74,6 +87,7 @@ export function getComponentEntry(componentId: string): ComponentRegistryEntry |
74
87
  export function getComponentOverrides(componentId: string, userFeatures?: readonly string[]): ComponentOverride[] {
75
88
  const state = getState()
76
89
  const relevant = state.overrides.filter((override) => {
90
+ if (!isComponentOverride(override)) return false
77
91
  if (override.target.componentId !== componentId) return false
78
92
  if (override.features && override.features.length > 0) {
79
93
  if (!hasAllFeatures(userFeatures, override.features)) return false