@monkeyplus/flow 5.0.0-rc.98 → 6.0.0
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/README.md +34 -0
- package/entry-server.d.ts +1 -0
- package/entry-server.mjs +5 -0
- package/modules/content/module.d.ts +6 -0
- package/modules/content/module.mjs +32 -0
- package/modules/content/query.mjs +104 -0
- package/modules/sitemap/handler.mjs +17 -0
- package/modules/sitemap/module.d.ts +6 -0
- package/modules/sitemap/module.mjs +28 -0
- package/modules/strapi/README.md +235 -0
- package/modules/strapi/module.d.ts +11 -0
- package/modules/strapi/module.mjs +69 -0
- package/modules/strapi/proxy.mjs +54 -0
- package/modules/strapi/runtime/client.d.ts +30 -0
- package/modules/strapi/runtime/client.mjs +248 -0
- package/package.json +73 -77
- package/server/lib/handler.d.ts +1 -0
- package/server/lib/handler.mjs +43 -0
- package/server/lib/pages.d.ts +20 -0
- package/server/lib/pages.mjs +276 -0
- package/server/lib/render.d.ts +7 -0
- package/server/lib/render.mjs +156 -0
- package/server/plugins/00.lifecycle.d.ts +2 -0
- package/server/plugins/00.lifecycle.mjs +9 -0
- package/server/renderer.d.ts +3 -0
- package/server/renderer.mjs +14 -0
- package/server/routes/api/health.get.d.ts +2 -0
- package/server/routes/api/health.get.mjs +5 -0
- package/server.d.ts +1 -0
- package/server.mjs +32 -0
- package/src/main.d.ts +1 -0
- package/src/main.mjs +29 -0
- package/src/public/boot.d.ts +1 -0
- package/src/public/components.d.ts +1 -0
- package/src/public/components.mjs +1 -0
- package/src/public/head.d.ts +1 -0
- package/src/public/head.mjs +1 -0
- package/src/public/index.d.ts +5 -0
- package/src/public/index.mjs +2 -0
- package/src/public/modules/content.d.ts +2 -0
- package/src/public/modules/content.mjs +1 -0
- package/src/public/modules/sitemap.d.ts +2 -0
- package/src/public/modules/sitemap.mjs +1 -0
- package/src/public/modules/strapi.d.ts +2 -0
- package/src/public/modules/strapi.mjs +1 -0
- package/src/public/nitro.d.ts +6 -0
- package/src/public/nitro.mjs +78 -0
- package/src/public/shared.d.ts +2 -0
- package/src/public/shared.mjs +18 -0
- package/src/public/vite.d.ts +6 -0
- package/src/public/vite.mjs +273 -0
- package/src/public/vue.d.ts +1 -0
- package/src/public/vue.mjs +1 -0
- package/src/runtime/boot.d.ts +9 -0
- package/src/runtime/components/FlowIsland.d.ts +42 -0
- package/src/runtime/components/FlowIsland.mjs +35 -0
- package/src/runtime/config.d.ts +73 -0
- package/src/runtime/config.mjs +26 -0
- package/src/runtime/head.d.ts +3 -0
- package/src/runtime/head.mjs +7 -0
- package/src/runtime/islands.d.ts +2 -0
- package/src/runtime/islands.mjs +52 -0
- package/src/runtime/modules.d.ts +12 -0
- package/src/runtime/modules.mjs +115 -0
- package/src/runtime/page-discovery.d.ts +6 -0
- package/src/runtime/page-discovery.mjs +175 -0
- package/src/runtime/pages.d.ts +107 -0
- package/src/runtime/pages.mjs +3 -0
- package/src/runtime/ssg.d.ts +9 -0
- package/src/runtime/ssg.mjs +37 -0
- package/src/runtime/virtual-pages.d.ts +8 -0
- package/src/runtime/virtual-pages.mjs +151 -0
- package/src/runtime/virtual.d.ts +103 -0
- package/src/runtime/vite-assets.d.ts +9 -0
- package/src/runtime/vue.d.ts +6 -0
- package/src/runtime/vue.mjs +6 -0
- package/src/styles.css +1 -0
- package/app.d.ts +0 -1
- package/bin/flow.mjs +0 -2
- package/dist/app/composables/index.d.ts +0 -5
- package/dist/app/composables/index.mjs +0 -12
- package/dist/app/entry.async.d.ts +0 -3
- package/dist/app/entry.async.mjs +0 -1
- package/dist/app/entry.d.ts +0 -3
- package/dist/app/entry.mjs +0 -23
- package/dist/app/flow.d.ts +0 -80
- package/dist/app/flow.mjs +0 -88
- package/dist/app/index.d.ts +0 -3
- package/dist/app/index.mjs +0 -3
- package/dist/chunks/dev-bundler.mjs +0 -247
- package/dist/chunks/external.mjs +0 -37
- package/dist/chunks/index.mjs +0 -1140
- package/dist/chunks/vite-node.mjs +0 -155
- package/dist/core/runtime/client.manifest.d.mts +0 -2
- package/dist/core/runtime/client.manifest.mjs +0 -6
- package/dist/core/runtime/nitro/flow.d.ts +0 -3
- package/dist/core/runtime/nitro/flow.mjs +0 -33
- package/dist/core/runtime/nitro/paths.d.ts +0 -4
- package/dist/core/runtime/nitro/paths.mjs +0 -15
- package/dist/core/runtime/nitro/renderer.d.ts +0 -2
- package/dist/core/runtime/nitro/renderer.mjs +0 -101
- package/dist/core/runtime/vite-node-shared.d.mts +0 -1
- package/dist/core/runtime/vite-node-shared.d.ts +0 -8
- package/dist/core/runtime/vite-node-shared.mjs +0 -3
- package/dist/core/runtime/vite-node.d.mts +0 -2
- package/dist/core/runtime/vite-node.mjs +0 -42
- package/dist/head/runtime/composables.d.ts +0 -9
- package/dist/head/runtime/composables.mjs +0 -2
- package/dist/head/runtime/index.d.ts +0 -1
- package/dist/head/runtime/index.mjs +0 -1
- package/dist/head/runtime/plugin.mjs +0 -5
- package/dist/index.d.ts +0 -11
- package/dist/index.mjs +0 -27
- package/dist/pages/runtime/helpers/index.d.ts +0 -5
- package/dist/pages/runtime/helpers/index.mjs +0 -31
- package/dist/pages/runtime/index.d.ts +0 -10
- package/dist/pages/runtime/index.mjs +0 -13
- package/dist/pages/runtime/pages.mjs +0 -100
- package/dist/vite-client/runtime/injectManifest.d.ts +0 -26
- package/dist/vite-client/runtime/injectManifest.mjs +0 -110
- package/dist/vite-client/runtime/plugin.mjs +0 -28
- package/types.d.ts +0 -2
- /package/{dist/head/runtime/plugin.d.ts → modules/content/query.d.ts} +0 -0
- /package/{dist/pages/runtime/pages.d.ts → modules/sitemap/handler.d.ts} +0 -0
- /package/{dist/vite-client/runtime/plugin.d.ts → modules/strapi/proxy.d.ts} +0 -0
- /package/{dist/pages/runtime/helpers/chunks.d.ts → src/public/boot.mjs} +0 -0
- /package/{dist/pages/runtime/helpers/chunks.mjs → src/runtime/boot.mjs} +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @monkeyplus/flow
|
|
2
|
+
|
|
3
|
+
`@monkeyplus/flow` es ahora un paquete reusable con un `playground/` de referencia dentro del mismo workspace.
|
|
4
|
+
|
|
5
|
+
## Desarrollo diario
|
|
6
|
+
|
|
7
|
+
- `pnpm dev`: arranca el proyecto de referencia en `playground/`
|
|
8
|
+
- `pnpm build`: construye el `playground/`
|
|
9
|
+
- `pnpm preview`: previsualiza el `playground/`
|
|
10
|
+
|
|
11
|
+
## Empaquetado del paquete
|
|
12
|
+
|
|
13
|
+
- `pnpm build:package`: compila el paquete publicable en `dist/`
|
|
14
|
+
- `pnpm pack:package`: genera un tarball npm desde `dist/`
|
|
15
|
+
- `pnpm publish:package`: publica el paquete desde `dist/` con acceso publico
|
|
16
|
+
|
|
17
|
+
El workspace consume la API fuente del root para mantener DX local, mientras que `dist/` genera un artefacto publicable con exports ya compilados.
|
|
18
|
+
|
|
19
|
+
## API pública
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { defineFlowConfig, definePage } from '@monkeyplus/flow';
|
|
23
|
+
import { FlowIsland } from '@monkeyplus/flow/components';
|
|
24
|
+
import { getClientHead } from '@monkeyplus/flow/head';
|
|
25
|
+
import sitemapModule from '@monkeyplus/flow/modules/sitemap';
|
|
26
|
+
import strapiModule from '@monkeyplus/flow/modules/strapi';
|
|
27
|
+
import { createFlowNitroConfig } from '@monkeyplus/flow/nitro';
|
|
28
|
+
import { createFlowViteConfig } from '@monkeyplus/flow/vite';
|
|
29
|
+
import { installFlowVuePlugins } from '@monkeyplus/flow/vue';
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Playground
|
|
33
|
+
|
|
34
|
+
La app de referencia vive en `playground/` y ya no en la raíz. Eso permite mantener el root enfocado en la distribución del paquete sin perder el flujo de desarrollo existente.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/entry-server.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { defineFlowModule } from "../../src/runtime/config.ts";
|
|
3
|
+
import { resolvePackageFile, resolvePackagePath } from "../../src/public/shared.ts";
|
|
4
|
+
export default defineFlowModule({
|
|
5
|
+
meta: {
|
|
6
|
+
name: "content",
|
|
7
|
+
configKey: "content"
|
|
8
|
+
},
|
|
9
|
+
defaults: {
|
|
10
|
+
apiBase: "/api/_content",
|
|
11
|
+
dir: "content"
|
|
12
|
+
},
|
|
13
|
+
setup(options, context) {
|
|
14
|
+
const contentDir = resolve(context.projectRoot, options.dir);
|
|
15
|
+
const queryHandlerPath = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/content/query.ts") : resolvePackageFile("modules/content/query.ts", "modules/content/query.mjs", "modules/content/query.js");
|
|
16
|
+
context.nitro.handlers.push({
|
|
17
|
+
method: "get",
|
|
18
|
+
route: `${options.apiBase}/query`,
|
|
19
|
+
handler: queryHandlerPath
|
|
20
|
+
});
|
|
21
|
+
context.nitro.routeRules[`${options.apiBase}/**`] = {
|
|
22
|
+
cors: true
|
|
23
|
+
};
|
|
24
|
+
context.nitro.runtimeConfig.flow = {
|
|
25
|
+
...typeof context.nitro.runtimeConfig.flow === "object" && context.nitro.runtimeConfig.flow ? context.nitro.runtimeConfig.flow : {},
|
|
26
|
+
content: {
|
|
27
|
+
apiBase: options.apiBase,
|
|
28
|
+
dir: contentDir
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { extname, relative, resolve } from "node:path";
|
|
3
|
+
import { defineEventHandler, getQuery } from "nitro/h3";
|
|
4
|
+
import { useRuntimeConfig } from "nitro/runtime-config";
|
|
5
|
+
function collectFiles(rootDir, currentDir = rootDir) {
|
|
6
|
+
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
7
|
+
const files = [];
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
const fullPath = resolve(currentDir, entry.name);
|
|
10
|
+
if (entry.isDirectory()) {
|
|
11
|
+
files.push(...collectFiles(rootDir, fullPath));
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (!entry.isFile()) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const extension = extname(entry.name);
|
|
18
|
+
if (![".md", ".json", ".yml", ".yaml", ".txt"].includes(extension)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
files.push(fullPath);
|
|
22
|
+
}
|
|
23
|
+
return files.sort((left, right) => left.localeCompare(right));
|
|
24
|
+
}
|
|
25
|
+
function normalizeContentPath(rootDir, filePath) {
|
|
26
|
+
const shortPath = relative(rootDir, filePath).replaceAll("\\", "/");
|
|
27
|
+
return `/${shortPath.replace(/\.(md|json|ya?ml|txt)$/i, "")}`;
|
|
28
|
+
}
|
|
29
|
+
function parseKeyValueBlock(block) {
|
|
30
|
+
return block.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).reduce((data, line) => {
|
|
31
|
+
const separatorIndex = line.indexOf(":");
|
|
32
|
+
if (separatorIndex <= 0) {
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
36
|
+
const value = line.slice(separatorIndex + 1).trim();
|
|
37
|
+
data[key] = value;
|
|
38
|
+
return data;
|
|
39
|
+
}, {});
|
|
40
|
+
}
|
|
41
|
+
function parseContentFile(rootDir, filePath) {
|
|
42
|
+
const raw = readFileSync(filePath, "utf8");
|
|
43
|
+
const extension = extname(filePath).toLowerCase();
|
|
44
|
+
const path = normalizeContentPath(rootDir, filePath);
|
|
45
|
+
if (extension === ".json") {
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
return {
|
|
48
|
+
path,
|
|
49
|
+
extension,
|
|
50
|
+
title: typeof parsed.title === "string" ? parsed.title : void 0,
|
|
51
|
+
body: JSON.stringify(parsed, null, 2),
|
|
52
|
+
data: Object.entries(parsed).reduce((result, [key, value]) => {
|
|
53
|
+
if (typeof value === "string") {
|
|
54
|
+
result[key] = value;
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}, {})
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if ((extension === ".yml" || extension === ".yaml") && raw.trim()) {
|
|
61
|
+
const data = parseKeyValueBlock(raw);
|
|
62
|
+
return {
|
|
63
|
+
path,
|
|
64
|
+
extension,
|
|
65
|
+
title: data.title,
|
|
66
|
+
body: raw,
|
|
67
|
+
data
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (raw.startsWith("---\n")) {
|
|
71
|
+
const end = raw.indexOf("\n---\n", 4);
|
|
72
|
+
if (end >= 0) {
|
|
73
|
+
const frontmatter = raw.slice(4, end);
|
|
74
|
+
const body = raw.slice(end + 5).trim();
|
|
75
|
+
const data = parseKeyValueBlock(frontmatter);
|
|
76
|
+
return {
|
|
77
|
+
path,
|
|
78
|
+
extension,
|
|
79
|
+
title: data.title,
|
|
80
|
+
body,
|
|
81
|
+
data
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
path,
|
|
87
|
+
extension,
|
|
88
|
+
body: raw,
|
|
89
|
+
data: {}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export default defineEventHandler((event) => {
|
|
93
|
+
const runtimeConfig = useRuntimeConfig();
|
|
94
|
+
const query = getQuery(event);
|
|
95
|
+
const contentDir = runtimeConfig.flow?.content?.dir;
|
|
96
|
+
if (!contentDir || !existsSync(contentDir)) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
const entries = collectFiles(contentDir).map((filePath) => parseContentFile(contentDir, filePath));
|
|
100
|
+
if (query.path) {
|
|
101
|
+
return entries.filter((entry) => entry.path === query.path || entry.path.startsWith(`${query.path}/`));
|
|
102
|
+
}
|
|
103
|
+
return entries;
|
|
104
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineEventHandler, getRequestURL, setHeader } from "nitro/h3";
|
|
2
|
+
import { useRuntimeConfig } from "nitro/runtime-config";
|
|
3
|
+
import { getUrls } from "../../server/lib/pages.ts";
|
|
4
|
+
function escapeXml(value) {
|
|
5
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
6
|
+
}
|
|
7
|
+
export default defineEventHandler(async (event) => {
|
|
8
|
+
const urls = await getUrls(true, true);
|
|
9
|
+
const runtimeConfig = useRuntimeConfig();
|
|
10
|
+
const origin = runtimeConfig.flow?.siteUrl || getRequestURL(event).origin;
|
|
11
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
12
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
13
|
+
${urls.map((entry) => typeof entry === "string" ? ` <url><loc>${escapeXml(new URL(entry, origin).toString())}</loc></url>` : ` <url><loc>${escapeXml(new URL(entry.url, origin).toString())}</loc><xhtml:link rel="alternate" hreflang="${escapeXml(entry.locale)}" href="${escapeXml(new URL(entry.url, origin).toString())}" xmlns:xhtml="http://www.w3.org/1999/xhtml" /></url>`).join("\n")}
|
|
14
|
+
</urlset>`;
|
|
15
|
+
setHeader(event, "content-type", "application/xml; charset=utf-8");
|
|
16
|
+
return xml;
|
|
17
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { defineFlowModule } from "../../src/runtime/config.ts";
|
|
3
|
+
import { resolvePackageFile, resolvePackagePath } from "../../src/public/shared.ts";
|
|
4
|
+
export default defineFlowModule({
|
|
5
|
+
meta: {
|
|
6
|
+
name: "sitemap",
|
|
7
|
+
configKey: "sitemap"
|
|
8
|
+
},
|
|
9
|
+
defaults: {
|
|
10
|
+
route: "/sitemap.xml",
|
|
11
|
+
prerender: true
|
|
12
|
+
},
|
|
13
|
+
setup(options, context) {
|
|
14
|
+
const localHandlerPath = resolve(context.projectRoot, "modules/sitemap/handler.ts");
|
|
15
|
+
const handlerPath = context.projectRoot === resolvePackagePath() ? localHandlerPath : resolvePackageFile("modules/sitemap/handler.ts", "modules/sitemap/handler.mjs", "modules/sitemap/handler.js");
|
|
16
|
+
context.nitro.handlers.push({
|
|
17
|
+
method: "get",
|
|
18
|
+
route: options.route,
|
|
19
|
+
handler: handlerPath
|
|
20
|
+
});
|
|
21
|
+
context.nitro.routeRules[options.route] = {
|
|
22
|
+
prerender: options.prerender
|
|
23
|
+
};
|
|
24
|
+
if (options.prerender) {
|
|
25
|
+
context.prerenderRoutes.add(options.route);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Flow Strapi Module
|
|
2
|
+
|
|
3
|
+
Este modulo integra Strapi en Flow con una API de uso inspirada en `@nuxtjs/strapi`.
|
|
4
|
+
|
|
5
|
+
Hoy cubre tres piezas:
|
|
6
|
+
|
|
7
|
+
- Configuracion del modulo desde `flow.config.ts`
|
|
8
|
+
- Proxy servidor para llamadas REST y GraphQL
|
|
9
|
+
- Helpers de runtime expuestos mediante el alias `#strapi`
|
|
10
|
+
|
|
11
|
+
La capa de autenticacion se implementara despues. Por ahora no incluye `login`, `logout`, `me` ni manejo de cookies/JWT en cliente.
|
|
12
|
+
|
|
13
|
+
## Activacion
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { defineFlowConfig } from './src/runtime/config.ts';
|
|
17
|
+
|
|
18
|
+
export default defineFlowConfig({
|
|
19
|
+
modules: [
|
|
20
|
+
'./modules/strapi/module.ts',
|
|
21
|
+
],
|
|
22
|
+
strapi: {
|
|
23
|
+
url: process.env.STRAPI_URL,
|
|
24
|
+
token: process.env.STRAPI_TOKEN,
|
|
25
|
+
prefix: '/api',
|
|
26
|
+
admin: '/admin',
|
|
27
|
+
version: 'v5',
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Opciones
|
|
33
|
+
|
|
34
|
+
Opciones disponibles en `strapi`:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
{
|
|
38
|
+
apiBase: '/api/_strapi',
|
|
39
|
+
url: process.env.STRAPI_URL || 'http://localhost:1337',
|
|
40
|
+
prefix: '/api',
|
|
41
|
+
admin: '/admin',
|
|
42
|
+
version: 'v5',
|
|
43
|
+
token: process.env.STRAPI_TOKEN || ''
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Descripcion de cada opcion:
|
|
48
|
+
|
|
49
|
+
- `apiBase`: ruta interna en Flow usada por el proxy cuando el codigo corre en cliente.
|
|
50
|
+
- `url`: URL base de la instancia de Strapi.
|
|
51
|
+
- `prefix`: prefijo REST de Strapi, normalmente `/api`.
|
|
52
|
+
- `admin`: prefijo del panel admin de Strapi.
|
|
53
|
+
- `version`: version objetivo de Strapi. Actualmente se expone para consumo de runtime y documentacion; la API helper actual esta enfocada en REST moderno tipo v4/v5.
|
|
54
|
+
- `token`: token Bearer usado del lado servidor para reenviar peticiones autenticadas al backend de Strapi.
|
|
55
|
+
|
|
56
|
+
## Como funciona
|
|
57
|
+
|
|
58
|
+
Cuando el modulo se carga:
|
|
59
|
+
|
|
60
|
+
- registra un handler Nitro en `${apiBase}/**`
|
|
61
|
+
- publica configuracion segura para cliente
|
|
62
|
+
- conserva el `token` solo en runtime del servidor
|
|
63
|
+
- expone el alias `#strapi`
|
|
64
|
+
|
|
65
|
+
En cliente, las llamadas van contra el proxy de Flow, por defecto `/api/_strapi`.
|
|
66
|
+
|
|
67
|
+
En servidor, las llamadas pueden resolverse directamente contra `STRAPI_URL`, adjuntando el `Bearer token` si existe.
|
|
68
|
+
|
|
69
|
+
## API de runtime
|
|
70
|
+
|
|
71
|
+
Importa desde `#strapi`:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import {
|
|
75
|
+
useStrapi,
|
|
76
|
+
useStrapiClient,
|
|
77
|
+
useStrapiGraphQL,
|
|
78
|
+
useStrapiVersion,
|
|
79
|
+
useStrapiUrl,
|
|
80
|
+
useStrapiAdminUrl,
|
|
81
|
+
useStrapiMedia,
|
|
82
|
+
} from '#strapi';
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `useStrapi()`
|
|
86
|
+
|
|
87
|
+
Devuelve helpers REST de alto nivel:
|
|
88
|
+
|
|
89
|
+
- `find(contentType, params?, options?)`
|
|
90
|
+
- `findOne(contentType, documentId, params?, options?)`
|
|
91
|
+
- `create(contentType, data, params?, options?)`
|
|
92
|
+
- `update(contentType, documentId, data?, params?, options?)`
|
|
93
|
+
- `delete(contentType, documentId?, params?, options?)`
|
|
94
|
+
|
|
95
|
+
Ejemplo:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { useStrapi } from '#strapi';
|
|
99
|
+
|
|
100
|
+
const { find, findOne, create, update, delete: remove } = useStrapi();
|
|
101
|
+
|
|
102
|
+
const articles = await find('articles', {
|
|
103
|
+
populate: '*',
|
|
104
|
+
sort: ['publishedAt:desc'],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const article = await findOne('articles', 'my-document-id', {
|
|
108
|
+
populate: '*',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await create('articles', {
|
|
112
|
+
title: 'Nuevo articulo',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await update('articles', 'my-document-id', {
|
|
116
|
+
title: 'Titulo actualizado',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await remove('articles', 'my-document-id');
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `useStrapiClient()`
|
|
123
|
+
|
|
124
|
+
Es un wrapper de `fetch` para endpoints no cubiertos por los helpers REST.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { useStrapiClient } from '#strapi';
|
|
128
|
+
|
|
129
|
+
const client = useStrapiClient();
|
|
130
|
+
|
|
131
|
+
const response = await client('/articles', {
|
|
132
|
+
params: {
|
|
133
|
+
populate: '*',
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `useStrapiGraphQL()`
|
|
139
|
+
|
|
140
|
+
Envuelve llamadas a GraphQL usando `POST` sobre `/graphql`.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { useStrapiGraphQL } from '#strapi';
|
|
144
|
+
|
|
145
|
+
const graphql = useStrapiGraphQL();
|
|
146
|
+
|
|
147
|
+
const data = await graphql(`
|
|
148
|
+
query Article($id: ID!) {
|
|
149
|
+
article(documentId: $id) {
|
|
150
|
+
documentId
|
|
151
|
+
title
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
`, {
|
|
155
|
+
id: 'my-document-id',
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `useStrapiUrl()`
|
|
160
|
+
|
|
161
|
+
Devuelve la URL REST de Strapi con `prefix` aplicado.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { useStrapiUrl } from '#strapi';
|
|
165
|
+
|
|
166
|
+
const articlesUrl = useStrapiUrl('/articles');
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `useStrapiAdminUrl()`
|
|
170
|
+
|
|
171
|
+
Devuelve la URL del admin de Strapi usando `admin`.
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { useStrapiAdminUrl } from '#strapi';
|
|
175
|
+
|
|
176
|
+
const adminUrl = useStrapiAdminUrl();
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### `useStrapiVersion()`
|
|
180
|
+
|
|
181
|
+
Expone la version configurada:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { useStrapiVersion } from '#strapi';
|
|
185
|
+
|
|
186
|
+
const version = useStrapiVersion();
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `useStrapiMedia()`
|
|
190
|
+
|
|
191
|
+
Convierte rutas relativas de medios en URLs absolutas cuando `url` esta configurado.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { useStrapiMedia } from '#strapi';
|
|
195
|
+
|
|
196
|
+
const imageUrl = useStrapiMedia('/uploads/hero.jpg');
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Parametros de consulta
|
|
200
|
+
|
|
201
|
+
Los helpers aceptan un objeto `params` y lo serializan a query string soportando objetos y arrays anidados.
|
|
202
|
+
|
|
203
|
+
Ejemplo:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const { find } = useStrapi();
|
|
207
|
+
|
|
208
|
+
await find('articles', {
|
|
209
|
+
populate: {
|
|
210
|
+
cover: true,
|
|
211
|
+
author: {
|
|
212
|
+
fields: ['name', 'slug'],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
filters: {
|
|
216
|
+
slug: {
|
|
217
|
+
$eq: 'hello-world',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Consideraciones
|
|
224
|
+
|
|
225
|
+
- El modulo esta pensado para trabajar bien en SSR y en cliente.
|
|
226
|
+
- En cliente no se expone el token privado; las llamadas pasan por el proxy.
|
|
227
|
+
- En servidor, si `STRAPI_URL` existe, se llama directo a Strapi.
|
|
228
|
+
- `version` hoy es informativa; la capa helper actual no separa clientes distintos por v3, v4 y v5.
|
|
229
|
+
- La autenticacion estilo Nuxt Strapi todavia no esta implementada.
|
|
230
|
+
|
|
231
|
+
## Archivos principales
|
|
232
|
+
|
|
233
|
+
- `modules/strapi/module.ts`: registro del modulo y configuracion
|
|
234
|
+
- `modules/strapi/proxy.ts`: proxy Nitro hacia Strapi
|
|
235
|
+
- `modules/strapi/runtime/client.ts`: helpers consumidos desde `#strapi`
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type StrapiVersion = 'v5' | 'v4' | 'v3';
|
|
2
|
+
export interface StrapiModuleOptions {
|
|
3
|
+
apiBase: string;
|
|
4
|
+
url: string;
|
|
5
|
+
prefix: string;
|
|
6
|
+
admin: string;
|
|
7
|
+
version: StrapiVersion;
|
|
8
|
+
token?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const _default: import("../../src/runtime/config.ts").FlowModuleDefinition<StrapiModuleOptions>;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { defineFlowModule } from "../../src/runtime/config.ts";
|
|
3
|
+
import { resolvePackageFile, resolvePackagePath } from "../../src/public/shared.ts";
|
|
4
|
+
function createStrapiConfigPlugin(config) {
|
|
5
|
+
const virtualId = "virtual:flow/strapi-config";
|
|
6
|
+
const resolvedVirtualId = `\0${virtualId}`;
|
|
7
|
+
return {
|
|
8
|
+
name: "flow:strapi-config",
|
|
9
|
+
enforce: "pre",
|
|
10
|
+
resolveId(id) {
|
|
11
|
+
if (id === virtualId) {
|
|
12
|
+
return resolvedVirtualId;
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
load(id) {
|
|
16
|
+
if (id !== resolvedVirtualId) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return `export default ${JSON.stringify(config)};
|
|
20
|
+
`;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export default defineFlowModule({
|
|
25
|
+
meta: {
|
|
26
|
+
name: "strapi",
|
|
27
|
+
configKey: "strapi"
|
|
28
|
+
},
|
|
29
|
+
defaults: {
|
|
30
|
+
apiBase: "/api/_strapi",
|
|
31
|
+
url: process.env.STRAPI_URL || "http://localhost:1337",
|
|
32
|
+
prefix: "/api",
|
|
33
|
+
admin: "/admin",
|
|
34
|
+
version: "v5",
|
|
35
|
+
token: process.env.STRAPI_TOKEN || ""
|
|
36
|
+
},
|
|
37
|
+
setup(options, context) {
|
|
38
|
+
const publicConfig = {
|
|
39
|
+
apiBase: options.apiBase,
|
|
40
|
+
url: options.url,
|
|
41
|
+
prefix: options.prefix,
|
|
42
|
+
admin: options.admin,
|
|
43
|
+
version: options.version
|
|
44
|
+
};
|
|
45
|
+
context.nitro.handlers.push({
|
|
46
|
+
route: `${options.apiBase}/**`,
|
|
47
|
+
handler: context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/strapi/proxy.ts") : resolvePackageFile("modules/strapi/proxy.ts", "modules/strapi/proxy.mjs", "modules/strapi/proxy.js")
|
|
48
|
+
});
|
|
49
|
+
context.nitro.routeRules[`${options.apiBase}/**`] = {
|
|
50
|
+
cors: true
|
|
51
|
+
};
|
|
52
|
+
context.nitro.runtimeConfig.flow = {
|
|
53
|
+
...typeof context.nitro.runtimeConfig.flow === "object" && context.nitro.runtimeConfig.flow ? context.nitro.runtimeConfig.flow : {},
|
|
54
|
+
strapi: {
|
|
55
|
+
...publicConfig,
|
|
56
|
+
token: options.token || ""
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
context.nitro.runtimeConfig.public = {
|
|
60
|
+
...typeof context.nitro.runtimeConfig.public === "object" && context.nitro.runtimeConfig.public ? context.nitro.runtimeConfig.public : {},
|
|
61
|
+
flow: {
|
|
62
|
+
...typeof context.nitro.runtimeConfig.public?.flow === "object" && context.nitro.runtimeConfig.public.flow ? context.nitro.runtimeConfig.public.flow : {},
|
|
63
|
+
strapi: publicConfig
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
context.vite.resolve.alias["#strapi"] = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/strapi/runtime/client.ts") : resolvePackageFile("modules/strapi/runtime/client.ts", "modules/strapi/runtime/client.mjs", "modules/strapi/runtime/client.js");
|
|
67
|
+
context.vite.plugins.push(createStrapiConfigPlugin(publicConfig));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { appendHeader, createError, defineEventHandler, getHeaders, getMethod, getQuery, getRequestURL, getRouterParam, readRawBody, setResponseStatus } from "nitro/h3";
|
|
2
|
+
import { useRuntimeConfig } from "nitro/runtime-config";
|
|
3
|
+
function withLeadingSlash(value) {
|
|
4
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
5
|
+
}
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
const runtimeConfig = useRuntimeConfig();
|
|
8
|
+
const strapi = runtimeConfig.flow?.strapi;
|
|
9
|
+
if (!strapi?.url) {
|
|
10
|
+
throw createError({
|
|
11
|
+
statusCode: 500,
|
|
12
|
+
statusMessage: "Missing Strapi URL in flow.strapi.url"
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const slug = getRouterParam(event, "_") || "";
|
|
16
|
+
const prefix = withLeadingSlash(strapi.prefix || "/api");
|
|
17
|
+
const target = new URL(`${prefix}/${slug}`.replace(/\/+/g, "/"), strapi.url);
|
|
18
|
+
const incomingUrl = getRequestURL(event);
|
|
19
|
+
for (const [key, value] of Object.entries(getQuery(event))) {
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
for (const item of value) {
|
|
22
|
+
target.searchParams.append(key, item);
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (value != null) {
|
|
27
|
+
target.searchParams.set(key, String(value));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const headers = new Headers();
|
|
31
|
+
for (const [key, value] of Object.entries(getHeaders(event))) {
|
|
32
|
+
if (value && key.toLowerCase() !== "host") {
|
|
33
|
+
headers.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (strapi.token) {
|
|
37
|
+
headers.set("authorization", `Bearer ${strapi.token}`);
|
|
38
|
+
}
|
|
39
|
+
const method = getMethod(event);
|
|
40
|
+
const body = method === "GET" || method === "HEAD" ? void 0 : await readRawBody(event, false);
|
|
41
|
+
const response = await fetch(target, {
|
|
42
|
+
method,
|
|
43
|
+
headers,
|
|
44
|
+
body
|
|
45
|
+
});
|
|
46
|
+
setResponseStatus(event, response.status, response.statusText);
|
|
47
|
+
response.headers.forEach((value, key) => {
|
|
48
|
+
appendHeader(event, key, value);
|
|
49
|
+
});
|
|
50
|
+
if (incomingUrl.pathname.endsWith(".json") || response.headers.get("content-type")?.includes("application/json")) {
|
|
51
|
+
return await response.json();
|
|
52
|
+
}
|
|
53
|
+
return await response.text();
|
|
54
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface StrapiFetchOptions extends RequestInit {
|
|
2
|
+
params?: Record<string, unknown>;
|
|
3
|
+
}
|
|
4
|
+
export interface StrapiPublicConfig {
|
|
5
|
+
apiBase: string;
|
|
6
|
+
url: string;
|
|
7
|
+
prefix: string;
|
|
8
|
+
admin: string;
|
|
9
|
+
version: 'v5' | 'v4' | 'v3';
|
|
10
|
+
}
|
|
11
|
+
export declare const strapiConfig: StrapiPublicConfig;
|
|
12
|
+
export declare function useStrapiVersion(): "v5" | "v4" | "v3";
|
|
13
|
+
export declare function useStrapiUrl(path?: string): string;
|
|
14
|
+
export declare function useStrapiAdminUrl(path?: string): string;
|
|
15
|
+
export declare function useStrapiMedia(url?: string | null): string | null | undefined;
|
|
16
|
+
export declare function useStrapiClient(): <T>(url: string, options?: StrapiFetchOptions) => Promise<T>;
|
|
17
|
+
export declare function useStrapi<T = unknown>(): {
|
|
18
|
+
find<TResult = T>(contentType: string, params?: Record<string, unknown>, options?: StrapiFetchOptions): Promise<TResult>;
|
|
19
|
+
findOne<TResult = T>(contentType: string, documentId: string, params?: Record<string, unknown>, options?: StrapiFetchOptions): Promise<TResult>;
|
|
20
|
+
create<TResult = T>(contentType: string, data: Partial<TResult>, params?: Record<string, unknown>, options?: StrapiFetchOptions): Promise<TResult>;
|
|
21
|
+
update<TResult = T>(contentType: string, documentId: string, data?: Partial<TResult>, params?: Record<string, unknown>, options?: StrapiFetchOptions): Promise<TResult>;
|
|
22
|
+
delete(contentType: string, documentId?: string, params?: Record<string, unknown>, options?: StrapiFetchOptions): Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
export declare function useStrapiGraphQL(): <T>(query: string | {
|
|
25
|
+
loc?: {
|
|
26
|
+
source?: {
|
|
27
|
+
body?: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
}, variables?: Record<string, unknown>, options?: StrapiFetchOptions) => Promise<T>;
|