@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.
- package/dist/lib/auth/server.js +1 -1
- package/dist/lib/auth/server.js.map +1 -1
- package/dist/lib/crud/custom-route-interceptor.js +27 -0
- package/dist/lib/crud/custom-route-interceptor.js.map +7 -0
- package/dist/lib/crud/errors.js +8 -1
- package/dist/lib/crud/errors.js.map +2 -2
- package/dist/lib/crud/factory.js +2 -2
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/middleware/page-executor.js +42 -0
- package/dist/lib/middleware/page-executor.js.map +7 -0
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/generators/index.js +1 -0
- package/dist/modules/generators/index.js.map +7 -0
- package/dist/modules/generators/types.js +1 -0
- package/dist/modules/generators/types.js.map +7 -0
- package/dist/modules/middleware/page.js +15 -0
- package/dist/modules/middleware/page.js.map +7 -0
- package/dist/modules/widgets/component-registry.js +12 -1
- package/dist/modules/widgets/component-registry.js.map +2 -2
- package/package.json +5 -1
- package/src/lib/auth/server.ts +1 -1
- package/src/lib/crud/__tests__/custom-route-interceptor.test.ts +180 -0
- package/src/lib/crud/custom-route-interceptor.ts +47 -0
- package/src/lib/crud/errors.ts +14 -0
- package/src/lib/crud/factory.ts +2 -2
- package/src/lib/middleware/__tests__/page-executor.test.ts +161 -0
- package/src/lib/middleware/page-executor.ts +63 -0
- package/src/modules/generators/index.ts +1 -0
- package/src/modules/generators/types.ts +29 -0
- package/src/modules/middleware/page.ts +52 -0
- 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 =
|
|
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
|