@farm.js/content 0.1.0-beta.0 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +13 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +38 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +123 -1
- package/dist/loader.d.ts +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +125 -10
- package/dist/markdown-assets.d.ts.map +1 -1
- package/dist/markdown-assets.js +17 -2
- package/dist/runtime.d.ts +14 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +157 -1
- package/dist/server.d.ts +11 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +7 -0
- package/dist/types.d.ts +65 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -3
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentAssetsInput, ContentCollection, ContentCollectionInput, ContentDataWithAssets, ContentFileSource, ContentSchema, InferContentSchema } from "./types.js";
|
|
1
|
+
import type { ContentAssetsInput, ContentCollection, ContentCollectionInput, ContentDataWithAssets, ContentFileSource, ContentRemoteDocument, ContentRemoteSource, ContentSchema, InferContentSchema } from "./types.js";
|
|
2
2
|
export interface ContentFilesOptions {
|
|
3
3
|
/** Ignore globs, evaluated relative to the Farm project root. */
|
|
4
4
|
ignore?: string | readonly string[];
|
|
@@ -6,6 +6,18 @@ export interface ContentFilesOptions {
|
|
|
6
6
|
base?: string;
|
|
7
7
|
}
|
|
8
8
|
export declare function files(patterns: string | readonly string[], options?: ContentFilesOptions): ContentFileSource;
|
|
9
|
+
export interface ContentRemoteOptions {
|
|
10
|
+
/** Names the source in error messages, e.g. "sanity:posts". */
|
|
11
|
+
name: string;
|
|
12
|
+
fetch(): Promise<readonly ContentRemoteDocument[]>;
|
|
13
|
+
/** Development re-fetch cadence in milliseconds. */
|
|
14
|
+
refreshInterval?: number;
|
|
15
|
+
create?: ContentRemoteSource["create"];
|
|
16
|
+
update?: ContentRemoteSource["update"];
|
|
17
|
+
delete?: ContentRemoteSource["delete"];
|
|
18
|
+
}
|
|
19
|
+
/** A content source that fetches documents instead of reading local files. */
|
|
20
|
+
export declare function remote(options: ContentRemoteOptions): ContentRemoteSource;
|
|
9
21
|
export declare function collection<TSchema extends ContentSchema<any>, const TAssets extends ContentAssetsInput = undefined, TTransformed = ContentDataWithAssets<InferContentSchema<TSchema>, TAssets>>(input: ContentCollectionInput<TSchema, TTransformed, TAssets>): ContentCollection<TTransformed, InferContentSchema<TSchema>>;
|
|
10
22
|
export declare function isSupportedContentFile(filePath: string): boolean;
|
|
11
23
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACpC,gGAAgG;IAChG,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,KAAK,CACnB,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACpC,OAAO,GAAE,mBAAwB,GAChC,iBAAiB,CAWnB;AAED,wBAAgB,UAAU,CACxB,OAAO,SAAS,aAAa,CAAC,GAAG,CAAC,EAClC,KAAK,CAAC,OAAO,SAAS,kBAAkB,GAAG,SAAS,EACpD,YAAY,GAAG,qBAAqB,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAE1E,KAAK,EAAE,sBAAsB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,GAC5D,iBAAiB,CAAC,YAAY,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACpC,gGAAgG;IAChG,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,KAAK,CACnB,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACpC,OAAO,GAAE,mBAAwB,GAChC,iBAAiB,CAWnB;AAED,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,SAAS,qBAAqB,EAAE,CAAC,CAAC;IACnD,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;CACxC;AAED,8EAA8E;AAC9E,wBAAgB,MAAM,CAAC,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CAmCzE;AAED,wBAAgB,UAAU,CACxB,OAAO,SAAS,aAAa,CAAC,GAAG,CAAC,EAClC,KAAK,CAAC,OAAO,SAAS,kBAAkB,GAAG,SAAS,EACpD,YAAY,GAAG,qBAAqB,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAE1E,KAAK,EAAE,sBAAsB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,GAC5D,iBAAiB,CAAC,YAAY,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAuC9D;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEhE"}
|
package/dist/config.js
CHANGED
|
@@ -12,12 +12,48 @@ export function files(patterns, options = {}) {
|
|
|
12
12
|
...(base ? { base } : {}),
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
/** A content source that fetches documents instead of reading local files. */
|
|
16
|
+
export function remote(options) {
|
|
17
|
+
if (!options || typeof options !== "object") {
|
|
18
|
+
throw new TypeError("remote() requires an options object");
|
|
19
|
+
}
|
|
20
|
+
const name = typeof options.name === "string" ? options.name.trim() : "";
|
|
21
|
+
if (!name) {
|
|
22
|
+
throw new TypeError("remote() requires a non-empty name for error messages");
|
|
23
|
+
}
|
|
24
|
+
if (typeof options.fetch !== "function") {
|
|
25
|
+
throw new TypeError(`remote(${JSON.stringify(name)}) requires a fetch() function`);
|
|
26
|
+
}
|
|
27
|
+
if (options.refreshInterval !== undefined &&
|
|
28
|
+
(!Number.isFinite(options.refreshInterval) || options.refreshInterval < 100)) {
|
|
29
|
+
throw new TypeError(`remote(${JSON.stringify(name)}) refreshInterval must be at least 100 milliseconds`);
|
|
30
|
+
}
|
|
31
|
+
for (const verb of ["create", "update", "delete"]) {
|
|
32
|
+
if (options[verb] !== undefined && typeof options[verb] !== "function") {
|
|
33
|
+
throw new TypeError(`remote(${JSON.stringify(name)}) ${verb} must be a function`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
kind: "remote",
|
|
38
|
+
name,
|
|
39
|
+
fetch: options.fetch,
|
|
40
|
+
...(options.refreshInterval !== undefined ? { refreshInterval: options.refreshInterval } : {}),
|
|
41
|
+
...(options.create ? { create: options.create } : {}),
|
|
42
|
+
...(options.update ? { update: options.update } : {}),
|
|
43
|
+
...(options.delete ? { delete: options.delete } : {}),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
15
46
|
export function collection(input) {
|
|
16
47
|
if (!input || typeof input !== "object") {
|
|
17
48
|
throw new TypeError("Content collection must be an object");
|
|
18
49
|
}
|
|
19
|
-
if (input.source?.kind !== "files") {
|
|
20
|
-
throw new TypeError("Content collection source must come from files()");
|
|
50
|
+
if (input.source?.kind !== "files" && input.source?.kind !== "remote") {
|
|
51
|
+
throw new TypeError("Content collection source must come from files() or remote()");
|
|
52
|
+
}
|
|
53
|
+
if (input.source.kind === "remote" && input.assets && input.assets !== true) {
|
|
54
|
+
// Typed asset fields resolve files next to the entry on disk; a remote
|
|
55
|
+
// document has no directory for them to live in.
|
|
56
|
+
throw new TypeError(`Content collection with remote source ${JSON.stringify(input.source.name)} cannot declare asset fields`);
|
|
21
57
|
}
|
|
22
58
|
if (!isContentSchema(input.schema)) {
|
|
23
59
|
throw new TypeError("Content collection schema must implement Standard Schema, parseAsync, or parse");
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,10 @@ import { asset } from "./assets.js";
|
|
|
2
2
|
import { collection, files } from "./config.js";
|
|
3
3
|
import type { ContentCollections, ContentOptions, ContentPlugin } from "./types.js";
|
|
4
4
|
export { asset, collection, files };
|
|
5
|
+
export { remote } from "./config.js";
|
|
6
|
+
export type { ContentRemoteOptions } from "./config.js";
|
|
5
7
|
export type { ContentFilesOptions } from "./config.js";
|
|
6
|
-
export type { AppContentEntry, AppContentRegistry, ContentAssetField, ContentAssetFields, ContentAssetsInput, ContentAssetValue, ContentCollection, ContentCollectionInput, ContentCollectionName, ContentCollections, ContentEntry, ContentFileAsset, ContentFileSource, ContentImageAsset, ContentOptions, ContentPlugin, ContentSchema, ContentTransformContext, InferContentCollectionEntry, InferContentAssetFields, InferContentRegistry, InferContentSchema, } from "./types.js";
|
|
8
|
+
export type { AppContentEntry, AppContentRegistry, ContentAssetField, ContentAssetFields, ContentAssetsInput, ContentAssetValue, ContentCollection, ContentCollectionInput, ContentCollectionName, ContentCollections, ContentEntry, ContentFileAsset, ContentFileSource, ContentRemoteDocument, ContentRemoteSource, ContentSource, ContentImageAsset, ContentOptions, ContentPlugin, ContentSchema, ContentTransformContext, InferContentCollectionEntry, InferContentAssetFields, InferContentRegistry, InferContentSchema, } from "./types.js";
|
|
7
9
|
/** Load schema-validated local content as a typed server-only collection registry. */
|
|
8
10
|
export declare function content<const TCollections extends ContentCollections>(options: ContentOptions<TCollections>): ContentPlugin<TCollections>;
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,KAAK,EAA0B,MAAM,aAAa,CAAC;AAGxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIpF,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAEpB,sFAAsF;AACtF,wBAAgB,OAAO,CAAC,KAAK,CAAC,YAAY,SAAS,kBAAkB,EACnE,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,GACpC,aAAa,CAAC,YAAY,CAAC,CAoM7B"}
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
2
3
|
import { definePlugin } from "@farm.js/core/plugin";
|
|
3
4
|
import fg from "fast-glob";
|
|
4
5
|
import { asset } from "./assets.js";
|
|
5
6
|
import { collection, files, isSupportedContentFile } from "./config.js";
|
|
6
7
|
import { loadContentCollections, writeContentServerModule } from "./loader.js";
|
|
8
|
+
import { registerContentWriteRuntime, setContentAfterWrite } from "./runtime.js";
|
|
7
9
|
const CONTENT_SERVER_ID = "@farm.js/content/server";
|
|
8
10
|
export { asset, collection, files };
|
|
11
|
+
export { remote } from "./config.js";
|
|
9
12
|
/** Load schema-validated local content as a typed server-only collection registry. */
|
|
10
13
|
export function content(options) {
|
|
11
14
|
validateOptions(options);
|
|
15
|
+
// Writes need the live sources and schemas; the snapshot module carries
|
|
16
|
+
// only data. Registered here so both dev and the production runtime (where
|
|
17
|
+
// the config module is evaluated too) can reach them.
|
|
18
|
+
const unregisterWriteRuntime = registerContentWriteRuntime(Object.fromEntries(Object.entries(options.collections).map(([name, definition]) => [
|
|
19
|
+
name,
|
|
20
|
+
{
|
|
21
|
+
...(definition.source.kind === "remote" ? { source: definition.source } : {}),
|
|
22
|
+
schema: definition.schema,
|
|
23
|
+
},
|
|
24
|
+
])));
|
|
12
25
|
let root = process.cwd();
|
|
13
26
|
let generatedFile = "";
|
|
14
27
|
let sourceFiles = new Set();
|
|
@@ -39,9 +52,11 @@ export function content(options) {
|
|
|
39
52
|
async function rebuild() {
|
|
40
53
|
const attemptedSourceFiles = new Set();
|
|
41
54
|
try {
|
|
55
|
+
await assertGeneratedOutputInsideRoot(root, generatedFile);
|
|
42
56
|
const loaded = await loadContentCollections(root, options.collections, attemptedSourceFiles);
|
|
43
|
-
await writeContentServerModule(generatedFile, loaded.collections, loaded.assetImports);
|
|
57
|
+
const changed = await writeContentServerModule(generatedFile, loaded.collections, loaded.assetImports);
|
|
44
58
|
sourceFiles = new Set(loaded.sourceFiles);
|
|
59
|
+
return changed;
|
|
45
60
|
}
|
|
46
61
|
catch (error) {
|
|
47
62
|
sourceFiles = new Set([...sourceFiles, ...attemptedSourceFiles]);
|
|
@@ -89,11 +104,116 @@ export function content(options) {
|
|
|
89
104
|
};
|
|
90
105
|
server.watcher.on("all", onFile);
|
|
91
106
|
server.httpServer?.once("close", () => server.watcher.off?.("all", onFile));
|
|
107
|
+
// A successful write refreshes the snapshot immediately in development.
|
|
108
|
+
// The stored chain must always settle: a rejected tail would swallow
|
|
109
|
+
// the next watcher rebuild entirely, so the error report lives inside
|
|
110
|
+
// the chain and the queue continues past it.
|
|
111
|
+
const disposeAfterWrite = setContentAfterWrite(async () => {
|
|
112
|
+
const settled = (rebuildQueue = rebuildQueue
|
|
113
|
+
.then(async () => {
|
|
114
|
+
if (!(await rebuild()))
|
|
115
|
+
return;
|
|
116
|
+
const modules = server.moduleGraph.getModulesByFile?.(generatedFile);
|
|
117
|
+
for (const module of modules ?? [])
|
|
118
|
+
server.moduleGraph.invalidateModule(module);
|
|
119
|
+
server.ws.send({ type: "full-reload" });
|
|
120
|
+
})
|
|
121
|
+
.catch((error) => {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
server.ws.send({
|
|
124
|
+
type: "error",
|
|
125
|
+
err: { message, stack: error instanceof Error ? error.stack : undefined },
|
|
126
|
+
});
|
|
127
|
+
}));
|
|
128
|
+
await settled;
|
|
129
|
+
});
|
|
130
|
+
// Vite restart creates the replacement server before closing this one;
|
|
131
|
+
// the scoped disposers only clear registrations this server owns.
|
|
132
|
+
server.httpServer?.once("close", () => {
|
|
133
|
+
disposeAfterWrite();
|
|
134
|
+
unregisterWriteRuntime();
|
|
135
|
+
});
|
|
136
|
+
// Remote sources have no file events; poll the ones that asked for it.
|
|
137
|
+
// One timer at the smallest requested cadence keeps ordering simple,
|
|
138
|
+
// and a reload only goes out when the generated module actually changed.
|
|
139
|
+
const intervals = Object.values(options.collections)
|
|
140
|
+
.map((definition) => definition.source)
|
|
141
|
+
.filter((candidate) => candidate.kind === "remote")
|
|
142
|
+
.map((candidate) => candidate.refreshInterval)
|
|
143
|
+
.filter((value) => typeof value === "number");
|
|
144
|
+
if (intervals.length > 0) {
|
|
145
|
+
// A persistently failing remote fetch reports once per distinct
|
|
146
|
+
// message, not once per tick - the overlay is a signal, not a log.
|
|
147
|
+
let lastPollError;
|
|
148
|
+
const timer = setInterval(() => {
|
|
149
|
+
rebuildQueue = rebuildQueue
|
|
150
|
+
.then(async () => {
|
|
151
|
+
if (!(await rebuild()))
|
|
152
|
+
return;
|
|
153
|
+
lastPollError = undefined;
|
|
154
|
+
const modules = server.moduleGraph.getModulesByFile?.(generatedFile);
|
|
155
|
+
for (const module of modules ?? [])
|
|
156
|
+
server.moduleGraph.invalidateModule(module);
|
|
157
|
+
server.ws.send({ type: "full-reload" });
|
|
158
|
+
})
|
|
159
|
+
.catch((error) => {
|
|
160
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
161
|
+
if (message === lastPollError)
|
|
162
|
+
return;
|
|
163
|
+
lastPollError = message;
|
|
164
|
+
server.ws.send({
|
|
165
|
+
type: "error",
|
|
166
|
+
err: { message, stack: error instanceof Error ? error.stack : undefined },
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}, Math.min(...intervals));
|
|
170
|
+
timer.unref?.();
|
|
171
|
+
server.httpServer?.once("close", () => clearInterval(timer));
|
|
172
|
+
}
|
|
92
173
|
},
|
|
93
174
|
};
|
|
94
175
|
}
|
|
95
176
|
return plugin;
|
|
96
177
|
}
|
|
178
|
+
async function assertGeneratedOutputInsideRoot(root, outputFile) {
|
|
179
|
+
const actualRoot = await realpath(root);
|
|
180
|
+
const missingSegments = [];
|
|
181
|
+
let existingAncestor = outputFile;
|
|
182
|
+
const unsafeOutput = () => new Error("[farm:content] Generated content output must stay inside the Farm project root, including through symlinks");
|
|
183
|
+
// Check the prospective destination before mkdir or writeFile can follow a link.
|
|
184
|
+
while (true) {
|
|
185
|
+
let actualAncestor;
|
|
186
|
+
try {
|
|
187
|
+
actualAncestor = await realpath(existingAncestor);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (error.code !== "ENOENT")
|
|
191
|
+
throw error;
|
|
192
|
+
const entry = await lstat(existingAncestor).catch((statError) => {
|
|
193
|
+
if (statError.code !== "ENOENT")
|
|
194
|
+
throw statError;
|
|
195
|
+
return undefined;
|
|
196
|
+
});
|
|
197
|
+
if (entry)
|
|
198
|
+
throw unsafeOutput(); // A dangling symlink is not a missing directory.
|
|
199
|
+
const parent = path.dirname(existingAncestor);
|
|
200
|
+
if (parent === existingAncestor)
|
|
201
|
+
throw error;
|
|
202
|
+
missingSegments.unshift(path.basename(existingAncestor));
|
|
203
|
+
existingAncestor = parent;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const destination = path.join(actualAncestor, ...missingSegments);
|
|
207
|
+
const relative = path.relative(actualRoot, destination);
|
|
208
|
+
if (!relative ||
|
|
209
|
+
relative === ".." ||
|
|
210
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
211
|
+
path.isAbsolute(relative)) {
|
|
212
|
+
throw unsafeOutput();
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
97
217
|
async function shouldRebuildForFile(root, filePath, event, sourceFiles, collections) {
|
|
98
218
|
if (sourceFiles.has(filePath))
|
|
99
219
|
return true;
|
|
@@ -106,6 +226,8 @@ async function shouldRebuildForFile(root, filePath, event, sourceFiles, collecti
|
|
|
106
226
|
return false;
|
|
107
227
|
}
|
|
108
228
|
for (const definition of Object.values(collections)) {
|
|
229
|
+
if (definition.source.kind !== "files")
|
|
230
|
+
continue;
|
|
109
231
|
const matches = await fg([...definition.source.patterns], {
|
|
110
232
|
cwd: root,
|
|
111
233
|
absolute: true,
|
package/dist/loader.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface LoadedContent {
|
|
|
6
6
|
readonly assetImports: ReadonlyMap<string, ContentAssetImport>;
|
|
7
7
|
}
|
|
8
8
|
export declare function loadContentCollections(root: string, collections: ContentCollections, sourceFiles?: Set<string>): Promise<LoadedContent>;
|
|
9
|
-
export declare function writeContentServerModule(outputFile: string, collections: Readonly<Record<string, readonly ContentEntry<any>[]>>, assetImports?: ReadonlyMap<string, ContentAssetImport>): Promise<
|
|
9
|
+
export declare function writeContentServerModule(outputFile: string, collections: Readonly<Record<string, readonly ContentEntry<any>[]>>, assetImports?: ReadonlyMap<string, ContentAssetImport>): Promise<boolean>;
|
|
10
10
|
type EncodedContentValue = null | boolean | number | string | ["undefined"] | ["date", string] | ["bigint", string] | ["array", EncodedContentValue[]] | ["object", Array<[string, EncodedContentValue]>];
|
|
11
11
|
export declare function encodeContentValue(value: unknown, seen?: Set<object>): EncodedContentValue;
|
|
12
12
|
export {};
|
package/dist/loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAKV,kBAAkB,EAClB,YAAY,EAIb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7E,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAChE;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,kBAAkB,EAC/B,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,OAAO,CAAC,aAAa,CAAC,CAuBxB;AAED,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EACnE,YAAY,GAAE,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAa,GAChE,OAAO,CAAC,OAAO,CAAC,CAsClB;AA+XD,KAAK,mBAAmB,GACpB,IAAI,GACJ,OAAO,GACP,MAAM,GACN,MAAM,GACN,CAAC,WAAW,CAAC,GACb,CAAC,MAAM,EAAE,MAAM,CAAC,GAChB,CAAC,QAAQ,EAAE,MAAM,CAAC,GAClB,CAAC,OAAO,EAAE,mBAAmB,EAAE,CAAC,GAChC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAErD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,cAAoB,GAAG,mBAAmB,CAmChG"}
|
package/dist/loader.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir, readFile, realpath, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import fg from "fast-glob";
|
|
4
5
|
import matter from "gray-matter";
|
|
@@ -9,9 +10,10 @@ import { resolveMarkdownAssets } from "./markdown-assets.js";
|
|
|
9
10
|
export async function loadContentCollections(root, collections, sourceFiles = new Set()) {
|
|
10
11
|
const loaded = Object.create(null);
|
|
11
12
|
const assetImports = new Map();
|
|
13
|
+
const actualRoot = await realpath(root);
|
|
12
14
|
for (const [name, definition] of Object.entries(collections)) {
|
|
13
15
|
validateCollectionName(name);
|
|
14
|
-
const entries = await loadCollection(root, name, definition, sourceFiles, assetImports);
|
|
16
|
+
const entries = await loadCollection(root, actualRoot, name, definition, sourceFiles, assetImports);
|
|
15
17
|
loaded[name] = Object.freeze(entries);
|
|
16
18
|
}
|
|
17
19
|
return {
|
|
@@ -26,9 +28,17 @@ export async function writeContentServerModule(outputFile, collections, assetImp
|
|
|
26
28
|
const actualOutputDirectory = await realpath(outputDirectory);
|
|
27
29
|
const encoded = encodeContentValue(collections);
|
|
28
30
|
const imports = [...assetImports.values()].sort((left, right) => left.token.localeCompare(right.token));
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
.
|
|
31
|
+
const importLines = [];
|
|
32
|
+
for (const [index, asset] of imports.entries()) {
|
|
33
|
+
const relativePath = toViteImportSpecifier(actualOutputDirectory, asset.filePath);
|
|
34
|
+
// Vite treats these characters as URL syntax, even in resolved filesystem IDs.
|
|
35
|
+
// Only stage affected assets; ordinary files keep their direct ?url imports.
|
|
36
|
+
const importPath = /[?#%]/.test(relativePath)
|
|
37
|
+
? await stageContentAsset(actualOutputDirectory, asset.filePath)
|
|
38
|
+
: relativePath;
|
|
39
|
+
importLines.push(`import farmContentAsset${index} from ${JSON.stringify(`${importPath}?url`)};`);
|
|
40
|
+
}
|
|
41
|
+
const importSource = importLines.join("\n");
|
|
32
42
|
const assetUrls = imports.length
|
|
33
43
|
? `, { ${imports
|
|
34
44
|
.map((asset, index) => `${JSON.stringify(asset.token)}: farmContentAsset${index}`)
|
|
@@ -43,11 +53,40 @@ const runtime = createContentRuntime(decodeContentValue(${JSON.stringify(encoded
|
|
|
43
53
|
export const getCollection = runtime.getCollection;
|
|
44
54
|
export const getEntry = runtime.getEntry;
|
|
45
55
|
export const getEntryOrThrow = runtime.getEntryOrThrow;
|
|
56
|
+
export const collections = runtime.collections;
|
|
46
57
|
`;
|
|
47
|
-
|
|
58
|
+
return writeFileIfChanged(outputFile, source);
|
|
59
|
+
}
|
|
60
|
+
async function stageContentAsset(outputDirectory, filePath) {
|
|
61
|
+
const bytes = await readFile(filePath);
|
|
62
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
63
|
+
const extension = path.extname(filePath);
|
|
64
|
+
const filename = `${digest}${/^\.[a-z\d]+$/i.test(extension) ? extension : ".bin"}`;
|
|
65
|
+
const directory = path.join(outputDirectory, "assets");
|
|
66
|
+
await mkdir(directory, { recursive: true });
|
|
67
|
+
if ((await realpath(directory)) !== directory) {
|
|
68
|
+
throw new Error("[farm:content] Generated asset directory cannot be a symlink");
|
|
69
|
+
}
|
|
70
|
+
const target = path.join(directory, filename);
|
|
71
|
+
try {
|
|
72
|
+
// Never overwrite an existing file or follow a pre-existing destination symlink.
|
|
73
|
+
await writeFile(target, bytes, { flag: "wx" });
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error.code !== "EEXIST")
|
|
77
|
+
throw error;
|
|
78
|
+
if (!(await lstat(target)).isFile() || !(await readFile(target)).equals(bytes)) {
|
|
79
|
+
throw new Error(`[farm:content] Generated asset ${filename} is not a matching regular file`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Content-addressing changes the import on edits, avoiding stale Vite module URLs.
|
|
83
|
+
return `./assets/${filename}`;
|
|
48
84
|
}
|
|
49
|
-
async function loadCollection(root, name, definition, sourceFiles, assetImports) {
|
|
85
|
+
async function loadCollection(root, actualRoot, name, definition, sourceFiles, assetImports) {
|
|
50
86
|
const source = definition.source;
|
|
87
|
+
if (source.kind === "remote") {
|
|
88
|
+
return loadRemoteCollection(name, definition, source);
|
|
89
|
+
}
|
|
51
90
|
const relativeFiles = await fg([...source.patterns], {
|
|
52
91
|
cwd: root,
|
|
53
92
|
absolute: false,
|
|
@@ -72,7 +111,15 @@ async function loadCollection(root, name, definition, sourceFiles, assetImports)
|
|
|
72
111
|
}
|
|
73
112
|
ids.set(id, relativeFile);
|
|
74
113
|
sourceFiles.add(absoluteFile);
|
|
75
|
-
const
|
|
114
|
+
const actualFile = await realpath(absoluteFile);
|
|
115
|
+
const relativeToRoot = path.relative(actualRoot, actualFile);
|
|
116
|
+
if (relativeToRoot === ".." ||
|
|
117
|
+
relativeToRoot.startsWith(`..${path.sep}`) ||
|
|
118
|
+
path.isAbsolute(relativeToRoot)) {
|
|
119
|
+
throw new Error(`[farm:content] Collection "${name}" source ${relativeFile} cannot leave the Farm project root`);
|
|
120
|
+
}
|
|
121
|
+
sourceFiles.add(actualFile);
|
|
122
|
+
const parsed = await parseContentFile(actualFile, relativeFile);
|
|
76
123
|
const assetContext = {
|
|
77
124
|
root,
|
|
78
125
|
collectionName: name,
|
|
@@ -121,12 +168,79 @@ async function loadCollection(root, name, definition, sourceFiles, assetImports)
|
|
|
121
168
|
}
|
|
122
169
|
return entries;
|
|
123
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* A remote collection goes through the same validation and transform pipeline
|
|
173
|
+
* as files; only acquisition differs. Documents are sorted by ID so the
|
|
174
|
+
* generated module is stable across fetches that return in a different order.
|
|
175
|
+
*/
|
|
176
|
+
async function loadRemoteCollection(name, definition, source) {
|
|
177
|
+
let documents;
|
|
178
|
+
try {
|
|
179
|
+
documents = await source.fetch();
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
throw new Error(`[farm:content] Collection ${JSON.stringify(name)} failed to fetch from ${JSON.stringify(source.name)}: ${errorMessage(error)}`, { cause: error });
|
|
183
|
+
}
|
|
184
|
+
if (!Array.isArray(documents)) {
|
|
185
|
+
throw new Error(`[farm:content] Collection ${JSON.stringify(name)}: source ${JSON.stringify(source.name)} must resolve to an array of documents`);
|
|
186
|
+
}
|
|
187
|
+
const ids = new Set();
|
|
188
|
+
const sorted = [...documents].sort((left, right) => String(left?.id ?? "").localeCompare(String(right?.id ?? "")));
|
|
189
|
+
const entries = [];
|
|
190
|
+
for (const document of sorted) {
|
|
191
|
+
if (!document ||
|
|
192
|
+
typeof document !== "object" ||
|
|
193
|
+
typeof document.id !== "string" ||
|
|
194
|
+
!document.id) {
|
|
195
|
+
throw new Error(`[farm:content] Collection ${JSON.stringify(name)}: every document from ${JSON.stringify(source.name)} needs a non-empty string id`);
|
|
196
|
+
}
|
|
197
|
+
if (ids.has(document.id)) {
|
|
198
|
+
throw new Error(`[farm:content] Collection ${JSON.stringify(name)} has duplicate entry ID ${JSON.stringify(document.id)} from ${JSON.stringify(source.name)}`);
|
|
199
|
+
}
|
|
200
|
+
ids.add(document.id);
|
|
201
|
+
if (!document.data || typeof document.data !== "object" || Array.isArray(document.data)) {
|
|
202
|
+
throw new Error(`[farm:content] Collection ${JSON.stringify(name)}: document ${JSON.stringify(document.id)} from ${JSON.stringify(source.name)} needs an object \`data\``);
|
|
203
|
+
}
|
|
204
|
+
const reference = `${source.name}/${document.id}`;
|
|
205
|
+
const validatedData = await validateContentData(definition.schema, document.data, name, reference);
|
|
206
|
+
const body = typeof document.body === "string" ? document.body : "";
|
|
207
|
+
const baseEntry = {
|
|
208
|
+
id: document.id,
|
|
209
|
+
data: validatedData,
|
|
210
|
+
body,
|
|
211
|
+
bodyAssets: Object.freeze([]),
|
|
212
|
+
filePath: reference,
|
|
213
|
+
words: countWords(body),
|
|
214
|
+
};
|
|
215
|
+
const transformed = definition.transform
|
|
216
|
+
? await definition.transform(baseEntry)
|
|
217
|
+
: baseEntry.data;
|
|
218
|
+
entries.push(Object.freeze({
|
|
219
|
+
id: document.id,
|
|
220
|
+
data: freezeContentValue(transformed),
|
|
221
|
+
body,
|
|
222
|
+
bodyAssets: freezeContentValue([]),
|
|
223
|
+
filePath: reference,
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
return entries;
|
|
227
|
+
}
|
|
124
228
|
async function parseContentFile(absoluteFile, relativeFile) {
|
|
125
229
|
const source = await readFile(absoluteFile, "utf8");
|
|
126
230
|
const extension = path.extname(relativeFile).toLowerCase();
|
|
127
231
|
try {
|
|
128
232
|
if (extension === ".md" || extension === ".mdx") {
|
|
129
|
-
|
|
233
|
+
// Check the same delimiter/language that gray-matter will parse, before
|
|
234
|
+
// its default engines can evaluate executable frontmatter.
|
|
235
|
+
const normalizedSource = source.replace(/^\uFEFF/, "");
|
|
236
|
+
if (normalizedSource.startsWith("---") && normalizedSource[3] !== "-") {
|
|
237
|
+
const language = matter.language(normalizedSource).name.toLowerCase();
|
|
238
|
+
if (language && language !== "yaml" && language !== "yml" && language !== "json") {
|
|
239
|
+
throw new Error("Only YAML and JSON frontmatter are supported");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Explicit options also avoid gray-matter's process-global parsed cache.
|
|
243
|
+
const parsed = matter(source, {});
|
|
130
244
|
return { data: parsed.data, body: parsed.content };
|
|
131
245
|
}
|
|
132
246
|
if (extension === ".json") {
|
|
@@ -347,13 +461,14 @@ function freezeContentValue(value, ancestors = new Set()) {
|
|
|
347
461
|
async function writeFileIfChanged(filePath, source) {
|
|
348
462
|
try {
|
|
349
463
|
if ((await readFile(filePath, "utf8")) === source)
|
|
350
|
-
return;
|
|
464
|
+
return false;
|
|
351
465
|
}
|
|
352
466
|
catch (error) {
|
|
353
467
|
if (error.code !== "ENOENT")
|
|
354
468
|
throw error;
|
|
355
469
|
}
|
|
356
470
|
await writeFile(filePath, source, "utf8");
|
|
471
|
+
return true;
|
|
357
472
|
}
|
|
358
473
|
function errorMessage(error) {
|
|
359
474
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-assets.d.ts","sourceRoot":"","sources":["../src/markdown-assets.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA8BpD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"markdown-assets.d.ts","sourceRoot":"","sources":["../src/markdown-assets.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA8BpD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAAE,CAAC,CAkDjE"}
|
package/dist/markdown-assets.js
CHANGED
|
@@ -6,7 +6,20 @@ export async function resolveMarkdownAssets(body, context) {
|
|
|
6
6
|
return { body, assets: Object.freeze([]) };
|
|
7
7
|
let tree;
|
|
8
8
|
try {
|
|
9
|
-
|
|
9
|
+
if (context.sourceFile.toLowerCase().endsWith(".mdx")) {
|
|
10
|
+
// Parse MDX without evaluating it. Only Markdown children own assets.
|
|
11
|
+
const [{ mdxFromMarkdown }, { mdxjs }] = await Promise.all([
|
|
12
|
+
import("mdast-util-mdx"),
|
|
13
|
+
import("micromark-extension-mdxjs"),
|
|
14
|
+
]);
|
|
15
|
+
tree = fromMarkdown(body, {
|
|
16
|
+
extensions: [mdxjs()],
|
|
17
|
+
mdastExtensions: [mdxFromMarkdown()],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
tree = fromMarkdown(body);
|
|
22
|
+
}
|
|
10
23
|
}
|
|
11
24
|
catch (error) {
|
|
12
25
|
throw new Error(`[farm:content] Could not inspect Markdown assets in ${context.sourceFile}: ${errorMessage(error)}`, { cause: error });
|
|
@@ -39,7 +52,9 @@ function collectCandidates(tree) {
|
|
|
39
52
|
const references = new Map();
|
|
40
53
|
visit(tree, (node) => {
|
|
41
54
|
if (node.type === "definition" && node.identifier) {
|
|
42
|
-
|
|
55
|
+
// CommonMark resolves duplicate reference labels to their first definition.
|
|
56
|
+
if (!definitions.has(node.identifier))
|
|
57
|
+
definitions.set(node.identifier, node);
|
|
43
58
|
return;
|
|
44
59
|
}
|
|
45
60
|
if (node.type === "imageReference" && node.identifier) {
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import type { ContentEntry } from "./types.js";
|
|
1
|
+
import type { ContentEntry, ContentRemoteSource, ContentSchema } from "./types.js";
|
|
2
2
|
type RuntimeCollections = Readonly<Record<string, readonly ContentEntry<any>[]>>;
|
|
3
3
|
type EncodedContentValue = null | boolean | number | string | ["undefined"] | ["date", string] | ["bigint", string] | ["array", EncodedContentValue[]] | ["object", Array<[string, EncodedContentValue]>];
|
|
4
4
|
export declare function decodeContentValue(value: EncodedContentValue, assetUrls?: Readonly<Record<string, string>>): any;
|
|
5
|
+
/**
|
|
6
|
+
* Live write runtime, shared between the config-time plugin and the generated
|
|
7
|
+
* server module through globalThis so bundle-copy duplication cannot split it.
|
|
8
|
+
* The plugin registers each collection's source and schema when the factory
|
|
9
|
+
* runs; the generated module's `collections` handle reads them for writes.
|
|
10
|
+
*/
|
|
11
|
+
interface ContentWriteRegistration {
|
|
12
|
+
source?: ContentRemoteSource;
|
|
13
|
+
schema?: ContentSchema<any>;
|
|
14
|
+
}
|
|
15
|
+
export declare function registerContentWriteRuntime(registrations: Record<string, ContentWriteRegistration>): () => void;
|
|
16
|
+
export declare function setContentAfterWrite(afterWrite: (() => Promise<void>) | undefined): () => void;
|
|
5
17
|
export declare function createContentRuntime(collections: RuntimeCollections): Readonly<{
|
|
6
18
|
getCollection: (name: string) => Promise<readonly ContentEntry<any>[]>;
|
|
7
19
|
getEntry: (name: string, id: string) => Promise<ContentEntry<any> | undefined>;
|
|
8
20
|
getEntryOrThrow: (name: string, id: string) => Promise<ContentEntry<any>>;
|
|
21
|
+
collections: Record<string, unknown>;
|
|
9
22
|
}>;
|
|
10
23
|
export {};
|
|
11
24
|
//# sourceMappingURL=runtime.d.ts.map
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnF,KAAK,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAEjF,KAAK,mBAAmB,GACpB,IAAI,GACJ,OAAO,GACP,MAAM,GACN,MAAM,GACN,CAAC,WAAW,CAAC,GACb,CAAC,MAAM,EAAE,MAAM,CAAC,GAChB,CAAC,QAAQ,EAAE,MAAM,CAAC,GAClB,CAAC,OAAO,EAAE,mBAAmB,EAAE,CAAC,GAChC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAErD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,mBAAmB,EAC1B,SAAS,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAC/C,GAAG,CAwBL;AAUD;;;;;GAKG;AACH,UAAU,wBAAwB;IAChC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,MAAM,CAAC,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;CAC7B;AA2BD,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,GACtD,MAAM,IAAI,CAiBZ;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,IAAI,CAS9F;AA6DD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,kBAAkB;0BAkB/B,MAAM;qBACX,MAAM,MAAM,MAAM;4BAQX,MAAM,MAAM,MAAM;;GAwExD"}
|
package/dist/runtime.js
CHANGED
|
@@ -34,6 +34,100 @@ function replaceAssetUrls(value, assetUrls) {
|
|
|
34
34
|
}
|
|
35
35
|
return result;
|
|
36
36
|
}
|
|
37
|
+
const WRITE_RUNTIME_KEY = "__FARM_CONTENT_WRITE_RUNTIME__";
|
|
38
|
+
function writeRuntime() {
|
|
39
|
+
const scope = globalThis;
|
|
40
|
+
return (scope[WRITE_RUNTIME_KEY] ??= { registrations: Object.create(null) });
|
|
41
|
+
}
|
|
42
|
+
const RESERVED_REGISTRATION_NAMES = new Set(["__proto__", "constructor", "prototype"]);
|
|
43
|
+
const PROTOCOL_PROBE_KEYS = new Set([
|
|
44
|
+
"then",
|
|
45
|
+
"toJSON",
|
|
46
|
+
"constructor",
|
|
47
|
+
"$$typeof",
|
|
48
|
+
"nodeType",
|
|
49
|
+
"asymmetricMatch",
|
|
50
|
+
"@@__IMMUTABLE_ITERABLE__@@",
|
|
51
|
+
]);
|
|
52
|
+
export function registerContentWriteRuntime(registrations) {
|
|
53
|
+
const target = writeRuntime().registrations;
|
|
54
|
+
const names = [];
|
|
55
|
+
// Own keys assigned one by one onto a null-prototype record: a collection
|
|
56
|
+
// literally named __proto__ must become data, never the record's prototype.
|
|
57
|
+
for (const name of Object.keys(registrations)) {
|
|
58
|
+
if (RESERVED_REGISTRATION_NAMES.has(name)) {
|
|
59
|
+
throw new Error(`[farm:content] Collection name ${JSON.stringify(name)} is reserved`);
|
|
60
|
+
}
|
|
61
|
+
target[name] = registrations[name];
|
|
62
|
+
names.push(name);
|
|
63
|
+
}
|
|
64
|
+
return () => {
|
|
65
|
+
for (const name of names) {
|
|
66
|
+
if (target[name] === registrations[name])
|
|
67
|
+
delete target[name];
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function setContentAfterWrite(afterWrite) {
|
|
72
|
+
const state = writeRuntime();
|
|
73
|
+
state.afterWrite = afterWrite;
|
|
74
|
+
// Vite's restart creates the new server (registering its hook) BEFORE it
|
|
75
|
+
// closes the old one, so an unconditional clear in the old server's close
|
|
76
|
+
// handler would disarm the replacement. Only the current owner may clear.
|
|
77
|
+
return () => {
|
|
78
|
+
if (state.afterWrite === afterWrite)
|
|
79
|
+
state.afterWrite = undefined;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function requireWritableSource(name, verb) {
|
|
83
|
+
const registration = writeRuntime().registrations[name];
|
|
84
|
+
if (!registration?.source) {
|
|
85
|
+
throw new Error(`[farm:content] Collection "${name}" is read-only: only a remote() source can write. ` +
|
|
86
|
+
"Local file collections are edited on disk.");
|
|
87
|
+
}
|
|
88
|
+
const callback = registration.source[verb];
|
|
89
|
+
if (!callback) {
|
|
90
|
+
throw new Error(`[farm:content] Collection "${name}": source ${JSON.stringify(registration.source.name)} ` +
|
|
91
|
+
`does not implement ${verb}(). Add the callback to the source, or perform the write ` +
|
|
92
|
+
"with the provider's own SDK in a server function.");
|
|
93
|
+
}
|
|
94
|
+
return { callback: callback.bind(registration.source), registration };
|
|
95
|
+
}
|
|
96
|
+
async function validateAgainstSchema(schema, data) {
|
|
97
|
+
if (!schema)
|
|
98
|
+
return;
|
|
99
|
+
if (typeof schema.parseAsync === "function") {
|
|
100
|
+
await schema.parseAsync(data);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (typeof schema.parse === "function") {
|
|
104
|
+
schema.parse(data);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const standard = schema["~standard"];
|
|
108
|
+
if (standard) {
|
|
109
|
+
const result = await standard.validate(data);
|
|
110
|
+
if (result && "issues" in result && result.issues?.length) {
|
|
111
|
+
throw new Error(`[farm:content] The written document failed validation: ${result.issues
|
|
112
|
+
.map((issue) => issue.message)
|
|
113
|
+
.join("; ")}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function settleWrite(result) {
|
|
118
|
+
// The CMS accepted the write; refresh the local snapshot where a dev server
|
|
119
|
+
// is present. In production the snapshot is bundled, so the change becomes
|
|
120
|
+
// visible on the next build - pair writes with a deploy hook. A failing
|
|
121
|
+
// refresh must not report the persisted write as failed: the dev pipeline
|
|
122
|
+
// surfaces its own errors.
|
|
123
|
+
try {
|
|
124
|
+
await writeRuntime().afterWrite?.();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// reported by the dev pipeline's own channel
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
37
131
|
export function createContentRuntime(collections) {
|
|
38
132
|
const indexes = new Map();
|
|
39
133
|
function collection(name) {
|
|
@@ -62,5 +156,67 @@ export function createContentRuntime(collections) {
|
|
|
62
156
|
}
|
|
63
157
|
return entry;
|
|
64
158
|
};
|
|
65
|
-
|
|
159
|
+
const handles = new Map();
|
|
160
|
+
const collectionHandle = (name) => {
|
|
161
|
+
let handle = handles.get(name);
|
|
162
|
+
if (!handle) {
|
|
163
|
+
collection(name); // unknown names fail here with the available list
|
|
164
|
+
handle = Object.freeze({
|
|
165
|
+
all: () => getCollection(name),
|
|
166
|
+
get: (id) => getEntry(name, id),
|
|
167
|
+
getOrThrow: (id) => getEntryOrThrow(name, id),
|
|
168
|
+
create: async (input) => {
|
|
169
|
+
const { callback, registration } = requireWritableSource(name, "create");
|
|
170
|
+
const document = await callback(input);
|
|
171
|
+
// The source of truth answered; make sure what it stored still
|
|
172
|
+
// satisfies the collection's schema before anyone trusts it.
|
|
173
|
+
// The CMS changed either way, so the snapshot refreshes even when
|
|
174
|
+
// validation rejects - readers should see the CMS's real state.
|
|
175
|
+
try {
|
|
176
|
+
await validateAgainstSchema(registration.schema, document.data);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
await settleWrite(undefined);
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
return settleWrite(document);
|
|
183
|
+
},
|
|
184
|
+
update: async (id, patch) => {
|
|
185
|
+
const { callback, registration } = requireWritableSource(name, "update");
|
|
186
|
+
const document = await callback(id, patch);
|
|
187
|
+
try {
|
|
188
|
+
await validateAgainstSchema(registration.schema, document.data);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
await settleWrite(undefined);
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
return settleWrite(document);
|
|
195
|
+
},
|
|
196
|
+
delete: async (id) => {
|
|
197
|
+
const { callback } = requireWritableSource(name, "delete");
|
|
198
|
+
await callback(id);
|
|
199
|
+
await settleWrite(undefined);
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
handles.set(name, handle);
|
|
203
|
+
}
|
|
204
|
+
return handle;
|
|
205
|
+
};
|
|
206
|
+
// Runtimes probe objects with protocol keys: await checks `then`,
|
|
207
|
+
// JSON.stringify checks `toJSON`, test matchers poke a few more. Those
|
|
208
|
+
// probes must see undefined; only a real unknown collection name throws.
|
|
209
|
+
const collectionsProxy = new Proxy(Object.create(null), {
|
|
210
|
+
get(_target, property) {
|
|
211
|
+
if (typeof property !== "string")
|
|
212
|
+
return undefined;
|
|
213
|
+
if (!(property in collections) && PROTOCOL_PROBE_KEYS.has(property))
|
|
214
|
+
return undefined;
|
|
215
|
+
return collectionHandle(property);
|
|
216
|
+
},
|
|
217
|
+
has: (_target, property) => typeof property === "string" && property in collections,
|
|
218
|
+
ownKeys: () => Object.keys(collections),
|
|
219
|
+
getOwnPropertyDescriptor: () => ({ enumerable: true, configurable: true }),
|
|
220
|
+
});
|
|
221
|
+
return Object.freeze({ getCollection, getEntry, getEntryOrThrow, collections: collectionsProxy });
|
|
66
222
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import type { AppContentEntry, ContentCollectionName } from "./types.js";
|
|
1
|
+
import type { AppContentEntry, ContentCollectionHandle, ContentCollectionName } from "./types.js";
|
|
2
2
|
/** Return every entry in a configured collection, ordered by entry ID. */
|
|
3
3
|
export declare function getCollection<TName extends ContentCollectionName>(_name: TName): Promise<readonly AppContentEntry<TName>[]>;
|
|
4
4
|
/** Find one entry by its path-derived ID. */
|
|
5
5
|
export declare function getEntry<TName extends ContentCollectionName>(_name: TName, _id: string): Promise<AppContentEntry<TName> | undefined>;
|
|
6
6
|
/** Find one entry by ID or throw an actionable error. */
|
|
7
7
|
export declare function getEntryOrThrow<TName extends ContentCollectionName>(_name: TName, _id: string): Promise<AppContentEntry<TName>>;
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The per-collection handle: reads from the validated snapshot, writes
|
|
10
|
+
* through the source's create/update/delete callbacks. Development rebuilds
|
|
11
|
+
* the snapshot after a write; production serves the bundled snapshot until
|
|
12
|
+
* the next build, so pair writes with a deploy hook.
|
|
13
|
+
*/
|
|
14
|
+
export declare const collections: {
|
|
15
|
+
readonly [TName in ContentCollectionName]: ContentCollectionHandle<AppContentEntry<TName>["data"]>;
|
|
16
|
+
};
|
|
17
|
+
export type { AppContentEntry, AppContentRegistry, ContentCollectionHandle, ContentCollectionName, ContentEntry, } from "./types.js";
|
|
9
18
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAQlG,0EAA0E;AAC1E,wBAAsB,aAAa,CAAC,KAAK,SAAS,qBAAqB,EACrE,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,SAAS,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAE5C;AAED,6CAA6C;AAC7C,wBAAsB,QAAQ,CAAC,KAAK,SAAS,qBAAqB,EAChE,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC,CAE7C;AAED,yDAAyD;AACzD,wBAAsB,eAAe,CAAC,KAAK,SAAS,qBAAqB,EACvE,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAEjC;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE;IACxB,QAAQ,EAAE,KAAK,IAAI,qBAAqB,GAAG,uBAAuB,CAChE,eAAe,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAC/B;CAC+D,CAAC;AAEnE,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,YAAY,GACb,MAAM,YAAY,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -13,3 +13,10 @@ export async function getEntry(_name, _id) {
|
|
|
13
13
|
export async function getEntryOrThrow(_name, _id) {
|
|
14
14
|
return missingPlugin();
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* The per-collection handle: reads from the validated snapshot, writes
|
|
18
|
+
* through the source's create/update/delete callbacks. Development rebuilds
|
|
19
|
+
* the snapshot after a write; production serves the bundled snapshot until
|
|
20
|
+
* the next build, so pair writes with a deploy hook.
|
|
21
|
+
*/
|
|
22
|
+
export const collections = new Proxy(Object.create(null), { get: () => missingPlugin() });
|
package/dist/types.d.ts
CHANGED
|
@@ -6,6 +6,48 @@ export interface ContentFileSource {
|
|
|
6
6
|
readonly ignore: readonly string[];
|
|
7
7
|
readonly base?: string;
|
|
8
8
|
}
|
|
9
|
+
/** One document delivered by a remote source, before validation. */
|
|
10
|
+
export interface ContentRemoteDocument {
|
|
11
|
+
/** Stable route-friendly identifier, unique within the collection. */
|
|
12
|
+
readonly id: string;
|
|
13
|
+
/** Structured data; goes through the collection schema like frontmatter. */
|
|
14
|
+
readonly data: Record<string, unknown>;
|
|
15
|
+
/** Optional Markdown body. Defaults to an empty string. */
|
|
16
|
+
readonly body?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A source that fetches documents instead of reading local files - a CMS, an
|
|
20
|
+
* API, a database. Fetched while the configuration loads, validated through
|
|
21
|
+
* the same schema pipeline as files, and bundled into production output as
|
|
22
|
+
* the same build-time snapshot. The shape is structural on purpose: an
|
|
23
|
+
* integration can return it without importing this package at runtime.
|
|
24
|
+
*/
|
|
25
|
+
/** The document fields a write sends to a remote source. */
|
|
26
|
+
export interface ContentRemoteWrite {
|
|
27
|
+
readonly data: Record<string, unknown>;
|
|
28
|
+
readonly body?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ContentRemoteSource {
|
|
31
|
+
readonly kind: "remote";
|
|
32
|
+
/** Names the source in error messages, e.g. "sanity:posts". */
|
|
33
|
+
readonly name: string;
|
|
34
|
+
fetch(): Promise<readonly ContentRemoteDocument[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Development only: re-fetch this often (milliseconds) and reload when the
|
|
37
|
+
* documents changed. Leave unset for fetch-on-rebuild only.
|
|
38
|
+
*/
|
|
39
|
+
readonly refreshInterval?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Optional write callbacks, mirroring fetch: the callback is the entire
|
|
42
|
+
* provider-specific surface. A source that omits them is read-only, and
|
|
43
|
+
* `collections.<name>.create/update/delete` reports which verb is missing.
|
|
44
|
+
* The CMS stays the source of truth: each returns the confirmed document.
|
|
45
|
+
*/
|
|
46
|
+
create?(input: ContentRemoteWrite): Promise<ContentRemoteDocument>;
|
|
47
|
+
update?(id: string, patch: Partial<ContentRemoteWrite>): Promise<ContentRemoteDocument>;
|
|
48
|
+
delete?(id: string): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
export type ContentSource = ContentFileSource | ContentRemoteSource;
|
|
9
51
|
export interface ContentSchemaIssue {
|
|
10
52
|
readonly message: string;
|
|
11
53
|
readonly path?: readonly unknown[];
|
|
@@ -98,12 +140,33 @@ export interface ContentEntry<TData = Record<string, unknown>> {
|
|
|
98
140
|
/** Project-relative source path with POSIX separators. */
|
|
99
141
|
readonly filePath: string;
|
|
100
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* The server-side handle for one collection: reads from the validated
|
|
145
|
+
* snapshot, writes through the source's callbacks. In development a write
|
|
146
|
+
* rebuilds the snapshot immediately; in production the write lands in the
|
|
147
|
+
* CMS and the running deployment serves the bundled snapshot until the
|
|
148
|
+
* next build, so pair writes with a deploy hook.
|
|
149
|
+
*/
|
|
150
|
+
export interface ContentCollectionHandle<TData = Record<string, unknown>> {
|
|
151
|
+
all(): Promise<readonly ContentEntry<TData>[]>;
|
|
152
|
+
get(id: string): Promise<ContentEntry<TData> | undefined>;
|
|
153
|
+
getOrThrow(id: string): Promise<ContentEntry<TData>>;
|
|
154
|
+
create(input: {
|
|
155
|
+
data: Record<string, unknown>;
|
|
156
|
+
body?: string;
|
|
157
|
+
}): Promise<ContentRemoteDocument>;
|
|
158
|
+
update(id: string, patch: {
|
|
159
|
+
data?: Partial<TData> & Record<string, unknown>;
|
|
160
|
+
body?: string;
|
|
161
|
+
}): Promise<ContentRemoteDocument>;
|
|
162
|
+
delete(id: string): Promise<void>;
|
|
163
|
+
}
|
|
101
164
|
export interface ContentTransformContext<TData> extends ContentEntry<TData> {
|
|
102
165
|
/** Number of whitespace-delimited words in body. */
|
|
103
166
|
readonly words: number;
|
|
104
167
|
}
|
|
105
168
|
export interface ContentCollectionInput<TSchema extends ContentSchema<any>, TTransformed = InferContentSchema<TSchema>, TAssets extends ContentAssetsInput = undefined> {
|
|
106
|
-
source:
|
|
169
|
+
source: ContentSource;
|
|
107
170
|
schema: TSchema;
|
|
108
171
|
/**
|
|
109
172
|
* Process relative assets. `true` manages Markdown/MDX body references. An object also declares
|
|
@@ -114,7 +177,7 @@ export interface ContentCollectionInput<TSchema extends ContentSchema<any>, TTra
|
|
|
114
177
|
transform?: (entry: ContentTransformContext<ContentDataWithAssets<InferContentSchema<TSchema>, TAssets>>) => TTransformed | Promise<TTransformed>;
|
|
115
178
|
}
|
|
116
179
|
export interface ContentCollection<TData = unknown, TSchemaData = TData> {
|
|
117
|
-
readonly source:
|
|
180
|
+
readonly source: ContentSource;
|
|
118
181
|
readonly schema: ContentSchema<TSchemaData>;
|
|
119
182
|
readonly assets?: true | ContentAssetFields;
|
|
120
183
|
readonly transform?: (entry: ContentTransformContext<any>) => unknown | Promise<unknown>;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farm.js/content",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
4
|
"description": "Typed local content collections for Farm.js applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"content",
|
|
@@ -42,14 +42,17 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"fast-glob": "^3.3.2",
|
|
44
44
|
"gray-matter": "^4.0.3",
|
|
45
|
-
"image-size": "^2.0.
|
|
45
|
+
"image-size": "^2.0.4",
|
|
46
46
|
"mdast-util-from-markdown": "^2.0.2",
|
|
47
|
+
"mdast-util-mdx": "^3.0.0",
|
|
48
|
+
"micromark-extension-mdxjs": "^3.0.0",
|
|
47
49
|
"yaml": "^2.8.0",
|
|
48
|
-
"@farm.js/core": "0.1.0
|
|
50
|
+
"@farm.js/core": "0.1.0"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
51
53
|
"@types/node": "^20.10.5",
|
|
52
54
|
"typescript": "^5.3.3",
|
|
55
|
+
"vite": "5.4.20",
|
|
53
56
|
"vitest": "^3.2.7"
|
|
54
57
|
},
|
|
55
58
|
"engines": {
|