@farm.js/content 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +96 -0
- package/dist/assets.d.ts +28 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.js +348 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +135 -0
- package/dist/loader.d.ts +13 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +360 -0
- package/dist/markdown-assets.d.ts +7 -0
- package/dist/markdown-assets.d.ts.map +1 -0
- package/dist/markdown-assets.js +197 -0
- package/dist/runtime.d.ts +11 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +66 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +15 -0
- package/dist/types.d.ts +149 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
2
|
+
import { resolveContentAsset } from "./assets.js";
|
|
3
|
+
const CONTENT_LINK_EXTENSIONS = new Set([".md", ".mdx"]);
|
|
4
|
+
export async function resolveMarkdownAssets(body, context) {
|
|
5
|
+
if (!body)
|
|
6
|
+
return { body, assets: Object.freeze([]) };
|
|
7
|
+
let tree;
|
|
8
|
+
try {
|
|
9
|
+
tree = fromMarkdown(body);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
throw new Error(`[farm:content] Could not inspect Markdown assets in ${context.sourceFile}: ${errorMessage(error)}`, { cause: error });
|
|
13
|
+
}
|
|
14
|
+
const candidates = collectCandidates(tree);
|
|
15
|
+
const replacements = [];
|
|
16
|
+
for (const candidate of candidates) {
|
|
17
|
+
const range = findDestinationRange(body, candidate);
|
|
18
|
+
if (!range) {
|
|
19
|
+
throw new Error(`[farm:content] Could not locate the asset destination at ${context.sourceFile}:${candidate.position.start.line}:${candidate.position.start.column}`);
|
|
20
|
+
}
|
|
21
|
+
const resolved = await resolveContentAsset(candidate.url, candidate.kind, context, [
|
|
22
|
+
`body:${candidate.position.start.line}:${candidate.position.start.column}`,
|
|
23
|
+
]);
|
|
24
|
+
replacements.push({ ...candidate, ...range, asset: resolved });
|
|
25
|
+
}
|
|
26
|
+
let output = body;
|
|
27
|
+
for (const replacement of [...replacements].sort((left, right) => right.start - left.start)) {
|
|
28
|
+
output = `${output.slice(0, replacement.start)}${replacement.asset.src}${output.slice(replacement.end)}`;
|
|
29
|
+
}
|
|
30
|
+
const uniqueAssets = new Map();
|
|
31
|
+
for (const replacement of replacements) {
|
|
32
|
+
uniqueAssets.set(`${replacement.asset.kind}:${replacement.asset.src}`, replacement.asset);
|
|
33
|
+
}
|
|
34
|
+
return { body: output, assets: Object.freeze([...uniqueAssets.values()]) };
|
|
35
|
+
}
|
|
36
|
+
function collectCandidates(tree) {
|
|
37
|
+
const candidates = [];
|
|
38
|
+
const definitions = new Map();
|
|
39
|
+
const references = new Map();
|
|
40
|
+
visit(tree, (node) => {
|
|
41
|
+
if (node.type === "definition" && node.identifier) {
|
|
42
|
+
definitions.set(node.identifier, node);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (node.type === "imageReference" && node.identifier) {
|
|
46
|
+
references.set(node.identifier, "image");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (node.type === "linkReference" && node.identifier && !references.has(node.identifier)) {
|
|
50
|
+
references.set(node.identifier, "file");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (node.type !== "image" && node.type !== "link")
|
|
54
|
+
return;
|
|
55
|
+
const kind = node.type === "image" ? "image" : "file";
|
|
56
|
+
if (!isManagedReference(node.url, kind) || !node.position)
|
|
57
|
+
return;
|
|
58
|
+
candidates.push({ kind, url: node.url, position: node.position, definition: false });
|
|
59
|
+
});
|
|
60
|
+
for (const [identifier, kind] of references) {
|
|
61
|
+
const definition = definitions.get(identifier);
|
|
62
|
+
if (!definition?.position || !isManagedReference(definition.url, kind))
|
|
63
|
+
continue;
|
|
64
|
+
candidates.push({
|
|
65
|
+
kind,
|
|
66
|
+
url: definition.url,
|
|
67
|
+
position: definition.position,
|
|
68
|
+
definition: true,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return candidates.sort((left, right) => (left.position.start.offset ?? 0) - (right.position.start.offset ?? 0));
|
|
72
|
+
}
|
|
73
|
+
function visit(node, callback) {
|
|
74
|
+
callback(node);
|
|
75
|
+
for (const child of node.children ?? [])
|
|
76
|
+
visit(child, callback);
|
|
77
|
+
}
|
|
78
|
+
function isManagedReference(value, kind) {
|
|
79
|
+
if (!value || !isRelativeReference(value))
|
|
80
|
+
return false;
|
|
81
|
+
if (kind === "image")
|
|
82
|
+
return true;
|
|
83
|
+
const pathname = value.split(/[?#]/, 1)[0];
|
|
84
|
+
const slash = pathname.lastIndexOf("/");
|
|
85
|
+
const dot = pathname.lastIndexOf(".");
|
|
86
|
+
if (dot <= slash)
|
|
87
|
+
return false;
|
|
88
|
+
return !CONTENT_LINK_EXTENSIONS.has(pathname.slice(dot).toLowerCase());
|
|
89
|
+
}
|
|
90
|
+
function isRelativeReference(value) {
|
|
91
|
+
return !(value.startsWith("/") ||
|
|
92
|
+
value.startsWith("\\") ||
|
|
93
|
+
value.startsWith("#") ||
|
|
94
|
+
value.startsWith("?") ||
|
|
95
|
+
/^[A-Za-z][A-Za-z\d+.-]*:/.test(value));
|
|
96
|
+
}
|
|
97
|
+
function findDestinationRange(body, candidate) {
|
|
98
|
+
const nodeStart = candidate.position.start.offset;
|
|
99
|
+
const nodeEnd = candidate.position.end.offset;
|
|
100
|
+
if (nodeStart === undefined || nodeEnd === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
const source = body.slice(nodeStart, nodeEnd);
|
|
103
|
+
const relative = candidate.definition
|
|
104
|
+
? findDefinitionDestination(source)
|
|
105
|
+
: findInlineDestination(source);
|
|
106
|
+
if (!relative)
|
|
107
|
+
return undefined;
|
|
108
|
+
return { start: nodeStart + relative.start, end: nodeStart + relative.end };
|
|
109
|
+
}
|
|
110
|
+
function findInlineDestination(source) {
|
|
111
|
+
let index = source.startsWith("![") ? 2 : source.startsWith("[") ? 1 : -1;
|
|
112
|
+
if (index < 0)
|
|
113
|
+
return undefined;
|
|
114
|
+
let brackets = 1;
|
|
115
|
+
while (index < source.length && brackets > 0) {
|
|
116
|
+
const character = source[index];
|
|
117
|
+
if (character === "\\") {
|
|
118
|
+
index += 2;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (character === "[")
|
|
122
|
+
brackets += 1;
|
|
123
|
+
else if (character === "]")
|
|
124
|
+
brackets -= 1;
|
|
125
|
+
index += 1;
|
|
126
|
+
}
|
|
127
|
+
if (brackets !== 0 || source[index] !== "(")
|
|
128
|
+
return undefined;
|
|
129
|
+
index += 1;
|
|
130
|
+
while (/\s/.test(source[index] ?? ""))
|
|
131
|
+
index += 1;
|
|
132
|
+
return readDestination(source, index, true);
|
|
133
|
+
}
|
|
134
|
+
function findDefinitionDestination(source) {
|
|
135
|
+
let index = 1;
|
|
136
|
+
while (index < source.length) {
|
|
137
|
+
if (source[index] === "\\") {
|
|
138
|
+
index += 2;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (source[index] === "]")
|
|
142
|
+
break;
|
|
143
|
+
index += 1;
|
|
144
|
+
}
|
|
145
|
+
if (source[index] !== "]")
|
|
146
|
+
return undefined;
|
|
147
|
+
index += 1;
|
|
148
|
+
while (/\s/.test(source[index] ?? ""))
|
|
149
|
+
index += 1;
|
|
150
|
+
if (source[index] !== ":")
|
|
151
|
+
return undefined;
|
|
152
|
+
index += 1;
|
|
153
|
+
while (/\s/.test(source[index] ?? ""))
|
|
154
|
+
index += 1;
|
|
155
|
+
return readDestination(source, index, false);
|
|
156
|
+
}
|
|
157
|
+
function readDestination(source, start, inline) {
|
|
158
|
+
if (source[start] === "<") {
|
|
159
|
+
let index = start + 1;
|
|
160
|
+
while (index < source.length) {
|
|
161
|
+
if (source[index] === "\\") {
|
|
162
|
+
index += 2;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (source[index] === ">")
|
|
166
|
+
return { start: start + 1, end: index };
|
|
167
|
+
index += 1;
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
let index = start;
|
|
172
|
+
let parentheses = 0;
|
|
173
|
+
while (index < source.length) {
|
|
174
|
+
const character = source[index];
|
|
175
|
+
if (character === "\\") {
|
|
176
|
+
index += 2;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (character === "(") {
|
|
180
|
+
parentheses += 1;
|
|
181
|
+
}
|
|
182
|
+
else if (character === ")") {
|
|
183
|
+
if (inline && parentheses === 0)
|
|
184
|
+
break;
|
|
185
|
+
if (parentheses > 0)
|
|
186
|
+
parentheses -= 1;
|
|
187
|
+
}
|
|
188
|
+
else if (/\s/.test(character) && parentheses === 0) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
index += 1;
|
|
192
|
+
}
|
|
193
|
+
return index > start ? { start, end: index } : undefined;
|
|
194
|
+
}
|
|
195
|
+
function errorMessage(error) {
|
|
196
|
+
return error instanceof Error ? error.message : String(error);
|
|
197
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ContentEntry } from "./types.js";
|
|
2
|
+
type RuntimeCollections = Readonly<Record<string, readonly ContentEntry<any>[]>>;
|
|
3
|
+
type EncodedContentValue = null | boolean | number | string | ["undefined"] | ["date", string] | ["bigint", string] | ["array", EncodedContentValue[]] | ["object", Array<[string, EncodedContentValue]>];
|
|
4
|
+
export declare function decodeContentValue(value: EncodedContentValue, assetUrls?: Readonly<Record<string, string>>): any;
|
|
5
|
+
export declare function createContentRuntime(collections: RuntimeCollections): Readonly<{
|
|
6
|
+
getCollection: (name: string) => Promise<readonly ContentEntry<any>[]>;
|
|
7
|
+
getEntry: (name: string, id: string) => Promise<ContentEntry<any> | undefined>;
|
|
8
|
+
getEntryOrThrow: (name: string, id: string) => Promise<ContentEntry<any>>;
|
|
9
|
+
}>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,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,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,kBAAkB;0BAkB/B,MAAM;qBACX,MAAM,MAAM,MAAM;4BAQX,MAAM,MAAM,MAAM;GASxD"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export function decodeContentValue(value, assetUrls = {}) {
|
|
2
|
+
if (typeof value === "string")
|
|
3
|
+
return replaceAssetUrls(value, assetUrls);
|
|
4
|
+
if (!Array.isArray(value))
|
|
5
|
+
return value;
|
|
6
|
+
const [kind, payload] = value;
|
|
7
|
+
if (kind === "undefined")
|
|
8
|
+
return undefined;
|
|
9
|
+
if (kind === "date")
|
|
10
|
+
return new Date(payload);
|
|
11
|
+
if (kind === "bigint")
|
|
12
|
+
return BigInt(payload);
|
|
13
|
+
if (kind === "array")
|
|
14
|
+
return Object.freeze(payload.map((entry) => decodeContentValue(entry, assetUrls)));
|
|
15
|
+
if (kind === "object") {
|
|
16
|
+
const result = Object.create(null);
|
|
17
|
+
for (const [key, entry] of payload) {
|
|
18
|
+
Object.defineProperty(result, key, {
|
|
19
|
+
value: decodeContentValue(entry, assetUrls),
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: false,
|
|
22
|
+
writable: false,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return Object.freeze(result);
|
|
26
|
+
}
|
|
27
|
+
throw new TypeError(`[farm:content] Unknown encoded content value: ${String(kind)}`);
|
|
28
|
+
}
|
|
29
|
+
function replaceAssetUrls(value, assetUrls) {
|
|
30
|
+
let result = value;
|
|
31
|
+
for (const [token, url] of Object.entries(assetUrls)) {
|
|
32
|
+
if (result.includes(token))
|
|
33
|
+
result = result.split(token).join(url);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
export function createContentRuntime(collections) {
|
|
38
|
+
const indexes = new Map();
|
|
39
|
+
function collection(name) {
|
|
40
|
+
const entries = collections[name];
|
|
41
|
+
if (!entries) {
|
|
42
|
+
const available = Object.keys(collections);
|
|
43
|
+
throw new Error(`[farm:content] Unknown collection "${name}".${available.length
|
|
44
|
+
? ` Available collections: ${available.join(", ")}`
|
|
45
|
+
: " No collections are configured."}`);
|
|
46
|
+
}
|
|
47
|
+
return entries;
|
|
48
|
+
}
|
|
49
|
+
const getCollection = async (name) => collection(name);
|
|
50
|
+
const getEntry = async (name, id) => {
|
|
51
|
+
let index = indexes.get(name);
|
|
52
|
+
if (!index) {
|
|
53
|
+
index = new Map(collection(name).map((entry) => [entry.id, entry]));
|
|
54
|
+
indexes.set(name, index);
|
|
55
|
+
}
|
|
56
|
+
return index.get(id);
|
|
57
|
+
};
|
|
58
|
+
const getEntryOrThrow = async (name, id) => {
|
|
59
|
+
const entry = await getEntry(name, id);
|
|
60
|
+
if (!entry) {
|
|
61
|
+
throw new Error(`[farm:content] Entry "${id}" was not found in collection "${name}"`);
|
|
62
|
+
}
|
|
63
|
+
return entry;
|
|
64
|
+
};
|
|
65
|
+
return Object.freeze({ getCollection, getEntry, getEntryOrThrow });
|
|
66
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AppContentEntry, ContentCollectionName } from "./types.js";
|
|
2
|
+
/** Return every entry in a configured collection, ordered by entry ID. */
|
|
3
|
+
export declare function getCollection<TName extends ContentCollectionName>(_name: TName): Promise<readonly AppContentEntry<TName>[]>;
|
|
4
|
+
/** Find one entry by its path-derived ID. */
|
|
5
|
+
export declare function getEntry<TName extends ContentCollectionName>(_name: TName, _id: string): Promise<AppContentEntry<TName> | undefined>;
|
|
6
|
+
/** Find one entry by ID or throw an actionable error. */
|
|
7
|
+
export declare function getEntryOrThrow<TName extends ContentCollectionName>(_name: TName, _id: string): Promise<AppContentEntry<TName>>;
|
|
8
|
+
export type { AppContentEntry, AppContentRegistry, ContentCollectionName, ContentEntry, } from "./types.js";
|
|
9
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +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;AAQzE,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,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,YAAY,GACb,MAAM,YAAY,CAAC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function missingPlugin() {
|
|
2
|
+
throw new Error("[farm:content] The generated content runtime is unavailable. Add content({ collections }) to farm.config.ts and run this module through Farm.");
|
|
3
|
+
}
|
|
4
|
+
/** Return every entry in a configured collection, ordered by entry ID. */
|
|
5
|
+
export async function getCollection(_name) {
|
|
6
|
+
return missingPlugin();
|
|
7
|
+
}
|
|
8
|
+
/** Find one entry by its path-derived ID. */
|
|
9
|
+
export async function getEntry(_name, _id) {
|
|
10
|
+
return missingPlugin();
|
|
11
|
+
}
|
|
12
|
+
/** Find one entry by ID or throw an actionable error. */
|
|
13
|
+
export async function getEntryOrThrow(_name, _id) {
|
|
14
|
+
return missingPlugin();
|
|
15
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { FarmPlugin } from "@farm.js/core/plugin";
|
|
2
|
+
export type ContentFileExtension = ".md" | ".mdx" | ".json" | ".yaml" | ".yml";
|
|
3
|
+
export interface ContentFileSource {
|
|
4
|
+
readonly kind: "files";
|
|
5
|
+
readonly patterns: readonly string[];
|
|
6
|
+
readonly ignore: readonly string[];
|
|
7
|
+
readonly base?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ContentSchemaIssue {
|
|
10
|
+
readonly message: string;
|
|
11
|
+
readonly path?: readonly unknown[];
|
|
12
|
+
}
|
|
13
|
+
export interface ContentSchemaResult<TOutput> {
|
|
14
|
+
readonly value?: TOutput;
|
|
15
|
+
readonly issues?: readonly ContentSchemaIssue[];
|
|
16
|
+
}
|
|
17
|
+
export interface ContentSchema<TOutput = unknown> {
|
|
18
|
+
readonly "~standard"?: {
|
|
19
|
+
readonly types?: {
|
|
20
|
+
readonly output: TOutput;
|
|
21
|
+
};
|
|
22
|
+
validate(value: unknown): ContentSchemaResult<TOutput> | Promise<ContentSchemaResult<TOutput>>;
|
|
23
|
+
};
|
|
24
|
+
readonly _output?: TOutput;
|
|
25
|
+
parseAsync?(value: unknown): Promise<TOutput>;
|
|
26
|
+
parse?(value: unknown): TOutput;
|
|
27
|
+
}
|
|
28
|
+
export type InferContentSchema<TSchema> = TSchema extends {
|
|
29
|
+
readonly "~standard": {
|
|
30
|
+
readonly types?: {
|
|
31
|
+
readonly output: infer TOutput;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
} ? TOutput : TSchema extends {
|
|
35
|
+
readonly _output?: infer TOutput;
|
|
36
|
+
} ? TOutput : TSchema extends ContentSchema<infer TOutput> ? TOutput : never;
|
|
37
|
+
export interface ContentFileAsset {
|
|
38
|
+
readonly kind: "file";
|
|
39
|
+
/** Relative path as written in the content source. */
|
|
40
|
+
readonly source: string;
|
|
41
|
+
/** Content-hashed public URL emitted by Farm. */
|
|
42
|
+
readonly src: string;
|
|
43
|
+
/** Original source filename. */
|
|
44
|
+
readonly name: string;
|
|
45
|
+
/** MIME type inferred from the source extension. */
|
|
46
|
+
readonly type: string;
|
|
47
|
+
/** Source size in bytes. */
|
|
48
|
+
readonly bytes: number;
|
|
49
|
+
}
|
|
50
|
+
export interface ContentImageAsset {
|
|
51
|
+
readonly kind: "image";
|
|
52
|
+
/** Relative path as written in the content source. */
|
|
53
|
+
readonly source: string;
|
|
54
|
+
/** Content-hashed public URL emitted by Farm. */
|
|
55
|
+
readonly src: string;
|
|
56
|
+
/** Intrinsic source width in pixels. */
|
|
57
|
+
readonly width: number;
|
|
58
|
+
/** Intrinsic source height in pixels. */
|
|
59
|
+
readonly height: number;
|
|
60
|
+
/** Original source filename. */
|
|
61
|
+
readonly name: string;
|
|
62
|
+
/** MIME type detected from the image contents. */
|
|
63
|
+
readonly type: string;
|
|
64
|
+
/** Source size in bytes. */
|
|
65
|
+
readonly bytes: number;
|
|
66
|
+
}
|
|
67
|
+
export type ContentAssetValue = ContentFileAsset | ContentImageAsset;
|
|
68
|
+
export interface ContentAssetField<TOutput, TInput extends string | readonly string[] = string | readonly string[]> {
|
|
69
|
+
/** @internal Runtime asset declaration consumed by the content loader. */
|
|
70
|
+
readonly __farmContentAsset: {
|
|
71
|
+
readonly kind: ContentAssetValue["kind"];
|
|
72
|
+
readonly multiple: boolean;
|
|
73
|
+
readonly optional: boolean;
|
|
74
|
+
readonly defaultValue?: TInput;
|
|
75
|
+
};
|
|
76
|
+
/** Accept an omitted frontmatter value and return `undefined`. */
|
|
77
|
+
optional(): ContentAssetField<TOutput | undefined, TInput>;
|
|
78
|
+
/** Resolve this relative path when the frontmatter value is omitted. */
|
|
79
|
+
default(value: TInput): ContentAssetField<Exclude<TOutput, undefined>, TInput>;
|
|
80
|
+
}
|
|
81
|
+
export type ContentAssetFields = {
|
|
82
|
+
readonly [key: string]: ContentAssetField<any, any> | ContentAssetFields;
|
|
83
|
+
};
|
|
84
|
+
export type ContentAssetsInput = true | ContentAssetFields | undefined;
|
|
85
|
+
export type InferContentAssetFields<TFields> = TFields extends true | undefined ? {} : TFields extends ContentAssetFields ? {
|
|
86
|
+
readonly [TKey in keyof TFields]: TFields[TKey] extends ContentAssetField<infer TOutput, any> ? TOutput : InferContentAssetFields<TFields[TKey]>;
|
|
87
|
+
} : {};
|
|
88
|
+
export type ContentDataWithAssets<TData, TAssets> = TData & InferContentAssetFields<TAssets>;
|
|
89
|
+
export interface ContentEntry<TData = Record<string, unknown>> {
|
|
90
|
+
/** Stable route-friendly identifier derived from the path relative to the source base. */
|
|
91
|
+
readonly id: string;
|
|
92
|
+
/** Schema-validated frontmatter or structured file data. */
|
|
93
|
+
readonly data: TData;
|
|
94
|
+
/** Markdown or MDX body. Structured JSON and YAML files use an empty string. */
|
|
95
|
+
readonly body: string;
|
|
96
|
+
/** Assets discovered in Markdown syntax, after validation and URL emission. */
|
|
97
|
+
readonly bodyAssets: readonly ContentAssetValue[];
|
|
98
|
+
/** Project-relative source path with POSIX separators. */
|
|
99
|
+
readonly filePath: string;
|
|
100
|
+
}
|
|
101
|
+
export interface ContentTransformContext<TData> extends ContentEntry<TData> {
|
|
102
|
+
/** Number of whitespace-delimited words in body. */
|
|
103
|
+
readonly words: number;
|
|
104
|
+
}
|
|
105
|
+
export interface ContentCollectionInput<TSchema extends ContentSchema<any>, TTransformed = InferContentSchema<TSchema>, TAssets extends ContentAssetsInput = undefined> {
|
|
106
|
+
source: ContentFileSource;
|
|
107
|
+
schema: TSchema;
|
|
108
|
+
/**
|
|
109
|
+
* Process relative assets. `true` manages Markdown/MDX body references. An object also declares
|
|
110
|
+
* typed frontmatter assets and merges their resolved values into `data`.
|
|
111
|
+
*/
|
|
112
|
+
assets?: TAssets;
|
|
113
|
+
/** Change the final data value after validation, for example to add reading time. */
|
|
114
|
+
transform?: (entry: ContentTransformContext<ContentDataWithAssets<InferContentSchema<TSchema>, TAssets>>) => TTransformed | Promise<TTransformed>;
|
|
115
|
+
}
|
|
116
|
+
export interface ContentCollection<TData = unknown, TSchemaData = TData> {
|
|
117
|
+
readonly source: ContentFileSource;
|
|
118
|
+
readonly schema: ContentSchema<TSchemaData>;
|
|
119
|
+
readonly assets?: true | ContentAssetFields;
|
|
120
|
+
readonly transform?: (entry: ContentTransformContext<any>) => unknown | Promise<unknown>;
|
|
121
|
+
/** @internal Used by generated Farm types. It has no runtime value. */
|
|
122
|
+
readonly __farmContentEntry: ContentEntry<TData>;
|
|
123
|
+
}
|
|
124
|
+
export type ContentCollections = Record<string, ContentCollection<any, any>>;
|
|
125
|
+
export type InferContentCollectionEntry<TCollection> = TCollection extends {
|
|
126
|
+
readonly __farmContentEntry: infer TEntry;
|
|
127
|
+
} ? TEntry : never;
|
|
128
|
+
export type InferContentRegistry<TCollections extends ContentCollections> = {
|
|
129
|
+
readonly [TName in keyof TCollections]: InferContentCollectionEntry<TCollections[TName]>;
|
|
130
|
+
};
|
|
131
|
+
export interface ContentOptions<TCollections extends ContentCollections> {
|
|
132
|
+
collections: TCollections;
|
|
133
|
+
}
|
|
134
|
+
export interface ContentPlugin<TCollections extends ContentCollections> extends FarmPlugin {
|
|
135
|
+
/** @internal Read by Farm's generated type declarations. It has no runtime value. */
|
|
136
|
+
readonly __farmContentRegistry: InferContentRegistry<TCollections>;
|
|
137
|
+
}
|
|
138
|
+
declare global {
|
|
139
|
+
namespace FarmJS {
|
|
140
|
+
interface ContentRegistry {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export type AppContentRegistry = FarmJS.ContentRegistry extends {
|
|
145
|
+
collections: infer TCollections extends Record<string, ContentEntry<any>>;
|
|
146
|
+
} ? TCollections : Record<string, ContentEntry<Record<string, unknown>>>;
|
|
147
|
+
export type ContentCollectionName = Extract<keyof AppContentRegistry, string>;
|
|
148
|
+
export type AppContentEntry<TName extends ContentCollectionName> = AppContentRegistry[TName];
|
|
149
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@farm.js/content",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "Typed local content collections for Farm.js applications",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"content",
|
|
7
|
+
"content-collections",
|
|
8
|
+
"farm.js",
|
|
9
|
+
"markdown",
|
|
10
|
+
"mdx"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/farming-labs/farm.js",
|
|
16
|
+
"directory": "packages/farm-content"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"module": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./server": {
|
|
31
|
+
"types": "./dist/server.d.ts",
|
|
32
|
+
"import": "./dist/server.js"
|
|
33
|
+
},
|
|
34
|
+
"./internal/runtime": {
|
|
35
|
+
"types": "./dist/runtime.d.ts",
|
|
36
|
+
"import": "./dist/runtime.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"fast-glob": "^3.3.2",
|
|
44
|
+
"gray-matter": "^4.0.3",
|
|
45
|
+
"image-size": "^2.0.2",
|
|
46
|
+
"mdast-util-from-markdown": "^2.0.2",
|
|
47
|
+
"yaml": "^2.8.0",
|
|
48
|
+
"@farm.js/core": "0.1.0-beta.92"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^20.10.5",
|
|
52
|
+
"typescript": "^5.3.3",
|
|
53
|
+
"vitest": "^3.2.7"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=22.13.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc",
|
|
60
|
+
"dev": "tsc --watch",
|
|
61
|
+
"type-check": "tsc --noEmit",
|
|
62
|
+
"test": "vitest run"
|
|
63
|
+
}
|
|
64
|
+
}
|