@open-mercato/shared 0.6.7-develop.6622.1.4d29ddaca4 → 0.6.7-develop.6628.1.f9e4ecf70f

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.
@@ -1,2 +1,2 @@
1
- [build:shared] found 238 entry points
1
+ [build:shared] found 239 entry points
2
2
  [build:shared] built successfully
@@ -0,0 +1,44 @@
1
+ function manifestKey(entry) {
2
+ return entry.kind === "legacy" && entry.method ? `${entry.moduleId}\0${entry.method}\0${entry.path}` : `${entry.moduleId}\0${entry.path}`;
3
+ }
4
+ async function loadOpenApiExport(entry) {
5
+ if (!entry) return void 0;
6
+ try {
7
+ const mod = await entry.load();
8
+ const openApi = mod.openApi;
9
+ return openApi && typeof openApi === "object" ? openApi : void 0;
10
+ } catch {
11
+ return void 0;
12
+ }
13
+ }
14
+ async function attachOpenApiDocsToModules(modules, manifests) {
15
+ const manifestsByKey = /* @__PURE__ */ new Map();
16
+ for (const entry of manifests) {
17
+ manifestsByKey.set(manifestKey(entry), entry);
18
+ }
19
+ return Promise.all(
20
+ modules.map(async (mod) => {
21
+ if (!Array.isArray(mod.apis) || !mod.apis.length) return mod;
22
+ const apis = await Promise.all(
23
+ mod.apis.map(async (api) => {
24
+ if (api.docs) return api;
25
+ if ("handlers" in api) {
26
+ const entry2 = manifestsByKey.get(`${mod.id}\0${api.path}`);
27
+ const docs2 = await loadOpenApiExport(entry2);
28
+ if (!docs2) return api;
29
+ return { ...api, docs: docs2 };
30
+ }
31
+ const entry = manifestsByKey.get(`${mod.id}\0${api.method}\0${api.path}`);
32
+ const docs = await loadOpenApiExport(entry);
33
+ if (!docs) return api;
34
+ return { ...api, docs };
35
+ })
36
+ );
37
+ return { ...mod, apis };
38
+ })
39
+ );
40
+ }
41
+ export {
42
+ attachOpenApiDocsToModules
43
+ };
44
+ //# sourceMappingURL=attach-docs.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/openapi/attach-docs.ts"],
4
+ "sourcesContent": ["import type {\n ApiRouteManifestEntry,\n Module,\n ModuleApi,\n ModuleApiLegacy,\n ModuleApiRouteFile,\n} from '../../modules/registry'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from './types'\n\nfunction manifestKey(entry: ApiRouteManifestEntry): string {\n return entry.kind === 'legacy' && entry.method\n ? `${entry.moduleId}\\0${entry.method}\\0${entry.path}`\n : `${entry.moduleId}\\0${entry.path}`\n}\n\nasync function loadOpenApiExport(entry: ApiRouteManifestEntry | undefined): Promise<unknown> {\n if (!entry) return undefined\n try {\n const mod = await entry.load()\n const openApi = (mod as { openApi?: unknown }).openApi\n return openApi && typeof openApi === 'object' ? openApi : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * The lazy runtime module registry omits each route's `docs` (the `openApi`\n * export) so route files stay dynamically imported. Documentation endpoints\n * that build the OpenAPI document from that registry must re-attach the docs\n * by loading each route module through the API route manifests on demand.\n */\nexport async function attachOpenApiDocsToModules(\n modules: Module[],\n manifests: ApiRouteManifestEntry[],\n): Promise<Module[]> {\n const manifestsByKey = new Map<string, ApiRouteManifestEntry>()\n for (const entry of manifests) {\n manifestsByKey.set(manifestKey(entry), entry)\n }\n\n return Promise.all(\n modules.map(async (mod) => {\n if (!Array.isArray(mod.apis) || !mod.apis.length) return mod\n const apis = await Promise.all(\n mod.apis.map(async (api): Promise<ModuleApi> => {\n if ((api as { docs?: unknown }).docs) return api\n if ('handlers' in api) {\n const entry = manifestsByKey.get(`${mod.id}\\0${api.path}`)\n const docs = await loadOpenApiExport(entry)\n if (!docs) return api\n return { ...api, docs: docs as OpenApiRouteDoc } satisfies ModuleApiRouteFile\n }\n const entry = manifestsByKey.get(`${mod.id}\\0${api.method}\\0${api.path}`)\n const docs = await loadOpenApiExport(entry)\n if (!docs) return api\n return { ...api, docs: docs as OpenApiMethodDoc } satisfies ModuleApiLegacy\n }),\n )\n return { ...mod, apis }\n }),\n )\n}\n"],
5
+ "mappings": "AASA,SAAS,YAAY,OAAsC;AACzD,SAAO,MAAM,SAAS,YAAY,MAAM,SACpC,GAAG,MAAM,QAAQ,KAAK,MAAM,MAAM,KAAK,MAAM,IAAI,KACjD,GAAG,MAAM,QAAQ,KAAK,MAAM,IAAI;AACtC;AAEA,eAAe,kBAAkB,OAA4D;AAC3F,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,KAAK;AAC7B,UAAM,UAAW,IAA8B;AAC/C,WAAO,WAAW,OAAO,YAAY,WAAW,UAAU;AAAA,EAC5D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,2BACpB,SACA,WACmB;AACnB,QAAM,iBAAiB,oBAAI,IAAmC;AAC9D,aAAW,SAAS,WAAW;AAC7B,mBAAe,IAAI,YAAY,KAAK,GAAG,KAAK;AAAA,EAC9C;AAEA,SAAO,QAAQ;AAAA,IACb,QAAQ,IAAI,OAAO,QAAQ;AACzB,UAAI,CAAC,MAAM,QAAQ,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,OAAQ,QAAO;AACzD,YAAM,OAAO,MAAM,QAAQ;AAAA,QACzB,IAAI,KAAK,IAAI,OAAO,QAA4B;AAC9C,cAAK,IAA2B,KAAM,QAAO;AAC7C,cAAI,cAAc,KAAK;AACrB,kBAAMA,SAAQ,eAAe,IAAI,GAAG,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE;AACzD,kBAAMC,QAAO,MAAM,kBAAkBD,MAAK;AAC1C,gBAAI,CAACC,MAAM,QAAO;AAClB,mBAAO,EAAE,GAAG,KAAK,MAAMA,MAAwB;AAAA,UACjD;AACA,gBAAM,QAAQ,eAAe,IAAI,GAAG,IAAI,EAAE,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,EAAE;AACxE,gBAAM,OAAO,MAAM,kBAAkB,KAAK;AAC1C,cAAI,CAAC,KAAM,QAAO;AAClB,iBAAO,EAAE,GAAG,KAAK,KAA+B;AAAA,QAClD,CAAC;AAAA,MACH;AACA,aAAO,EAAE,GAAG,KAAK,KAAK;AAAA,IACxB,CAAC;AAAA,EACH;AACF;",
6
+ "names": ["entry", "docs"]
7
+ }
@@ -2,7 +2,9 @@ export * from "./types.js";
2
2
  import { buildOpenApiDocument, generateMarkdownFromOpenApi } from "./generator.js";
