@monkeyplus/flow 6.0.13 → 6.0.15

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 (56) hide show
  1. package/modules/content/module.mjs +12 -0
  2. package/modules/content/query.d.ts +1 -1
  3. package/modules/content/query.mjs +157 -57
  4. package/modules/netlify-cms/handler.d.ts +2 -0
  5. package/modules/netlify-cms/handler.mjs +5 -0
  6. package/modules/netlify-cms/module.d.ts +6 -0
  7. package/modules/netlify-cms/module.mjs +66 -0
  8. package/modules/netlify-cms/resources/admin.html +16 -0
  9. package/modules/netlify-cms/resources/adminV2.html +15 -0
  10. package/modules/netlify-cms/runtime/cms.d.ts +3 -0
  11. package/modules/netlify-cms/runtime/cms.mjs +3 -0
  12. package/modules/netlify-cms/server/api/admin.d.ts +2 -0
  13. package/modules/netlify-cms/server/api/admin.mjs +20 -0
  14. package/modules/netlify-cms/server/api/config.d.ts +2 -0
  15. package/modules/netlify-cms/server/api/config.mjs +72 -0
  16. package/modules/netlify-cms/server/api/local-fs.d.ts +2 -0
  17. package/modules/netlify-cms/server/api/local-fs.mjs +189 -0
  18. package/modules/netlify-cms/server/api/meta.d.ts +2 -0
  19. package/modules/netlify-cms/server/api/meta.mjs +8 -0
  20. package/modules/netlify-cms/server/lib/cms/handler.d.ts +2 -0
  21. package/modules/netlify-cms/server/lib/cms/handler.mjs +37 -0
  22. package/modules/netlify-cms/server/lib/cms/handlerV1.d.ts +2 -0
  23. package/modules/netlify-cms/server/lib/cms/handlerV1.mjs +40 -0
  24. package/modules/netlify-cms/server/lib/cms/helpers.d.ts +14 -0
  25. package/modules/netlify-cms/server/lib/cms/helpers.mjs +76 -0
  26. package/modules/netlify-cms/server/lib/cms/widgets.d.ts +113 -0
  27. package/modules/netlify-cms/server/lib/cms/widgets.mjs +168 -0
  28. package/modules/netlify-cms/server/lib/composables.d.ts +28 -0
  29. package/modules/netlify-cms/server/lib/composables.mjs +14 -0
  30. package/modules/netlify-cms/server/lib/entries.d.ts +25 -0
  31. package/modules/netlify-cms/server/lib/entries.mjs +39 -0
  32. package/modules/netlify-cms/server/lib/fs.d.ts +5 -0
  33. package/modules/netlify-cms/server/lib/fs.mjs +43 -0
  34. package/modules/netlify-cms/server/lib/types/collections.d.ts +16 -0
  35. package/modules/netlify-cms/server/lib/types/collections.mjs +0 -0
  36. package/modules/netlify-cms/server/lib/types/helpers.d.ts +23 -0
  37. package/modules/netlify-cms/server/lib/types/helpers.mjs +0 -0
  38. package/modules/netlify-cms/server/lib/types/index.d.ts +23 -0
  39. package/modules/netlify-cms/server/lib/types/index.mjs +11 -0
  40. package/modules/netlify-cms/server/lib/types/widgets.d.ts +39 -0
  41. package/modules/netlify-cms/server/lib/types/widgets.mjs +0 -0
  42. package/package.json +1 -1
  43. package/server/lib/handler.mjs +2 -2
  44. package/server/renderer.mjs +4 -0
  45. package/src/main.d.ts +1 -1
  46. package/src/main.mjs +2 -1
  47. package/src/public/nitro.mjs +2 -0
  48. package/src/public/query-content.d.ts +8 -0
  49. package/src/public/query-content.mjs +103 -37
  50. package/src/public/vite.mjs +19 -6
  51. package/src/runtime/config.d.ts +7 -0
  52. package/src/runtime/modules.mjs +2 -1
  53. package/src/runtime/nitro-plugin.d.ts +1 -0
  54. package/src/runtime/nitro-plugin.mjs +5 -0
  55. package/src/runtime/virtual-pages.d.ts +1 -0
  56. package/src/runtime/virtual-pages.mjs +18 -0
