astro 4.13.4 → 4.14.1
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/components/Code.astro +9 -0
- package/dist/@types/astro.d.ts +249 -1
- package/dist/actions/consts.d.ts +1 -1
- package/dist/actions/consts.js +1 -1
- package/dist/actions/index.js +12 -21
- package/dist/assets/endpoint/node.js +1 -1
- package/dist/assets/utils/resolveImports.d.ts +9 -0
- package/dist/assets/utils/resolveImports.js +22 -0
- package/dist/cli/add/index.d.ts +2 -2
- package/dist/cli/add/index.js +2 -2
- package/dist/cli/build/index.d.ts +2 -2
- package/dist/cli/build/index.js +5 -1
- package/dist/cli/check/index.d.ts +2 -2
- package/dist/cli/check/index.js +5 -2
- package/dist/cli/db/index.d.ts +4 -3
- package/dist/cli/db/index.js +10 -3
- package/dist/cli/dev/index.d.ts +2 -2
- package/dist/cli/dev/index.js +1 -0
- package/dist/cli/docs/index.d.ts +2 -2
- package/dist/cli/flags.d.ts +3 -1
- package/dist/cli/flags.js +2 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +26 -13
- package/dist/cli/info/index.d.ts +2 -2
- package/dist/cli/preferences/index.d.ts +2 -2
- package/dist/cli/preferences/index.js +1 -1
- package/dist/cli/preview/index.d.ts +2 -2
- package/dist/cli/sync/index.d.ts +2 -2
- package/dist/cli/sync/index.js +5 -2
- package/dist/cli/telemetry/index.d.ts +2 -2
- package/dist/content/consts.d.ts +16 -2
- package/dist/content/consts.js +32 -2
- package/dist/content/content-layer.d.ts +40 -0
- package/dist/content/content-layer.js +253 -0
- package/dist/content/data-store.d.ts +54 -0
- package/dist/content/data-store.js +72 -0
- package/dist/content/loaders/file.d.ts +7 -0
- package/dist/content/loaders/file.js +72 -0
- package/dist/content/loaders/glob.d.ts +25 -0
- package/dist/content/loaders/glob.js +218 -0
- package/dist/content/loaders/index.d.ts +3 -0
- package/dist/content/loaders/index.js +7 -0
- package/dist/content/loaders/types.d.ts +36 -0
- package/dist/content/loaders/types.js +0 -0
- package/dist/content/mutable-data-store.d.ts +77 -0
- package/dist/content/mutable-data-store.js +269 -0
- package/dist/content/runtime.d.ts +46 -8
- package/dist/content/runtime.js +225 -31
- package/dist/content/types-generator.js +123 -35
- package/dist/content/utils.d.ts +307 -2
- package/dist/content/utils.js +101 -7
- package/dist/content/vite-plugin-content-assets.js +9 -1
- package/dist/content/vite-plugin-content-virtual-mod.js +94 -2
- package/dist/core/build/index.js +14 -7
- package/dist/core/build/plugins/plugin-ssr.js +32 -4
- package/dist/core/config/config.d.ts +2 -5
- package/dist/core/config/config.js +0 -12
- package/dist/core/config/index.d.ts +1 -1
- package/dist/core/config/index.js +0 -2
- package/dist/core/config/schema.d.ts +34 -0
- package/dist/core/config/schema.js +6 -2
- package/dist/core/config/settings.js +5 -3
- package/dist/core/constants.js +1 -1
- package/dist/core/create-vite.js +1 -1
- package/dist/core/dev/container.js +2 -1
- package/dist/core/dev/dev.js +33 -3
- package/dist/core/dev/restart.js +25 -10
- package/dist/core/errors/errors-data.d.ts +21 -0
- package/dist/core/errors/errors-data.js +13 -0
- package/dist/core/index.js +1 -1
- package/dist/core/logger/vite.js +1 -1
- package/dist/core/messages.js +2 -2
- package/dist/core/preview/static-preview-server.js +1 -1
- package/dist/core/routing/manifest/create.js +1 -1
- package/dist/core/sync/constants.d.ts +1 -0
- package/dist/core/sync/constants.js +4 -0
- package/dist/core/sync/index.d.ts +12 -4
- package/dist/core/sync/index.js +54 -24
- package/dist/core/sync/write-files.d.ts +4 -0
- package/dist/core/sync/write-files.js +69 -0
- package/dist/core/util.js +1 -1
- package/dist/env/sync.js +6 -4
- package/dist/integrations/hooks.d.ts +7 -1
- package/dist/integrations/hooks.js +54 -0
- package/dist/preferences/index.d.ts +1 -1
- package/dist/preferences/index.js +2 -2
- package/dist/runtime/server/render/server-islands.js +6 -4
- package/dist/vite-plugin-astro-server/response.js +1 -1
- package/dist/vite-plugin-env/index.d.ts +3 -1
- package/dist/vite-plugin-env/index.js +11 -1
- package/dist/vite-plugin-markdown/content-entry-type.js +25 -2
- package/dist/vite-plugin-scanner/index.js +15 -5
- package/package.json +10 -5
- package/templates/content/module.mjs +6 -1
- package/templates/content/types.d.ts +18 -5
- package/types/content.d.ts +34 -1
- package/dist/core/sync/setup-env-ts.d.ts +0 -8
- package/dist/core/sync/setup-env-ts.js +0 -79
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Loader } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Loads entries from a JSON file. The file must contain an array of objects that contain unique `id` fields, or an object with string keys.
|
|
4
|
+
* @todo Add support for other file types, such as YAML, CSV etc.
|
|
5
|
+
* @param fileName The path to the JSON file to load, relative to the content directory.
|
|
6
|
+
*/
|
|
7
|
+
export declare function file(fileName: string): Loader;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { promises as fs, existsSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { posixRelative } from "../utils.js";
|
|
4
|
+
function file(fileName) {
|
|
5
|
+
if (fileName.includes("*")) {
|
|
6
|
+
throw new Error("Glob patterns are not supported in `file` loader. Use `glob` loader instead.");
|
|
7
|
+
}
|
|
8
|
+
async function syncData(filePath, { logger, parseData, store, settings }) {
|
|
9
|
+
let json;
|
|
10
|
+
try {
|
|
11
|
+
const data = await fs.readFile(filePath, "utf-8");
|
|
12
|
+
json = JSON.parse(data);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
logger.error(`Error reading data from ${fileName}`);
|
|
15
|
+
logger.debug(error.message);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(json)) {
|
|
19
|
+
if (json.length === 0) {
|
|
20
|
+
logger.warn(`No items found in ${fileName}`);
|
|
21
|
+
}
|
|
22
|
+
logger.debug(`Found ${json.length} item array in ${fileName}`);
|
|
23
|
+
store.clear();
|
|
24
|
+
for (const rawItem of json) {
|
|
25
|
+
const id = (rawItem.id ?? rawItem.slug)?.toString();
|
|
26
|
+
if (!id) {
|
|
27
|
+
logger.error(`Item in ${fileName} is missing an id or slug field.`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const data = await parseData({ id, data: rawItem, filePath });
|
|
31
|
+
store.set({
|
|
32
|
+
id,
|
|
33
|
+
data,
|
|
34
|
+
filePath: posixRelative(fileURLToPath(settings.config.root), filePath)
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
} else if (typeof json === "object") {
|
|
38
|
+
const entries = Object.entries(json);
|
|
39
|
+
logger.debug(`Found object with ${entries.length} entries in ${fileName}`);
|
|
40
|
+
store.clear();
|
|
41
|
+
for (const [id, rawItem] of entries) {
|
|
42
|
+
const data = await parseData({ id, data: rawItem, filePath });
|
|
43
|
+
store.set({ id, data });
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
logger.error(`Invalid data in ${fileName}. Must be an array or object.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
name: "file-loader",
|
|
51
|
+
load: async (options) => {
|
|
52
|
+
const { settings, logger, watcher } = options;
|
|
53
|
+
logger.debug(`Loading data from ${fileName}`);
|
|
54
|
+
const url = new URL(fileName, settings.config.root);
|
|
55
|
+
if (!existsSync(url)) {
|
|
56
|
+
logger.error(`File not found: ${fileName}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const filePath = fileURLToPath(url);
|
|
60
|
+
await syncData(filePath, options);
|
|
61
|
+
watcher?.on("change", async (changedPath) => {
|
|
62
|
+
if (changedPath === filePath) {
|
|
63
|
+
logger.info(`Reloading data from ${fileName}`);
|
|
64
|
+
await syncData(filePath, options);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
file
|
|
72
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Loader } from './types.js';
|
|
2
|
+
export interface GenerateIdOptions {
|
|
3
|
+
/** The path to the entry file, relative to the base directory. */
|
|
4
|
+
entry: string;
|
|
5
|
+
/** The base directory URL. */
|
|
6
|
+
base: URL;
|
|
7
|
+
/** The parsed, unvalidated data of the entry. */
|
|
8
|
+
data: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface GlobOptions {
|
|
11
|
+
/** The glob pattern to match files, relative to the base directory */
|
|
12
|
+
pattern: string;
|
|
13
|
+
/** The base directory to resolve the glob pattern from. Relative to the root directory, or an absolute file URL. Defaults to `.` */
|
|
14
|
+
base?: string | URL;
|
|
15
|
+
/**
|
|
16
|
+
* Function that generates an ID for an entry. Default implementation generates a slug from the entry path.
|
|
17
|
+
* @returns The ID of the entry. Must be unique per collection.
|
|
18
|
+
**/
|
|
19
|
+
generateId?: (options: GenerateIdOptions) => string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Loads multiple entries, using a glob pattern to match files.
|
|
23
|
+
* @param pattern A glob pattern to match files, relative to the content directory.
|
|
24
|
+
*/
|
|
25
|
+
export declare function glob(globOptions: GlobOptions): Loader;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
import fastGlob from "fast-glob";
|
|
4
|
+
import { bold, green } from "kleur/colors";
|
|
5
|
+
import micromatch from "micromatch";
|
|
6
|
+
import pLimit from "p-limit";
|
|
7
|
+
import { getContentEntryIdAndSlug, getEntryConfigByExtMap, posixRelative } from "../utils.js";
|
|
8
|
+
function generateIdDefault({ entry, base, data }) {
|
|
9
|
+
if (data.slug) {
|
|
10
|
+
return data.slug;
|
|
11
|
+
}
|
|
12
|
+
const entryURL = new URL(entry, base);
|
|
13
|
+
const { slug } = getContentEntryIdAndSlug({
|
|
14
|
+
entry: entryURL,
|
|
15
|
+
contentDir: base,
|
|
16
|
+
collection: ""
|
|
17
|
+
});
|
|
18
|
+
return slug;
|
|
19
|
+
}
|
|
20
|
+
function glob(globOptions) {
|
|
21
|
+
if (globOptions.pattern.startsWith("../")) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Glob patterns cannot start with `../`. Set the `base` option to a parent directory instead."
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (globOptions.pattern.startsWith("/")) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"Glob patterns cannot start with `/`. Set the `base` option to a parent directory or use a relative path instead."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const generateId = globOptions?.generateId ?? generateIdDefault;
|
|
32
|
+
const fileToIdMap = /* @__PURE__ */ new Map();
|
|
33
|
+
return {
|
|
34
|
+
name: "glob-loader",
|
|
35
|
+
load: async ({ settings, logger, watcher, parseData, store, generateDigest }) => {
|
|
36
|
+
const renderFunctionByContentType = /* @__PURE__ */ new WeakMap();
|
|
37
|
+
const untouchedEntries = new Set(store.keys());
|
|
38
|
+
async function syncData(entry, base, entryType) {
|
|
39
|
+
if (!entryType) {
|
|
40
|
+
logger.warn(`No entry type found for ${entry}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const fileUrl = new URL(entry, base);
|
|
44
|
+
const contents = await fs.readFile(fileUrl, "utf-8").catch((err) => {
|
|
45
|
+
logger.error(`Error reading ${entry}: ${err.message}`);
|
|
46
|
+
return;
|
|
47
|
+
});
|
|
48
|
+
if (!contents) {
|
|
49
|
+
logger.warn(`No contents found for ${entry}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const { body, data } = await entryType.getEntryInfo({
|
|
53
|
+
contents,
|
|
54
|
+
fileUrl
|
|
55
|
+
});
|
|
56
|
+
const id = generateId({ entry, base, data });
|
|
57
|
+
untouchedEntries.delete(id);
|
|
58
|
+
const existingEntry = store.get(id);
|
|
59
|
+
const digest = generateDigest(contents);
|
|
60
|
+
if (existingEntry && existingEntry.digest === digest && existingEntry.filePath) {
|
|
61
|
+
if (existingEntry.deferredRender) {
|
|
62
|
+
store.addModuleImport(existingEntry.filePath);
|
|
63
|
+
}
|
|
64
|
+
if (existingEntry.rendered?.metadata?.imagePaths?.length) {
|
|
65
|
+
store.addAssetImports(
|
|
66
|
+
existingEntry.rendered.metadata.imagePaths,
|
|
67
|
+
existingEntry.filePath
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
await parseData(existingEntry);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const filePath = fileURLToPath(fileUrl);
|
|
74
|
+
const relativePath = posixRelative(fileURLToPath(settings.config.root), filePath);
|
|
75
|
+
const parsedData = await parseData({
|
|
76
|
+
id,
|
|
77
|
+
data,
|
|
78
|
+
filePath
|
|
79
|
+
});
|
|
80
|
+
if (entryType.getRenderFunction) {
|
|
81
|
+
let render = renderFunctionByContentType.get(entryType);
|
|
82
|
+
if (!render) {
|
|
83
|
+
render = await entryType.getRenderFunction(settings);
|
|
84
|
+
renderFunctionByContentType.set(entryType, render);
|
|
85
|
+
}
|
|
86
|
+
let rendered = void 0;
|
|
87
|
+
try {
|
|
88
|
+
rendered = await render?.({
|
|
89
|
+
id,
|
|
90
|
+
data: parsedData,
|
|
91
|
+
body,
|
|
92
|
+
filePath,
|
|
93
|
+
digest
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
logger.error(`Error rendering ${entry}: ${error.message}`);
|
|
97
|
+
}
|
|
98
|
+
store.set({
|
|
99
|
+
id,
|
|
100
|
+
data: parsedData,
|
|
101
|
+
body,
|
|
102
|
+
filePath: relativePath,
|
|
103
|
+
digest,
|
|
104
|
+
rendered
|
|
105
|
+
});
|
|
106
|
+
if (rendered?.metadata?.imagePaths?.length) {
|
|
107
|
+
store.addAssetImports(rendered.metadata.imagePaths, relativePath);
|
|
108
|
+
}
|
|
109
|
+
} else if ("contentModuleTypes" in entryType) {
|
|
110
|
+
store.set({
|
|
111
|
+
id,
|
|
112
|
+
data: parsedData,
|
|
113
|
+
body,
|
|
114
|
+
filePath: relativePath,
|
|
115
|
+
digest,
|
|
116
|
+
deferredRender: true
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
store.set({ id, data: parsedData, body, filePath: relativePath, digest });
|
|
120
|
+
}
|
|
121
|
+
fileToIdMap.set(filePath, id);
|
|
122
|
+
}
|
|
123
|
+
const entryConfigByExt = getEntryConfigByExtMap([
|
|
124
|
+
...settings.contentEntryTypes,
|
|
125
|
+
...settings.dataEntryTypes
|
|
126
|
+
]);
|
|
127
|
+
const baseDir = globOptions.base ? new URL(globOptions.base, settings.config.root) : settings.config.root;
|
|
128
|
+
if (!baseDir.pathname.endsWith("/")) {
|
|
129
|
+
baseDir.pathname = `${baseDir.pathname}/`;
|
|
130
|
+
}
|
|
131
|
+
const files = await fastGlob(globOptions.pattern, {
|
|
132
|
+
cwd: fileURLToPath(baseDir)
|
|
133
|
+
});
|
|
134
|
+
function configForFile(file) {
|
|
135
|
+
const ext = file.split(".").at(-1);
|
|
136
|
+
if (!ext) {
|
|
137
|
+
logger.warn(`No extension found for ${file}`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
return entryConfigByExt.get(`.${ext}`);
|
|
141
|
+
}
|
|
142
|
+
const limit = pLimit(10);
|
|
143
|
+
const skippedFiles = [];
|
|
144
|
+
const contentDir = new URL("content/", settings.config.srcDir);
|
|
145
|
+
function isInContentDir(file) {
|
|
146
|
+
const fileUrl = new URL(file, baseDir);
|
|
147
|
+
return fileUrl.href.startsWith(contentDir.href);
|
|
148
|
+
}
|
|
149
|
+
const configFiles = new Set(
|
|
150
|
+
["config.js", "config.ts", "config.mjs"].map((file) => new URL(file, contentDir).href)
|
|
151
|
+
);
|
|
152
|
+
function isConfigFile(file) {
|
|
153
|
+
const fileUrl = new URL(file, baseDir);
|
|
154
|
+
return configFiles.has(fileUrl.href);
|
|
155
|
+
}
|
|
156
|
+
await Promise.all(
|
|
157
|
+
files.map((entry) => {
|
|
158
|
+
if (isConfigFile(entry)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (isInContentDir(entry)) {
|
|
162
|
+
skippedFiles.push(entry);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
return limit(async () => {
|
|
166
|
+
const entryType = configForFile(entry);
|
|
167
|
+
await syncData(entry, baseDir, entryType);
|
|
168
|
+
});
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
const skipCount = skippedFiles.length;
|
|
172
|
+
if (skipCount > 0) {
|
|
173
|
+
logger.warn(`The glob() loader cannot be used for files in ${bold("src/content")}.`);
|
|
174
|
+
if (skipCount > 10) {
|
|
175
|
+
logger.warn(
|
|
176
|
+
`Skipped ${green(skippedFiles.length)} files that matched ${green(globOptions.pattern)}.`
|
|
177
|
+
);
|
|
178
|
+
} else {
|
|
179
|
+
logger.warn(`Skipped the following files that matched ${green(globOptions.pattern)}:`);
|
|
180
|
+
skippedFiles.forEach((file) => logger.warn(`\u2022 ${green(file)}`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
untouchedEntries.forEach((id) => store.delete(id));
|
|
184
|
+
if (!watcher) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const matcher = micromatch.makeRe(globOptions.pattern);
|
|
188
|
+
const matchesGlob = (entry) => !entry.startsWith("../") && matcher.test(entry);
|
|
189
|
+
const basePath = fileURLToPath(baseDir);
|
|
190
|
+
async function onChange(changedPath) {
|
|
191
|
+
const entry = posixRelative(basePath, changedPath);
|
|
192
|
+
if (!matchesGlob(entry)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const entryType = configForFile(changedPath);
|
|
196
|
+
const baseUrl = pathToFileURL(basePath);
|
|
197
|
+
await syncData(entry, baseUrl, entryType);
|
|
198
|
+
logger.info(`Reloaded data from ${green(entry)}`);
|
|
199
|
+
}
|
|
200
|
+
watcher.on("change", onChange);
|
|
201
|
+
watcher.on("add", onChange);
|
|
202
|
+
watcher.on("unlink", async (deletedPath) => {
|
|
203
|
+
const entry = posixRelative(basePath, deletedPath);
|
|
204
|
+
if (!matchesGlob(entry)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const id = fileToIdMap.get(deletedPath);
|
|
208
|
+
if (id) {
|
|
209
|
+
store.delete(id);
|
|
210
|
+
fileToIdMap.delete(deletedPath);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export {
|
|
217
|
+
glob
|
|
218
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { FSWatcher } from 'vite';
|
|
2
|
+
import type { ZodSchema } from 'zod';
|
|
3
|
+
import type { AstroIntegrationLogger, AstroSettings } from '../../@types/astro.js';
|
|
4
|
+
import type { MetaStore, ScopedDataStore } from '../mutable-data-store.js';
|
|
5
|
+
export interface ParseDataOptions<TData extends Record<string, unknown>> {
|
|
6
|
+
/** The ID of the entry. Unique per collection */
|
|
7
|
+
id: string;
|
|
8
|
+
/** The raw, unvalidated data of the entry */
|
|
9
|
+
data: TData;
|
|
10
|
+
/** An optional file path, where the entry represents a local file. */
|
|
11
|
+
filePath?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface LoaderContext {
|
|
14
|
+
/** The unique name of the collection */
|
|
15
|
+
collection: string;
|
|
16
|
+
/** A database abstraction to store the actual data */
|
|
17
|
+
store: ScopedDataStore;
|
|
18
|
+
/** A simple KV store, designed for things like sync tokens */
|
|
19
|
+
meta: MetaStore;
|
|
20
|
+
logger: AstroIntegrationLogger;
|
|
21
|
+
settings: AstroSettings;
|
|
22
|
+
/** Validates and parses the data according to the collection schema */
|
|
23
|
+
parseData<TData extends Record<string, unknown>>(props: ParseDataOptions<TData>): Promise<TData>;
|
|
24
|
+
/** Generates a non-cryptographic content digest. This can be used to check if the data has changed */
|
|
25
|
+
generateDigest(data: Record<string, unknown> | string): string;
|
|
26
|
+
/** When running in dev, this is a filesystem watcher that can be used to trigger updates */
|
|
27
|
+
watcher?: FSWatcher;
|
|
28
|
+
}
|
|
29
|
+
export interface Loader {
|
|
30
|
+
/** Unique name of the loader, e.g. the npm package name */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Do the actual loading of the data */
|
|
33
|
+
load: (context: LoaderContext) => Promise<void>;
|
|
34
|
+
/** Optionally, define the schema of the data. Will be overridden by user-defined schema */
|
|
35
|
+
schema?: ZodSchema | Promise<ZodSchema> | (() => ZodSchema | Promise<ZodSchema>);
|
|
36
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type PathLike } from 'node:fs';
|
|
2
|
+
import { type DataEntry, DataStore, type RenderedContent } from './data-store.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extends the DataStore with the ability to change entries and write them to disk.
|
|
5
|
+
* This is kept as a separate class to avoid needing node builtins at runtime, when read-only access is all that is needed.
|
|
6
|
+
*/
|
|
7
|
+
export declare class MutableDataStore extends DataStore {
|
|
8
|
+
#private;
|
|
9
|
+
set(collectionName: string, key: string, value: unknown): void;
|
|
10
|
+
delete(collectionName: string, key: string): void;
|
|
11
|
+
clear(collectionName: string): void;
|
|
12
|
+
clearAll(): void;
|
|
13
|
+
addAssetImport(assetImport: string, filePath: string): void;
|
|
14
|
+
addAssetImports(assets: Array<string>, filePath: string): void;
|
|
15
|
+
addModuleImport(fileName: string): void;
|
|
16
|
+
writeAssetImports(filePath: PathLike): Promise<void>;
|
|
17
|
+
writeModuleImports(filePath: PathLike): Promise<void>;
|
|
18
|
+
scopedStore(collectionName: string): ScopedDataStore;
|
|
19
|
+
/**
|
|
20
|
+
* Returns a MetaStore for a given collection, or if no collection is provided, the default meta collection.
|
|
21
|
+
*/
|
|
22
|
+
metaStore(collectionName?: string): MetaStore;
|
|
23
|
+
toString(): string;
|
|
24
|
+
writeToDisk(filePath: PathLike): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Attempts to load a MutableDataStore from the virtual module.
|
|
27
|
+
* This only works in Vite.
|
|
28
|
+
*/
|
|
29
|
+
static fromModule(): Promise<MutableDataStore>;
|
|
30
|
+
static fromMap(data: Map<string, Map<string, any>>): Promise<MutableDataStore>;
|
|
31
|
+
static fromString(data: string): Promise<MutableDataStore>;
|
|
32
|
+
static fromFile(filePath: string | URL): Promise<MutableDataStore>;
|
|
33
|
+
}
|
|
34
|
+
export interface ScopedDataStore {
|
|
35
|
+
get: <TData extends Record<string, unknown> = Record<string, unknown>>(key: string) => DataEntry<TData> | undefined;
|
|
36
|
+
entries: () => Array<[id: string, DataEntry]>;
|
|
37
|
+
set: <TData extends Record<string, unknown>>(opts: {
|
|
38
|
+
/** The ID of the entry. Must be unique per collection. */
|
|
39
|
+
id: string;
|
|
40
|
+
/** The data to store. */
|
|
41
|
+
data: TData;
|
|
42
|
+
/** The raw body of the content, if applicable. */
|
|
43
|
+
body?: string;
|
|
44
|
+
/** The file path of the content, if applicable. Relative to the site root. */
|
|
45
|
+
filePath?: string;
|
|
46
|
+
/** A content digest, to check if the content has changed. */
|
|
47
|
+
digest?: number | string;
|
|
48
|
+
/** The rendered content, if applicable. */
|
|
49
|
+
rendered?: RenderedContent;
|
|
50
|
+
/**
|
|
51
|
+
* If an entry is a deferred, its rendering phase is delegated to a virtual module during the runtime phase.
|
|
52
|
+
*/
|
|
53
|
+
deferredRender?: boolean;
|
|
54
|
+
}) => boolean;
|
|
55
|
+
values: () => Array<DataEntry>;
|
|
56
|
+
keys: () => Array<string>;
|
|
57
|
+
delete: (key: string) => void;
|
|
58
|
+
clear: () => void;
|
|
59
|
+
has: (key: string) => boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Adds a single asset to the store. This asset will be transformed
|
|
62
|
+
* by Vite, and the URL will be available in the final build.
|
|
63
|
+
* @param fileName
|
|
64
|
+
* @param specifier
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
67
|
+
addModuleImport: (fileName: string) => void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A key-value store for metadata strings. Useful for storing things like sync tokens.
|
|
71
|
+
*/
|
|
72
|
+
export interface MetaStore {
|
|
73
|
+
get: (key: string) => string | undefined;
|
|
74
|
+
set: (key: string, value: string) => void;
|
|
75
|
+
has: (key: string) => boolean;
|
|
76
|
+
delete: (key: string) => void;
|
|
77
|
+
}
|