@farm.js/contentful 0.1.0-beta.104
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 +33 -0
- package/dist/content.d.ts +45 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +85 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Farm.js Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @farm.js/contentful
|
|
2
|
+
|
|
3
|
+
Contentful content source for [Farm.js](https://github.com/farming-labs/farm.js). Feeds Contentful
|
|
4
|
+
entries into `@farm.js/content` collections, where they get the same schema validation, transforms,
|
|
5
|
+
and generated server types as local Markdown.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { collection, content } from "@farm.js/content";
|
|
9
|
+
import { contentfulSource } from "@farm.js/contentful";
|
|
10
|
+
|
|
11
|
+
content({
|
|
12
|
+
collections: {
|
|
13
|
+
posts: collection({
|
|
14
|
+
source: contentfulSource({
|
|
15
|
+
contentType: "post",
|
|
16
|
+
query: { order: ["-sys.createdAt"] },
|
|
17
|
+
refreshInterval: 30_000, // dev only
|
|
18
|
+
}),
|
|
19
|
+
schema: post,
|
|
20
|
+
}),
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Set `CONTENTFUL_SPACE_ID` and `CONTENTFUL_ACCESS_TOKEN`, or pass an existing client through
|
|
26
|
+
`client`. Entry IDs default to a string `fields.slug`, falling back to `sys.id`. Pagination past
|
|
27
|
+
Contentful's 1000-entry page cap is handled internally. For drafts, set
|
|
28
|
+
`host: "preview.contentful.com"` with `CONTENTFUL_PREVIEW_TOKEN`.
|
|
29
|
+
|
|
30
|
+
Content is a build-time snapshot: entries are fetched while the configuration loads and bundled
|
|
31
|
+
into production output. Point a Contentful webhook at your platform's deploy hook to rebuild on
|
|
32
|
+
publish. See the [content plugin docs](https://farmjs.dev/docs/plugins/content) for the shared
|
|
33
|
+
pipeline and the live-content alternative.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ContentRemoteSource } from "@farm.js/content";
|
|
2
|
+
import { type ContentfulClientApi } from "contentful";
|
|
3
|
+
export interface ContentfulSourceOptions {
|
|
4
|
+
/** Contentful content type ID to load, e.g. "post". */
|
|
5
|
+
contentType: string;
|
|
6
|
+
/**
|
|
7
|
+
* Extra `getEntries` parameters: `order`, `include`, field filters. `skip`
|
|
8
|
+
* and `content_type` are owned by the source; `limit` caps the page size.
|
|
9
|
+
*/
|
|
10
|
+
query?: Record<string, unknown>;
|
|
11
|
+
/** Existing Contentful client. When provided, Farm does not construct its own. */
|
|
12
|
+
client?: ContentfulClientApi<undefined>;
|
|
13
|
+
/** Defaults to `CONTENTFUL_SPACE_ID`. */
|
|
14
|
+
space?: string;
|
|
15
|
+
/** Defaults to `CONTENTFUL_ACCESS_TOKEN` (`CONTENTFUL_PREVIEW_TOKEN` for the preview host). */
|
|
16
|
+
accessToken?: string;
|
|
17
|
+
/** Defaults to `CONTENTFUL_ENVIRONMENT`, then "master". */
|
|
18
|
+
environment?: string;
|
|
19
|
+
/** Set "preview.contentful.com" with a preview token to load drafts. */
|
|
20
|
+
host?: string;
|
|
21
|
+
/** Names the source in error messages. Defaults to `contentful:<contentType>`. */
|
|
22
|
+
name?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Entry identifier per document. Defaults to a string `fields.slug` when
|
|
25
|
+
* present, falling back to `sys.id`.
|
|
26
|
+
*/
|
|
27
|
+
id?: (entry: {
|
|
28
|
+
sys: {
|
|
29
|
+
id: string;
|
|
30
|
+
};
|
|
31
|
+
fields: Record<string, unknown>;
|
|
32
|
+
}) => string;
|
|
33
|
+
/** Optional Markdown body per document, for `entry.body` and word counts. */
|
|
34
|
+
body?: (fields: Record<string, unknown>) => string | undefined;
|
|
35
|
+
/** Development re-fetch cadence in milliseconds. */
|
|
36
|
+
refreshInterval?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A `@farm.js/content` source that loads entries from Contentful, so a CMS
|
|
40
|
+
* collection gets the same schema validation, transforms, and generated
|
|
41
|
+
* server types as local files. Content is a build-time snapshot; point a
|
|
42
|
+
* Contentful webhook at a deploy hook to rebuild on publish.
|
|
43
|
+
*/
|
|
44
|
+
export declare function contentfulSource(options: ContentfulSourceOptions): ContentRemoteSource;
|
|
45
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAapE,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,kFAAkF;IAClF,MAAM,CAAC,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACxC,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KAAK,MAAM,CAAC;IACjF,6EAA6E;IAC7E,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/D,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAKD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,mBAAmB,CAmFtF"}
|
package/dist/content.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createClient } from "contentful";
|
|
2
|
+
/** Contentful caps a single page at 1000 entries. */
|
|
3
|
+
const PAGE_SIZE = 1000;
|
|
4
|
+
/**
|
|
5
|
+
* A `@farm.js/content` source that loads entries from Contentful, so a CMS
|
|
6
|
+
* collection gets the same schema validation, transforms, and generated
|
|
7
|
+
* server types as local files. Content is a build-time snapshot; point a
|
|
8
|
+
* Contentful webhook at a deploy hook to rebuild on publish.
|
|
9
|
+
*/
|
|
10
|
+
export function contentfulSource(options) {
|
|
11
|
+
if (typeof options?.contentType !== "string" || !options.contentType.trim()) {
|
|
12
|
+
throw new TypeError("contentfulSource() requires a `contentType`");
|
|
13
|
+
}
|
|
14
|
+
const name = options.name ?? `contentful:${options.contentType}`;
|
|
15
|
+
const resolveId = options.id ?? defaultEntryId;
|
|
16
|
+
// Resolved lazily so importing farm.config.ts without the env set only
|
|
17
|
+
// fails when the collection actually loads, with an actionable message.
|
|
18
|
+
let client = options.client;
|
|
19
|
+
const resolveClient = () => {
|
|
20
|
+
if (client)
|
|
21
|
+
return client;
|
|
22
|
+
const preview = options.host?.includes("preview") ?? false;
|
|
23
|
+
const space = options.space ?? process.env.CONTENTFUL_SPACE_ID;
|
|
24
|
+
const accessToken = options.accessToken ??
|
|
25
|
+
(preview ? process.env.CONTENTFUL_PREVIEW_TOKEN : undefined) ??
|
|
26
|
+
process.env.CONTENTFUL_ACCESS_TOKEN;
|
|
27
|
+
if (!space || !accessToken) {
|
|
28
|
+
throw new Error(`contentfulSource(${JSON.stringify(name)}) requires a space and access token. Set ` +
|
|
29
|
+
"CONTENTFUL_SPACE_ID and CONTENTFUL_ACCESS_TOKEN, pass them to contentfulSource(), " +
|
|
30
|
+
"or supply an existing client through `client`.");
|
|
31
|
+
}
|
|
32
|
+
client = createClient({
|
|
33
|
+
space,
|
|
34
|
+
accessToken,
|
|
35
|
+
environment: options.environment ?? process.env.CONTENTFUL_ENVIRONMENT ?? "master",
|
|
36
|
+
...(options.host ? { host: options.host } : {}),
|
|
37
|
+
});
|
|
38
|
+
return client;
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
kind: "remote",
|
|
42
|
+
name,
|
|
43
|
+
...(options.refreshInterval !== undefined ? { refreshInterval: options.refreshInterval } : {}),
|
|
44
|
+
async fetch() {
|
|
45
|
+
const resolved = resolveClient();
|
|
46
|
+
const pageSize = Math.min(Number(options.query?.limit ?? PAGE_SIZE) || PAGE_SIZE, PAGE_SIZE);
|
|
47
|
+
const entries = [];
|
|
48
|
+
// getEntries pages at most 1000 entries; keep fetching until `total`.
|
|
49
|
+
for (let skip = 0;; skip += pageSize) {
|
|
50
|
+
const page = (await resolved.getEntries({
|
|
51
|
+
...options.query,
|
|
52
|
+
content_type: options.contentType,
|
|
53
|
+
limit: pageSize,
|
|
54
|
+
skip,
|
|
55
|
+
}));
|
|
56
|
+
if (!page || !Array.isArray(page.items)) {
|
|
57
|
+
throw new Error(`contentfulSource(${JSON.stringify(name)}) received a response without items`);
|
|
58
|
+
}
|
|
59
|
+
entries.push(...page.items);
|
|
60
|
+
if (entries.length >= page.total || page.items.length === 0)
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
return entries.map((entry, index) => {
|
|
64
|
+
if (!entry?.sys?.id || !entry.fields || typeof entry.fields !== "object") {
|
|
65
|
+
throw new Error(`contentfulSource(${JSON.stringify(name)}) entry at index ${index} is missing sys.id or fields`);
|
|
66
|
+
}
|
|
67
|
+
const id = resolveId(entry);
|
|
68
|
+
if (typeof id !== "string" || !id) {
|
|
69
|
+
throw new Error(`contentfulSource(${JSON.stringify(name)}) could not derive an id for entry ` +
|
|
70
|
+
`${JSON.stringify(entry.sys.id)}; give entries a slug field or pass an \`id\` function`);
|
|
71
|
+
}
|
|
72
|
+
const body = options.body?.(entry.fields);
|
|
73
|
+
return {
|
|
74
|
+
id,
|
|
75
|
+
data: entry.fields,
|
|
76
|
+
...(typeof body === "string" ? { body } : {}),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function defaultEntryId(entry) {
|
|
83
|
+
const slug = entry.fields.slug;
|
|
84
|
+
return typeof slug === "string" && slug ? slug : entry.sys.id;
|
|
85
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { contentfulSource } from "./content.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@farm.js/contentful",
|
|
3
|
+
"version": "0.1.0-beta.104",
|
|
4
|
+
"description": "Contentful content source for Farm.js",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/farming-labs/farm.js",
|
|
9
|
+
"directory": "packages/farm-contentful"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"contentful": "^11.8.6",
|
|
29
|
+
"typescript": "^5.3.3",
|
|
30
|
+
"vitest": "^3.2.7",
|
|
31
|
+
"@farm.js/content": "0.1.0-beta.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"contentful": "^10.0.0 || ^11.0.0",
|
|
35
|
+
"@farm.js/content": "^0.1.0-beta.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@farm.js/content": {
|
|
39
|
+
"optional": false
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc",
|
|
44
|
+
"dev": "tsc --watch",
|
|
45
|
+
"type-check": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run"
|
|
47
|
+
}
|
|
48
|
+
}
|