@@ -0,0 +1,14 @@
1
+ import { join } from "node:path";
2
+ export function defineCmsCollection(options) {
3
+ return (ctx) => {
4
+ const setupFn = options.setup(ctx);
5
+ const collectionFn = setupFn(join(options.group, options.dir));
6
+ return (baseDir, ctx2) => {
7
+ const collection = collectionFn(baseDir, ctx2);
8
+ return {
9
+ ...collection,
10
+ group: options.group
11
+ };
12
+ };
13
+ };
14
+ }
@@ -0,0 +1,25 @@
1
+ export declare function entriesFromFiles(repoPath: string, files: {
2
+ path: string;
3
+ label?: string;
4
+ }[]): Promise<({
5
+ data: string;
6
+ file: {
7
+ path: string;
8
+ label: string | undefined;
9
+ id: any;
10
+ };
11
+ } | {
12
+ data: null;
13
+ file: {
14
+ path: string;
15
+ label: string | undefined;
16
+ id: null;
17
+ };
18
+ })[]>;
19
+ export declare function readMediaFile(repoPath: string, file: string): Promise<{
20
+ id: any;
21
+ content: string;
22
+ encoding: string;
23
+ path: string;
24
+ name: any;
25
+ }>;
@@ -0,0 +1,39 @@
1
+ import crypto from "node:crypto";
2
+ import { promises as fs } from "node:fs";
3
+ import path from "node:path";
4
+ function sha256(buffer) {
5
+ return crypto.createHash("sha256").update(buffer).digest("hex");
6
+ }
7
+ function normalizePath(path2) {
8
+ return path2.replace(/\\/g, "/");
9
+ }
10
+ export async function entriesFromFiles(repoPath, files) {
11
+ return Promise.all(
12
+ files.map(async (file) => {
13
+ try {
14
+ const content = await fs.readFile(path.join(repoPath, file.path));
15
+ return {
16
+ data: content.toString(),
17
+ file: { path: normalizePath(file.path), label: file.label, id: sha256(content) }
18
+ };
19
+ } catch (e) {
20
+ return {
21
+ data: null,
22
+ file: { path: normalizePath(file.path), label: file.label, id: null }
23
+ };
24
+ }
25
+ })
26
+ );
27
+ }
28
+ export async function readMediaFile(repoPath, file) {
29
+ const encoding = "base64";
30
+ const buffer = await fs.readFile(path.join(repoPath, file));
31
+ const id = sha256(buffer);
32
+ return {
33
+ id,
34
+ content: buffer.toString(encoding),
35
+ encoding,
36
+ path: normalizePath(file),
37
+ name: path.basename(file)
38
+ };
39
+ }
@@ -0,0 +1,5 @@
1
+ export declare function listRepoFiles(repoPath: string, folder: string, extension: string, depth: number): Promise<string[]>;
2
+ export declare function writeFile(filePath: string, content: Buffer | string): Promise<void>;
3
+ export declare function deleteFile(repoPath: string, filePath: string): Promise<void>;
4
+ export declare function move(from: string, to: string): Promise<void>;
5
+ export declare function getUpdateDate(repoPath: string, filePath: string): Promise<Date>;
@@ -0,0 +1,43 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ async function listFiles(dir, extension, depth) {
4
+ if (depth <= 0)
5
+ return [];
6
+ try {
7
+ const dirents = await fs.readdir(dir, { withFileTypes: true });
8
+ const files = await Promise.all(
9
+ dirents.map((dirent) => {
10
+ const res = path.join(dir, dirent.name);
11
+ return dirent.isDirectory() ? listFiles(res, extension, depth - 1) : [res].filter((f) => f.endsWith(extension));
12
+ })
13
+ );
14
+ return [].concat(...files);
15
+ } catch (e) {
16
+ return [];
17
+ }
18
+ }
19
+ export async function listRepoFiles(repoPath, folder, extension, depth) {
20
+ const files = await listFiles(path.join(repoPath, folder), extension, depth);
21
+ return files.map((f) => f.substr(repoPath.length + 1));
22
+ }
23
+ export async function writeFile(filePath, content) {
24
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
25
+ await fs.writeFile(filePath, content);
26
+ }
27
+ export async function deleteFile(repoPath, filePath) {
28
+ await fs.unlink(path.join(repoPath, filePath)).catch(() => void 0);
29
+ }
30
+ async function moveFile(from, to) {
31
+ await fs.mkdir(path.dirname(to), { recursive: true });
32
+ await fs.rename(from, to);
33
+ }
34
+ export async function move(from, to) {
35
+ await moveFile(from, to);
36
+ const sourceDir = path.dirname(from);
37
+ const destDir = path.dirname(to);
38
+ const allFiles = await listFiles(sourceDir, "", 100);
39
+ await Promise.all(allFiles.map((file) => moveFile(file, file.replace(sourceDir, destDir))));
40
+ }
41
+ export async function getUpdateDate(repoPath, filePath) {
42
+ return fs.stat(path.join(repoPath, filePath)).then((stat) => stat.mtime).catch(() => /* @__PURE__ */ new Date());
43
+ }
@@ -0,0 +1,16 @@
1
+ import type { CmsCollection, CmsCollectionFile } from 'netlify-cms-core';
2
+ import type { Cms } from './index';
3
+ export declare namespace Collections {
4
+ type _Base = Omit<CmsCollection, 'files' | 'folder' | 'fields'>;
5
+ type Folder = _Base & {
6
+ folder: string;
7
+ fields: Cms.Fields[];
8
+ };
9
+ type Files = _Base & {
10
+ files: File[];
11
+ };
12
+ type File = Omit<CmsCollectionFile, 'fields'> & {
13
+ fields: Cms.Fields[];
14
+ htmlId?: string;
15
+ };
16
+ }
@@ -0,0 +1,23 @@
1
+ import type { Collections } from './collections';
2
+ import type { Cms } from './index';
3
+ export type DefineMethod<R> = (parentName: string, ctx: ContextWidget) => R;
4
+ export type DefineMethodExtend<R> = (id: string) => (parentName: string, ctx: ContextWidget) => R;
5
+ export interface ContextWidget {
6
+ rootDir: string;
7
+ }
8
+ export type DefineFile = Record<string, DefineMethodExtend<Collections.File>>;
9
+ export interface CollectionFileId extends Omit<Collections._Base, 'name'> {
10
+ files: DefineFile;
11
+ }
12
+ export interface CollectionFolderId extends Omit<Collections.Folder, 'name' | 'folder' | 'fields'> {
13
+ fields: Model.Fields;
14
+ }
15
+ export declare namespace Model {
16
+ type Fields = Record<string, (parentName: string, ctx: ContextWidget) => Cms.Fields>;
17
+ }
18
+ export declare namespace Helpers {
19
+ interface CollectionFiles {
20
+ (base: CollectionFileId): (baseDir: string) => DefineMethod<Collections.Files>;
21
+ }
22
+ }
23
+ export type DefineCms = (options: Omit<Cms.Config, 'collections'>, collections: DefineMethod<Collections.Files | Collections.Folder>[]) => (dir: string, ctx: ContextWidget) => Cms.Config;
@@ -0,0 +1,23 @@
1
+ import type { Collections } from './collections';
2
+ import type { DefineMethod } from './helpers';
3
+ import type { Widgets } from './widgets';
4
+ export { Collections } from './collections';
5
+ export { ContextWidget, DefineCms } from './helpers';
6
+ export { CollectionFileId, CollectionFolderId, DefineMethod, DefineMethodExtend, Helpers, Model, } from './helpers';
7
+ export { Widgets } from './widgets';
8
+ export declare namespace Cms {
9
+ type Config = Record<string, any> & {
10
+ collections: (Collections.Folder | Collections.Files)[];
11
+ };
12
+ type Fields = Widgets.Boolean | Widgets.Markdown | Widgets.Code | Widgets.Color | Widgets.DateTime | Widgets.Hidden | Widgets.Map | Widgets.Number | Widgets.Select | Widgets.Text | Widgets.String | Widgets.Relation | Widgets.File | Widgets.Image | Widgets.Object | Widgets.List | Widgets.Date | Widgets.Meta;
13
+ }
14
+ export interface FlowNetlifyCms {
15
+ config?: Omit<Partial<Cms.Config>, 'collections'>;
16
+ functions?: {
17
+ dir: string;
18
+ proxy?: Record<string, string>;
19
+ };
20
+ omitRoutes?: boolean;
21
+ omitProxy?: boolean;
22
+ collections?: DefineMethod<Collections.Files | Collections.Folder>[];
23
+ }
@@ -0,0 +1,11 @@
1
+ export { Collections } from "./collections.mjs";
2
+ export { ContextWidget, DefineCms } from "./helpers.mjs";
3
+ export {
4
+ CollectionFileId,
5
+ CollectionFolderId,
6
+ DefineMethod,
7
+ DefineMethodExtend,
8
+ Helpers,
9
+ Model
10
+ } from "./helpers.mjs";
11
+ export { Widgets } from "./widgets.mjs";
@@ -0,0 +1,39 @@
1
+ import type { CmsFieldBase, CmsFieldBoolean, CmsFieldCode, CmsFieldColor, CmsFieldDateTime, CmsFieldFileOrImage, CmsFieldHidden, CmsFieldList, CmsFieldMap, CmsFieldMarkdown, CmsFieldMeta, CmsFieldNumber, CmsFieldObject, CmsFieldRelation, CmsFieldSelect, CmsFieldStringOrText } from 'netlify-cms-core';
2
+ import type { Cms } from './index';
3
+ interface CmsFieldExtra {
4
+ extraOptions?: Record<string, any>;
5
+ htmlId?: string;
6
+ }
7
+ export declare namespace Widgets {
8
+ type Boolean = CmsFieldExtra & CmsFieldBase & CmsFieldBoolean;
9
+ type Markdown = CmsFieldExtra & CmsFieldBase & CmsFieldMarkdown;
10
+ type Code = CmsFieldExtra & CmsFieldBase & CmsFieldCode;
11
+ type Color = CmsFieldExtra & CmsFieldBase & CmsFieldColor;
12
+ type DateTime = CmsFieldExtra & CmsFieldBase & CmsFieldDateTime;
13
+ type Hidden = CmsFieldExtra & CmsFieldBase & CmsFieldHidden;
14
+ type Map = CmsFieldExtra & CmsFieldBase & CmsFieldMap;
15
+ type Number = CmsFieldExtra & CmsFieldBase & CmsFieldNumber;
16
+ type Select = CmsFieldExtra & CmsFieldBase & CmsFieldSelect;
17
+ type Text = CmsFieldExtra & CmsFieldBase & CmsFieldStringOrText;
18
+ type String = CmsFieldExtra & CmsFieldBase & CmsFieldStringOrText;
19
+ type Relation = CmsFieldExtra & CmsFieldBase & CmsFieldRelation;
20
+ type File = CmsFieldExtra & CmsFieldBase & CmsFieldFileOrImage;
21
+ type Image = CmsFieldExtra & CmsFieldBase & CmsFieldFileOrImage;
22
+ type Meta = CmsFieldExtra & CmsFieldBase & CmsFieldMeta & {
23
+ options?: Record<string, unknown>;
24
+ };
25
+ type Object = CmsFieldExtra & CmsFieldBase & Omit<CmsFieldObject, 'fields'> & {
26
+ fields?: Cms.Fields[];
27
+ };
28
+ type List = CmsFieldExtra & CmsFieldBase & Omit<CmsFieldList, 'fields' | 'field'> & {
29
+ field?: Cms.Fields;
30
+ fields?: Cms.Fields[];
31
+ };
32
+ type Date = CmsFieldExtra & CmsFieldBase & {
33
+ widget: 'date';
34
+ format?: string;
35
+ date_format?: boolean | string;
36
+ time_format?: boolean | string;
37
+ };
38
+ }
39
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monkeyplus/flow",
3
- "version": "6.0.13",
3
+ "version": "6.0.15",
4
4
  "description": "@monkeyplus/flow package-first runtime with Vite, Nitro, Vue and a workspace playground.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -1,7 +1,7 @@