3
3
  export * from "./crud.js";
4
4
  import { sanitizeOpenApiDocument } from "./sanitize.js";
5
+ import { attachOpenApiDocsToModules } from "./attach-docs.js";
5
6
  export {
7
+ attachOpenApiDocsToModules,
6
8
  buildOpenApiDocument,
7
9
  generateMarkdownFromOpenApi,
8
10
  sanitizeOpenApiDocument
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/openapi/index.ts"],
4
- "sourcesContent": ["export * from './types'\nexport { buildOpenApiDocument, generateMarkdownFromOpenApi } from './generator'\nexport * from './crud'\nexport { sanitizeOpenApiDocument } from './sanitize'\n"],
5
- "mappings": "AAAA,cAAc;AACd,SAAS,sBAAsB,mCAAmC;AAClE,cAAc;AACd,SAAS,+BAA+B;",
4
+ "sourcesContent": ["export * from './types'\nexport { buildOpenApiDocument, generateMarkdownFromOpenApi } from './generator'\nexport * from './crud'\nexport { sanitizeOpenApiDocument } from './sanitize'\nexport { attachOpenApiDocsToModules } from './attach-docs'\n"],
5
+ "mappings": "AAAA,cAAc;AACd,SAAS,sBAAsB,mCAAmC;AAClE,cAAc;AACd,SAAS,+BAA+B;AACxC,SAAS,kCAAkC;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.6.7-develop.6622.1.4d29ddaca4";
1
+ const APP_VERSION = "0.6.7-develop.6628.1.f9e4ecf70f";
2
2
  const appVersion = APP_VERSION;
3
3
  export {
4
4
  APP_VERSION,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/version.ts"],
4
- "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.7-develop.6622.1.4d29ddaca4'\nexport const appVersion = APP_VERSION\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.7-develop.6628.1.f9e4ecf70f'\nexport const appVersion = APP_VERSION\n"],
5
5
  "mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/shared",
3
- "version": "0.6.7-develop.6622.1.4d29ddaca4",
3
+ "version": "0.6.7-develop.6628.1.f9e4ecf70f",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -97,7 +97,7 @@
97
97
  "@mikro-orm/core": "^7.1.5",
98
98
  "@mikro-orm/decorators": "^7.1.5",
99
99
  "@mikro-orm/postgresql": "^7.1.5",
100
- "@open-mercato/cache": "0.6.7-develop.6622.1.4d29ddaca4",
100
+ "@open-mercato/cache": "0.6.7-develop.6628.1.f9e4ecf70f",
101
101
  "@types/sanitize-html": "^2.16.1",
102
102
  "dotenv": "^17.4.2",
103
103
  "pino": "^10.3.1",
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod'
2
+ import { attachOpenApiDocsToModules } from '../attach-docs'
3
+ import { buildOpenApiDocument } from '../generator'
4
+ import type { ApiRouteManifestEntry, Module } from '../../../modules/registry'
5
+
6
+ const inviteSchema = z.object({ email: z.string().email(), roleIds: z.array(z.string()) })
7
+
8
+ const routeFileOpenApi = {
9
+ methods: {
10
+ POST: {
11
+ summary: 'Invite user',
12
+ requestBody: { schema: inviteSchema, description: 'Invitation payload' },
13
+ },
14
+ },
15
+ }
16
+
17
+ const legacyOpenApi = {
18
+ summary: 'Legacy create',
19
+ requestBody: { schema: z.object({ name: z.string() }) },
20
+ }
21
+
22
+ function makeModules(): Module[] {
23
+ return [
24
+ {
25
+ id: 'customer_accounts',
26
+ apis: [
27
+ {
28
+ path: '/customer_accounts/admin/users-invite',
29
+ metadata: { POST: { requireAuth: true } },
30
+ handlers: { POST: async () => new Response(null) },
31
+ },
32
+ {
33
+ method: 'POST',
34
+ path: '/customer_accounts/legacy-create',
35
+ handler: async () => new Response(null),
36
+ },
37
+ ],
38
+ } as unknown as Module,
39
+ ]
40
+ }
41
+
42
+ const manifests: ApiRouteManifestEntry[] = [
43
+ {
44
+ moduleId: 'customer_accounts',
45
+ kind: 'route-file',
46
+ path: '/customer_accounts/admin/users-invite',
47
+ methods: ['POST'],
48
+ load: async () => ({ openApi: routeFileOpenApi }),
49
+ },
50
+ {
51
+ moduleId: 'customer_accounts',
52
+ kind: 'legacy',
53
+ method: 'POST',
54
+ path: '/customer_accounts/legacy-create',
55
+ methods: ['POST'],
56
+ load: async () => ({ openApi: legacyOpenApi }),
57
+ },
58
+ ]
59
+
60
+ describe('attachOpenApiDocsToModules', () => {
61
+ it('re-attaches route docs the runtime registry omits, restoring request bodies (#4361)', async () => {
62
+ const bare = buildOpenApiDocument(makeModules())
63
+ const barePost = bare.paths['/customer_accounts/admin/users-invite']?.post as Record<string, any>
64
+ expect(barePost.requestBody).toBeUndefined()
65
+
66
+ const enriched = await attachOpenApiDocsToModules(makeModules(), manifests)
67
+ const doc = buildOpenApiDocument(enriched)
68
+
69
+ const post = doc.paths['/customer_accounts/admin/users-invite']?.post as Record<string, any>
70
+ expect(post.summary).toBe('Invite user')
71
+ const bodySchema = post.requestBody?.content?.['application/json']?.schema
72
+ expect(bodySchema?.properties?.email).toBeDefined()
73
+ expect(bodySchema?.required).toContain('email')
74
+
75
+ const legacyPost = doc.paths['/customer_accounts/legacy-create']?.post as Record<string, any>
76
+ expect(legacyPost.summary).toBe('Legacy create')
77
+ expect(legacyPost.requestBody?.content?.['application/json']?.schema?.properties?.name).toBeDefined()
78
+ })
79
+
80
+ it('keeps entries untouched when no manifest matches or loading fails', async () => {
81
+ const failing: ApiRouteManifestEntry[] = [
82
+ {
83
+ moduleId: 'customer_accounts',
84
+ kind: 'route-file',
85
+ path: '/customer_accounts/admin/users-invite',
86
+ methods: ['POST'],
87
+ load: async () => {
88
+ throw new Error('boom')
89
+ },
90
+ },
91
+ ]
92
+ const enriched = await attachOpenApiDocsToModules(makeModules(), failing)
93
+ const apis = enriched[0].apis ?? []
94
+ expect((apis[0] as { docs?: unknown }).docs).toBeUndefined()
95
+ expect((apis[1] as { docs?: unknown }).docs).toBeUndefined()
96
+ })
97
+
98
+ it('does not overwrite docs that are already present', async () => {
99
+ const modules = makeModules()
100
+ const existingDocs = { methods: { POST: { summary: 'Existing' } } }
101
+ ;(modules[0].apis?.[0] as { docs?: unknown }).docs = existingDocs
102
+ const enriched = await attachOpenApiDocsToModules(modules, manifests)
103
+ expect((enriched[0].apis?.[0] as { docs?: unknown }).docs).toBe(existingDocs)
104
+ })
105
+ })
@@ -0,0 +1,63 @@
1
+ import type {
2
+ ApiRouteManifestEntry,
3
+ Module,
4
+ ModuleApi,
5
+ ModuleApiLegacy,
6
+ ModuleApiRouteFile,
7
+ } from '../../modules/registry'
8
+ import type { OpenApiMethodDoc, OpenApiRouteDoc } from './types'
9
+
10
+ function manifestKey(entry: ApiRouteManifestEntry): string {
11
+ return entry.kind === 'legacy' && entry.method
12
+ ? `${entry.moduleId}\0${entry.method}\0${entry.path}`
13
+ : `${entry.moduleId}\0${entry.path}`
14
+ }
15
+
16
+ async function loadOpenApiExport(entry: ApiRouteManifestEntry | undefined): Promise<unknown> {
17
+ if (!entry) return undefined
18
+ try {
19
+ const mod = await entry.load()
20
+ const openApi = (mod as { openApi?: unknown }).openApi
21
+ return openApi && typeof openApi === 'object' ? openApi : undefined
22
+ } catch {
23
+ return undefined
24
+ }
25
+ }
26
+
27
+ /**
28
+ * The lazy runtime module registry omits each route's `docs` (the `openApi`
29
+ * export) so route files stay dynamically imported. Documentation endpoints
30
+ * that build the OpenAPI document from that registry must re-attach the docs
31
+ * by loading each route module through the API route manifests on demand.
32
+ */
33
+ export async function attachOpenApiDocsToModules(
34
+ modules: Module[],
35
+ manifests: ApiRouteManifestEntry[],
36
+ ): Promise<Module[]> {
37
+ const manifestsByKey = new Map<string, ApiRouteManifestEntry>()
38
+ for (const entry of manifests) {
39
+ manifestsByKey.set(manifestKey(entry), entry)
40
+ }
41
+
42
+ return Promise.all(
43
+ modules.map(async (mod) => {
44
+ if (!Array.isArray(mod.apis) || !mod.apis.length) return mod
45
+ const apis = await Promise.all(
46
+ mod.apis.map(async (api): Promise<ModuleApi> => {
47
+ if ((api as { docs?: unknown }).docs) return api
48
+ if ('handlers' in api) {
49
+ const entry = manifestsByKey.get(`${mod.id}\0${api.path}`)
50
+ const docs = await loadOpenApiExport(entry)
51
+ if (!docs) return api
52
+ return { ...api, docs: docs as OpenApiRouteDoc } satisfies ModuleApiRouteFile
53
+ }
54
+ const entry = manifestsByKey.get(`${mod.id}\0${api.method}\0${api.path}`)
55
+ const docs = await loadOpenApiExport(entry)
56
+ if (!docs) return api
57
+ return { ...api, docs: docs as OpenApiMethodDoc } satisfies ModuleApiLegacy
58
+ }),
59
+ )
60
+ return { ...mod, apis }
61
+ }),
62
+ )
63
+ }
@@ -2,3 +2,4 @@ export * from './types'
2
2
  export { buildOpenApiDocument, generateMarkdownFromOpenApi } from './generator'
3
3
  export * from './crud'
4
4
  export { sanitizeOpenApiDocument } from './sanitize'
5
+ export { attachOpenApiDocsToModules } from './attach-docs'