@open-mercato/shared 0.9.1-develop.7477.1.64ba865101 → 0.9.1-develop.7497.1.543ea44c40
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/bootstrap/dynamicLoader.js +46 -6
- package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
- package/dist/lib/crud/ids.js +13 -1
- package/dist/lib/crud/ids.js.map +2 -2
- package/dist/lib/openapi/generator.js +84 -67
- package/dist/lib/openapi/generator.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/registry.js.map +2 -2
- package/package.json +2 -2
- package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +2 -2
- package/src/lib/bootstrap/__tests__/dynamicLoader.appModuleOverrides.test.ts +2 -2
- package/src/lib/bootstrap/__tests__/dynamicLoader.cacheRecovery.test.ts +2 -2
- package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +2 -2
- package/src/lib/bootstrap/__tests__/dynamicLoader.externalSubpath.test.ts +141 -0
- package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +2 -2
- package/src/lib/bootstrap/dynamicLoader.ts +63 -5
- package/src/lib/crud/__tests__/ids.test.ts +34 -1
- package/src/lib/crud/ids.ts +24 -0
- package/src/lib/openapi/__tests__/optional-path-params.test.ts +102 -0
- package/src/lib/openapi/generator.ts +106 -77
- package/src/modules/registry.ts +5 -0
|
@@ -15,6 +15,12 @@ type PathParamInfo = {
|
|
|
15
15
|
optional?: boolean
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
type PathVariant = {
|
|
19
|
+
path: string
|
|
20
|
+
params: PathParamInfo[]
|
|
21
|
+
omittedParam?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
type ParameterLocation = 'query' | 'path' | 'header'
|
|
19
25
|
|
|
20
26
|
export type JsonSchema = Record<string, unknown>
|
|
@@ -119,6 +125,20 @@ function normalizePath(path: string): { path: string; params: PathParamInfo[] }
|
|
|
119
125
|
return { path: '/' + normalized, params }
|
|
120
126
|
}
|
|
121
127
|
|
|
128
|
+
function expandOptionalPathParams(normalized: { path: string; params: PathParamInfo[] }): PathVariant[] {
|
|
129
|
+
const required = normalized.params.filter((param) => !param.optional)
|
|
130
|
+
const optionalParam = normalized.params.find((param) => param.optional)
|
|
131
|
+
const fullVariant: PathVariant = {
|
|
132
|
+
path: normalized.path,
|
|
133
|
+
params: normalized.params.map((param) => ({ ...param, optional: false })),
|
|
134
|
+
}
|
|
135
|
+
if (!optionalParam) return [fullVariant]
|
|
136
|
+
const placeholder = `/{${optionalParam.name}}`
|
|
137
|
+
if (!normalized.path.endsWith(placeholder)) return [fullVariant]
|
|
138
|
+
const basePath = normalized.path.slice(0, -placeholder.length) || '/'
|
|
139
|
+
return [fullVariant, { path: basePath, params: required, omittedParam: optionalParam.name }]
|
|
140
|
+
}
|
|
141
|
+
|
|
122
142
|
function unwrap(schema?: ZodTypeAny): {
|
|
123
143
|
schema: ZodTypeAny | undefined
|
|
124
144
|
optional: boolean
|
|
@@ -681,14 +701,14 @@ function buildParameters(
|
|
|
681
701
|
|
|
682
702
|
if (location === 'path' && pathParamNames && pathParamNames.length) {
|
|
683
703
|
const merged = mergePathParamSchemas(schema, pathParamNames)
|
|
684
|
-
for (const { name, schema: paramSchema
|
|
704
|
+
for (const { name, schema: paramSchema } of merged) {
|
|
685
705
|
const jsonSchema = zodToJsonSchema(paramSchema)
|
|
686
706
|
const example = generateExample(paramSchema)
|
|
687
707
|
const description = extractZodDescription(paramSchema)
|
|
688
708
|
params.push({
|
|
689
709
|
name,
|
|
690
710
|
in: 'path',
|
|
691
|
-
required:
|
|
711
|
+
required: true,
|
|
692
712
|
schema: jsonSchema ?? { type: 'string' },
|
|
693
713
|
example,
|
|
694
714
|
...(description ? { description } : {}),
|
|
@@ -738,7 +758,7 @@ function buildParameters(
|
|
|
738
758
|
}
|
|
739
759
|
|
|
740
760
|
function mergePathParamSchemas(schema: ZodTypeAny | undefined, params: PathParamInfo[]) {
|
|
741
|
-
const merged: Array<{ name: string; schema: ZodTypeAny | undefined
|
|
761
|
+
const merged: Array<{ name: string; schema: ZodTypeAny | undefined }> = []
|
|
742
762
|
const map: Record<string, ZodTypeAny> = {}
|
|
743
763
|
if (schema) {
|
|
744
764
|
const { schema: unwrapped } = unwrap(schema)
|
|
@@ -753,7 +773,6 @@ function mergePathParamSchemas(schema: ZodTypeAny | undefined, params: PathParam
|
|
|
753
773
|
merged.push({
|
|
754
774
|
name: param.name,
|
|
755
775
|
schema: map[param.name],
|
|
756
|
-
optional: !!param.optional,
|
|
757
776
|
})
|
|
758
777
|
}
|
|
759
778
|
return merged
|
|
@@ -1051,6 +1070,7 @@ export function buildOpenApiDocument(modules: Module[], options: OpenApiDocument
|
|
|
1051
1070
|
|
|
1052
1071
|
const includeAccessControlMetadata = options.includeAccessControlMetadata !== false
|
|
1053
1072
|
const tags = new Map<string, string | undefined>()
|
|
1073
|
+
const derivedOperations = new Set<string>()
|
|
1054
1074
|
|
|
1055
1075
|
for (const moduleEntry of modules) {
|
|
1056
1076
|
const defaultTag = moduleEntry.info?.title ?? toTitle(moduleEntry.id)
|
|
@@ -1060,85 +1080,94 @@ export function buildOpenApiDocument(modules: Module[], options: OpenApiDocument
|
|
|
1060
1080
|
for (const api of apis) {
|
|
1061
1081
|
const routeDoc = collectRouteDoc(api, moduleEntry.id)
|
|
1062
1082
|
const moduleTag = routeDoc?.tag ?? defaultTag
|
|
1063
|
-
const normalized = normalizePath((api as any).path ?? (api as any).path ?? '')
|
|
1064
|
-
const pathKey = normalized.path
|
|
1065
|
-
if (!doc.paths[pathKey]) doc.paths[pathKey] = {}
|
|
1066
1083
|
const availableMethods: HttpMethod[] =
|
|
1067
1084
|
'handlers' in api
|
|
1068
1085
|
? HTTP_METHODS.filter((method) => typeof (api as ModuleApiRouteFile).handlers?.[method] === 'function')
|
|
1069
1086
|
: [api.method as HttpMethod]
|
|
1070
1087
|
|
|
1071
|
-
for (const
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
: undefined
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1088
|
+
for (const normalized of expandOptionalPathParams(normalizePath((api as any).path ?? ''))) {
|
|
1089
|
+
const pathKey = normalized.path
|
|
1090
|
+
if (!doc.paths[pathKey]) doc.paths[pathKey] = {}
|
|
1091
|
+
|
|
1092
|
+
for (const method of availableMethods) {
|
|
1093
|
+
const methodLower = method.toLowerCase() as Lowercase<HttpMethod>
|
|
1094
|
+
const operationKey = `${methodLower} ${pathKey}`
|
|
1095
|
+
const existing = doc.paths[pathKey][methodLower]
|
|
1096
|
+
if (existing && (normalized.omittedParam || !derivedOperations.has(operationKey))) continue
|
|
1097
|
+
if (normalized.omittedParam) derivedOperations.add(operationKey)
|
|
1098
|
+
else derivedOperations.delete(operationKey)
|
|
1099
|
+
|
|
1100
|
+
const metadata = 'handlers' in api ? (api as ModuleApiRouteFile).metadata?.[method] : undefined
|
|
1101
|
+
const methodDoc = routeDoc?.methods?.[method]
|
|
1102
|
+
const summary = methodDoc?.summary ?? routeDoc?.summary ?? `${method} ${pathKey}`
|
|
1103
|
+
const baseDescription = methodDoc?.description ?? routeDoc?.description
|
|
1104
|
+
const meta = metadata && typeof metadata === 'object' ? (metadata as Record<string, unknown>) : undefined
|
|
1105
|
+
const requireFeatures = Array.isArray(meta?.['requireFeatures'])
|
|
1106
|
+
? (meta!['requireFeatures'] as string[])
|
|
1107
|
+
: undefined
|
|
1108
|
+
const requireRoles = Array.isArray(meta?.['requireRoles'])
|
|
1109
|
+
? (meta!['requireRoles'] as string[])
|
|
1110
|
+
: undefined
|
|
1111
|
+
const requireAuth = meta?.['requireAuth'] === true
|
|
1112
|
+
const descriptionParts: string[] = []
|
|
1113
|
+
if (baseDescription) descriptionParts.push(baseDescription)
|
|
1114
|
+
if (includeAccessControlMetadata && Array.isArray(requireFeatures) && requireFeatures.length) {
|
|
1115
|
+
descriptionParts.push(`Requires features: ${requireFeatures.join(', ')}`)
|
|
1116
|
+
}
|
|
1117
|
+
if (includeAccessControlMetadata && Array.isArray(requireRoles) && requireRoles.length) {
|
|
1118
|
+
descriptionParts.push(`Requires roles: ${requireRoles.join(', ')}`)
|
|
1119
|
+
}
|
|
1096
1120
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1121
|
+
const querySchema = methodDoc?.query
|
|
1122
|
+
const pathSchema = methodDoc?.pathParams ?? routeDoc?.pathParams
|
|
1123
|
+
const headerSchema = methodDoc?.headers
|
|
1124
|
+
const requestBody = methodDoc?.requestBody
|
|
1125
|
+
const examples = collectExamples(querySchema, requestBody?.schema, pathSchema, headerSchema, metadata)
|
|
1126
|
+
const curlSample = buildCurlSample(
|
|
1127
|
+
method,
|
|
1128
|
+
pathKey,
|
|
1129
|
+
normalized.params,
|
|
1130
|
+
examples,
|
|
1131
|
+
options.baseUrlForExamples ?? 'https://api.open-mercato.local',
|
|
1132
|
+
metadata,
|
|
1133
|
+
requestBody
|
|
1134
|
+
)
|
|
1135
|
+
|
|
1136
|
+
doc.paths[pathKey][methodLower] = {
|
|
1137
|
+
operationId: methodDoc?.operationId
|
|
1138
|
+
? normalized.omittedParam
|
|
1139
|
+
? `${methodDoc.operationId}_without_${normalizeOperationIdSegment(normalized.omittedParam)}`
|
|
1140
|
+
: methodDoc.operationId
|
|
1141
|
+
: resolveOperationId(moduleEntry.id, pathKey, method),
|
|
1142
|
+
summary,
|
|
1143
|
+
description: descriptionParts.length ? descriptionParts.join('\n\n') : undefined,
|
|
1144
|
+
tags: methodDoc?.tags ?? (moduleTag ? [moduleTag] : undefined),
|
|
1145
|
+
deprecated: methodDoc?.deprecated,
|
|
1146
|
+
externalDocs: methodDoc?.externalDocs,
|
|
1147
|
+
parameters: [
|
|
1148
|
+
...buildParameters(pathSchema, 'path', normalized.params),
|
|
1149
|
+
...buildParameters(querySchema, 'query'),
|
|
1150
|
+
...buildParameters(headerSchema, 'header'),
|
|
1151
|
+
].filter(Boolean),
|
|
1152
|
+
requestBody: buildRequestBody(requestBody),
|
|
1153
|
+
responses: buildResponses(method, methodDoc?.responses, methodDoc?.errors, metadata),
|
|
1154
|
+
security: buildSecurity(metadata, methodDoc, options.defaultSecurity),
|
|
1155
|
+
'x-codeSamples': methodDoc?.codeSamples ?? [
|
|
1156
|
+
{
|
|
1157
|
+
lang: 'curl',
|
|
1158
|
+
label: 'cURL',
|
|
1159
|
+
source: curlSample,
|
|
1160
|
+
},
|
|
1161
|
+
],
|
|
1162
|
+
...(includeAccessControlMetadata && Array.isArray(requireFeatures) && requireFeatures.length
|
|
1163
|
+
? { 'x-require-features': requireFeatures }
|
|
1164
|
+
: {}),
|
|
1165
|
+
...(includeAccessControlMetadata && Array.isArray(requireRoles) && requireRoles.length
|
|
1166
|
+
? { 'x-require-roles': requireRoles }
|
|
1167
|
+
: {}),
|
|
1168
|
+
...(requireAuth ? { 'x-require-auth': true } : {}),
|
|
1169
|
+
...(methodDoc?.extensions ?? {}),
|
|
1170
|
+
}
|
|
1142
1171
|
}
|
|
1143
1172
|
}
|
|
1144
1173
|
}
|
package/src/modules/registry.ts
CHANGED
|
@@ -178,6 +178,11 @@ export type ApiRouteManifestEntry = {
|
|
|
178
178
|
export type ModuleCli = {
|
|
179
179
|
command: string
|
|
180
180
|
run: (argv: string[]) => Promise<void> | void
|
|
181
|
+
// Usage text printed by the `mercato` dispatcher when the command is invoked with --help/-h.
|
|
182
|
+
help?: string
|
|
183
|
+
// Set when the command parses --help/-h itself; the dispatcher then forwards the flag
|
|
184
|
+
// instead of intercepting it, so the command can print its own richer usage text.
|
|
185
|
+
handlesHelp?: boolean
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
export type ModuleSubscriber = {
|