1
- import clientAssets from "../../src/main.mjs?assets=client";
2
1
  import bundleAssets from "virtual:flow/client-page-assets";
3
- import { renderDocument } from "./render.mjs";
2
+ import clientAssets from "../../src/main.mjs?assets=client";
4
3
  import { resolvePage, resolvePageByName } from "./pages.mjs";
4
+ import { renderDocument } from "./render.mjs";
5
5
  function mergeClientAssets(baseAssets, pageAssets) {
6
6
  const css = [...baseAssets.css || [], ...pageAssets?.css || []];
7
7
  const js = [...baseAssets.js || [], ...pageAssets?.js || []];
@@ -36,6 +36,10 @@ async function resolvePrerenderFetchHandler() {
36
36
  return await prerenderFetchHandlerPromise;
37
37
  }
38
38
  export default async function renderPage(input) {
39
+ const nitroApp = globalThis.__nitro__;
40
+ if (nitroApp && nitroApp.localFetch) {
41
+ globalThis.$fetch = nitroApp.localFetch;
42
+ }
39
43
  const viteEnvs = globalThis.__nitro_vite_envs__;
40
44
  const request = resolveRequest(input);
41
45
  const viteEnv = viteEnvs?.ssr;
package/src/main.d.ts CHANGED
@@ -1 +1 @@
1
- export {};
1
+ import 'virtual:flow/server-styles';
package/src/main.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import { hydrateIslands } from "./runtime/islands.mjs";
2
1
  import bundles from "virtual:flow/client-pages";
2
+ import { hydrateIslands } from "./runtime/islands.mjs";
3
+ import "virtual:flow/server-styles";
3
4
  function readBoot() {
4
5
  const element = document.getElementById("flow-boot");
5
6
  if (!element?.textContent) {
@@ -64,10 +64,12 @@ export function createFlowNitroConfig(options = {}) {
64
64
  plugins: flowModules.nitro.plugins,
65
65
  noExternals: [...configuredNoExternals, flowPackagePattern],
66
66
  handlers: flowModules.nitro.handlers,
67
+ storage: flowModules.nitro.storage,
67
68
  routeRules: {
68
69
  "/api/**": { cors: true },
69
70
  ...flowModules.nitro.routeRules
70
71
  },
72
+ imports: flowModules.nitro.imports,
71
73
  runtimeConfig: {
72
74
  app: {
73
75
  name: "Flow Next",
@@ -1,7 +1,15 @@
1
1
  import type { ContentEntry, ContentTreeNode } from '../../modules/content/query.ts';
2
2
  export interface QueryContentBuilder {
3
+ where: (field: string, operator: string, value?: unknown) => QueryContentBuilder;
4
+ order: (field: string, direction: 'ASC' | 'DESC' | 'asc' | 'desc') => QueryContentBuilder;
5
+ skip: (amount: number) => QueryContentBuilder;
6
+ limit: (amount: number) => QueryContentBuilder;
7
+ select: (...fields: string[]) => QueryContentBuilder;
3
8
  find: () => Promise<ContentEntry[]>;
4
9
  findOne: () => Promise<ContentEntry | null>;
10
+ all: () => Promise<ContentEntry[]>;
11
+ first: () => Promise<ContentEntry | null>;
12
+ surround: (targetPath: string) => Promise<[ContentEntry | null, ContentEntry | null]>;
5
13
  tree: () => Promise<ContentTreeNode[]>;
6
14
  }
7
15
  export declare function queryContent(path?: string): QueryContentBuilder;
@@ -27,6 +27,7 @@ function withQuery(url, params) {
27
27
  if (!params || Object.keys(params).length === 0) {
28
28
  return url;
29
29
  }
30
+ const isAbsolute = url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//");
30
31
  const origin = typeof window !== "undefined" ? window.location.origin : "http://localhost:3000";
31
32
  const target = new URL(url, origin);
32
33
  Object.entries(params).forEach(([key, value]) => {
@@ -35,7 +36,7 @@ function withQuery(url, params) {
35
36
  }
36
37
  target.searchParams.set(key, String(value));
37
38
  });
38
- return target.toString();
39
+ return isAbsolute ? target.toString() : target.pathname + target.search;
39
40
  }
40
41
  async function getRuntimeConfig() {
41
42
  if (typeof window !== "undefined") {
@@ -65,44 +66,46 @@ async function resolveContentApiBase() {
65
66
  const runtimeConfig = await getRuntimeConfig();
66
67
  return runtimeConfig?.flow?.content?.apiBase || runtimeConfig?.public?.flow?.content?.apiBase || defaultContentConfig.apiBase;
67
68
  }
68
- async function resolveAbsoluteContentApiBase() {
69
- const runtimeConfig = await getRuntimeConfig();
69
+ async function fetchContent(route, queryParams) {
70
70
  const apiBase = await resolveContentApiBase();
71
- if (typeof window !== "undefined") {
72
- return apiBase;
71
+ const query = { ...queryParams };
72
+ if (query.path === "/") {
73
+ delete query.path;
73
74
  }
74
- if (runtimeConfig?.flow?.siteUrl) {
75
- return joinUrl(runtimeConfig.flow.siteUrl, apiBase);
75
+ for (const key of Object.keys(query)) {
76
+ if (query[key] === void 0 || query[key] === null) {
77
+ delete query[key];
78
+ }
76
79
  }
77
- return joinUrl("http://localhost:3000", apiBase);
78
- }
79
- async function fetchContent(route, path) {
80
- const apiBase = await resolveContentApiBase();
81
- const query = path && path !== "/" ? { path } : void 0;
82
80
  const localFetch = getGlobalFetch();
83
- const nitroFetch = await getNitroFetch();
84
- if (typeof window === "undefined" && localFetch) {
85
- console.debug(`[Flow Content] Fetching content from ${joinUrl(apiBase, route)} using global fetch`);
86
- return await localFetch(joinUrl(apiBase, route), {
87
- headers: {
88
- accept: "application/json"
89
- },
90
- query
91
- });
92
- }
93
- if (typeof window === "undefined" && nitroFetch) {
94
- console.debug(`[Flow Content] Fetching content from ${joinUrl(apiBase, route)} using Nitro fetch`);
95
- return await nitroFetch(joinUrl(apiBase, route), {
96
- headers: {
97
- accept: "application/json"
98
- },
99
- query
100
- });
81
+ const relativeTarget = withQuery(joinUrl(apiBase, route), query);
82
+ if (typeof window === "undefined") {
83
+ try {
84
+ if (import.meta.env?.SSR || process.server) {
85
+ const { findContentEntries, buildContentTree, findContentTree, readContentEntries } = await import("../../modules/content/query.js");
86
+ let entries = await readContentEntries();
87
+ const isTreeRequest = route.endsWith("/tree") || query.tree === true || query.tree === "true" || query.tree === "1";
88
+ if (isTreeRequest) {
89
+ const tree = buildContentTree(entries);
90
+ return findContentTree(tree, query.path);
91
+ }
92
+ if (query.path) {
93
+ entries = findContentEntries(entries, query.path);
94
+ }
95
+ if (query.where) {
96
+ try {
97
+ const whereConditions = JSON.parse(query.where);
98
+ } catch {
99
+ }
100
+ }
101
+ return entries;
102
+ }
103
+ } catch (e) {
104
+ }
101
105
  }
102
- const absoluteBase = await resolveAbsoluteContentApiBase();
103
- const target = withQuery(joinUrl(absoluteBase, route), query);
104
- console.debug(`[Flow Content] Fetching content from ${target}`);
105
- const response = await fetch(target, {
106
+ const fetchFn = typeof window === "undefined" ? localFetch || fetch : fetch;
107
+ const targetToFetch = typeof window === "undefined" && localFetch ? relativeTarget : relativeTarget;
108
+ const response = await fetchFn(targetToFetch, {
106
109
  headers: {
107
110
  accept: "application/json"
108
111
  }
@@ -115,16 +118,79 @@ async function fetchContent(route, path) {
115
118
  }
116
119
  export function queryContent(path = "") {
117
120
  const normalizedPath = normalizeQueryPath(path);
118
- return {
121
+ const queryObj = {
122
+ path: normalizedPath,
123
+ where: [],
124
+ order: [],
125
+ skip: 0,
126
+ limit: 0,
127
+ select: []
128
+ };
129
+ const builder = {
130
+ where(field, operator, value) {
131
+ if (value === void 0) {
132
+ value = operator;
133
+ operator = "=";
134
+ }
135
+ queryObj.where.push({ field, operator, value });
136
+ return this;
137
+ },
138
+ order(field, direction = "ASC") {
139
+ queryObj.order.push({ field, direction });
140
+ return this;
141
+ },
142
+ skip(amount) {
143
+ queryObj.skip = amount;
144
+ return this;
145
+ },
146
+ limit(amount) {
147
+ queryObj.limit = amount;
148
+ return this;
149
+ },
150
+ select(...fields) {
151
+ queryObj.select.push(...fields);
152
+ return this;
153
+ },
119
154
  async find() {
120
- return await fetchContent("query", normalizedPath);
155
+ const q = { path: queryObj.path };
156
+ if (queryObj.where.length)
157
+ q.where = JSON.stringify(queryObj.where);
158
+ if (queryObj.order.length)
159
+ q.order = JSON.stringify(queryObj.order);
160
+ if (queryObj.skip > 0)
161
+ q.skip = String(queryObj.skip);
162
+ if (queryObj.limit > 0)
163
+ q.limit = String(queryObj.limit);
164
+ if (queryObj.select.length)
165
+ q.select = queryObj.select.join(",");
166
+ return await fetchContent("query", q);
121
167
  },
122
168
  async findOne() {
123
169
  const entries = await this.find();
124
170
  return entries[0] || null;
125
171
  },
172
+ async all() {
173
+ return await this.find();
174
+ },
175
+ async first() {
176
+ this.limit(1);
177
+ return await this.findOne();
178
+ },
179
+ async surround(targetPath) {
180
+ const entries = await this.find();
181
+ const index = entries.findIndex((e) => e.path === targetPath);
182
+ if (index === -1) {
183
+ return [null, null];
184
+ }
185
+ return [
186
+ index > 0 ? entries[index - 1] : null,
187
+ index < entries.length - 1 ? entries[index + 1] : null
188
+ ];
189
+ },
126
190
  async tree() {
127
- return await fetchContent("tree", normalizedPath);
191
+ const q = { path: queryObj.path };
192
+ return await fetchContent("tree", q);
128
193
  }
129
194
  };
195
+ return builder;
130
196
  }
@@ -17,6 +17,7 @@ import {
17
17
  createVirtualLayoutContextsModule,
18
18
  createVirtualLayoutsModule,
19
19
  createVirtualPagesModule,
20
+ createVirtualServerStylesModule,
20
21
  createVirtualTemplatesModule
21
22
  } from "../runtime/virtual-pages.mjs";
22
23
  const flowRestartPatterns = [
@@ -55,6 +56,9 @@ function getVirtualModuleIdsForPath(projectPath) {
55
56
  if (projectPath === "flow.config.ts" || projectPath.startsWith("pages/")) {
56
57
  ids.add("virtual:flow/pages");
57
58
  }
59
+ if (projectPath.startsWith("views/")) {
60
+ ids.add("virtual:flow/server-styles");
61
+ }
58
62
  if (projectPath.startsWith("views/templates/")) {
59
63
  ids.add("virtual:flow/templates");
60
64
  }
@@ -141,7 +145,7 @@ function createFlowHotReload(projectRoot, extraWatchPaths = []) {
141
145
  });
142
146
  }, 0);
143
147
  }
144
- async function handleServerChange(server, filePath, event) {
148
+ async function handleServerChange(server, filePath, event, hmrModules) {
145
149
  const projectPath = toProjectPath(filePath);
146
150
  invalidateFileModules(server, filePath, event);
147
151
  invalidateVirtualModules(server, projectPath);
@@ -153,11 +157,21 @@ function createFlowHotReload(projectRoot, extraWatchPaths = []) {
153
157
  if (!shouldReload) {
154
158
  return false;
155
159
  }
156
- server.ws.send({ type: "full-reload", path: "*" });
160
+ if (event === "change" && projectPath.endsWith(".vue") && hmrModules) {
161
+ const validModules = hmrModules.filter((m) => m && m.id);
162
+ const isStyleOnly = validModules.length > 0 && validModules.every((m) => m.id.includes("type=style"));
163
+ if (isStyleOnly) {
164
+ return false;
165
+ }
166
+ }
167
+ setTimeout(() => {
168
+ server.ws.send({ type: "full-reload", path: "*" });
169
+ }, 50);
157
170
  return true;
158
171
  }
159
172
  return {
160
173
  name: "flow:hot-reload",
174
+ enforce: "post",
161
175
  configureServer(server) {
162
176
  if (extraWatchPaths.length) {
163
177
  server.watcher.add(extraWatchPaths);
@@ -170,9 +184,7 @@ function createFlowHotReload(projectRoot, extraWatchPaths = []) {
170
184
  });
171
185
  },
172
186
  async handleHotUpdate(ctx) {
173
- if (await handleServerChange(ctx.server, ctx.file, "change")) {
174
- return [];
175
- }
187
+ await handleServerChange(ctx.server, ctx.file, "change", ctx.modules);
176
188
  }
177
189
  };
178
190
  }
@@ -184,7 +196,8 @@ function createFlowVirtualServerModules(projectRoot, flowConfig) {
184
196
  ["virtual:flow/templates", () => createVirtualTemplatesModule(projectRoot)],
185
197
  ["virtual:flow/layouts", () => createVirtualLayoutsModule(projectRoot)],
186
198
  ["virtual:flow/layout-contexts", () => createVirtualLayoutContextsModule(projectRoot)],
187
- ["virtual:flow/bases", () => createVirtualBaseTemplatesModule(projectRoot)]
199
+ ["virtual:flow/bases", () => createVirtualBaseTemplatesModule(projectRoot)],
200
+ ["virtual:flow/server-styles", () => createVirtualServerStylesModule(projectRoot)]
188
201
  ]);
189
202
  return {
190
203
  name: "flow:server-virtuals",
@@ -71,6 +71,13 @@ export interface FlowModuleNitroConfig extends Record<string, unknown> {
71
71
  routeRules: Record<string, Record<string, unknown>>;
72
72
  runtimeConfig: Record<string, unknown>;
73
73
  virtual: Record<string, string | (() => string | Promise<string>)>;
74
+ imports?: {
75
+ imports: Array<{
76
+ name: string;
77
+ from: string;
78
+ as?: string;
79
+ }>;
80
+ };
74
81
  }
75
82
  export interface FlowModuleViteConfig extends Record<string, unknown> {
76
83
  plugins: unknown[];
@@ -7,7 +7,8 @@ function createNitroConfig() {
7
7
  handlers: [],
8
8
  routeRules: {},
9
9
  runtimeConfig: {},
10
- virtual: {}
10
+ virtual: {},
11
+ imports: { imports: [] }
11
12
  };
12
13
  }
13
14
  function createViteConfig() {
@@ -0,0 +1 @@
1
+ export default function (nitroApp: any): void;
@@ -0,0 +1,5 @@
1
+ export default function(nitroApp) {
2
+ if (!globalThis.$fetch && nitroApp) {
3
+ globalThis.$fetch = nitroApp.fetch;
4
+ }
5
+ }
@@ -7,3 +7,4 @@ export declare function createVirtualBaseTemplatesModule(projectRoot: string): s
7
7
  export declare function createVirtualIslandsModule(projectRoot: string): string;
8
8
  export declare function createVirtualClientPagesModule(projectRoot: string): string;
9
9
  export declare function createVirtualClientPageAssetsModule(projectRoot: string): string;
10
+ export declare function createVirtualServerStylesModule(projectRoot: string): string;
@@ -1,3 +1,4 @@
1
+ import { parse } from "vue/compiler-sfc";
1
2
  import { existsSync, readFileSync, readdirSync } from "node:fs";
2
3
  import { basename, extname, resolve } from "node:path";
3
4
  function collectFiles(rootDir, extensions, currentDir = rootDir) {
@@ -170,3 +171,20 @@ export function createVirtualClientPageAssetsModule(projectRoot) {
170
171
  ""
171
172
  ].join("\n");
172
173
  }
174
+ export function createVirtualServerStylesModule(projectRoot) {
175
+ const viewsDir = resolve(projectRoot, "views");
176
+ const files = existsSync(viewsDir) ? collectFiles(viewsDir, [".vue"]) : [];
177
+ const imports = files.flatMap((filePath) => {
178
+ const code = readFileSync(filePath, "utf8");
179
+ const { descriptor } = parse(code);
180
+ return descriptor.styles.map((style, i) => {
181
+ return `import "${toAbsoluteImport(filePath)}?vue&type=style&index=${i}&lang.${style.lang || "css"}";`;
182
+ });
183
+ });
184
+ return [
185
+ ...imports,
186
+ "",
187
+ "export default {};",
188
+ ""
189
+ ].join("\n");
190
+ }