@buildbase/sdk 0.0.71 → 0.0.74
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/CHANGELOG.md +106 -0
- package/README.md +43 -10
- package/dist/cli.mjs +2 -0
- package/dist/css/styles.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +103 -2
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.cjs +1 -1
- package/dist/mcp/index.d.ts +30 -0
- package/dist/mcp/index.mjs +1 -1
- package/dist/react/chunks/SettingsDialog-C0ocJMBA.cjs +2 -0
- package/dist/react/chunks/SettingsDialog-CWrmSyKV.mjs +2 -0
- package/dist/react/chunks/{SubscriptionDialog-Bkue_qyM.cjs → SubscriptionDialog-CLh4zW9Y.cjs} +1 -1
- package/dist/react/chunks/{SubscriptionDialog-ZjdyTmxn.mjs → SubscriptionDialog-sH0cQdPD.mjs} +1 -1
- package/dist/react/chunks/react-BBSVsY3n.cjs +2 -0
- package/dist/react/chunks/react-CNHfoa5L.mjs +2 -0
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +130 -8
- package/dist/react/index.mjs +1 -1
- package/dist/server/browser.cjs +1 -0
- package/dist/server/browser.mjs +1 -0
- package/dist/server/index.cjs +1 -0
- package/dist/server/index.d.ts +556 -0
- package/dist/server/index.mjs +1 -0
- package/dist/server/next/index.cjs +1 -0
- package/dist/server/next/index.d.ts +156 -0
- package/dist/server/next/index.mjs +1 -0
- package/dist/tracking/index.cjs +1 -1
- package/dist/tracking/index.d.ts +205 -8
- package/dist/tracking/index.mjs +1 -1
- package/docs/SERVER-CONTENT.md +297 -0
- package/package.json +65 -5
- package/dist/react/chunks/SettingsDialog-D5lLHG9-.cjs +0 -2
- package/dist/react/chunks/SettingsDialog-DiTnAWFt.mjs +0 -2
- package/dist/react/chunks/react-BIZcTNos.mjs +0 -2
- package/dist/react/chunks/react-DM1nGVlR.cjs +0 -2
- /package/dist/react/chunks/{ar-D_agqxXa.cjs → ar-CRVhjpVE.cjs} +0 -0
- /package/dist/react/chunks/{ar-By_nJtJ6.mjs → ar-DFPDdu2U.mjs} +0 -0
- /package/dist/react/chunks/{de-D7j-9T8u.cjs → de-BGhzn711.cjs} +0 -0
- /package/dist/react/chunks/{de-CQW2yLLr.mjs → de-Bqi8scew.mjs} +0 -0
- /package/dist/react/chunks/{es-CggBj8dc.mjs → es-D_Eqhob3.mjs} +0 -0
- /package/dist/react/chunks/{es-DYtfnqFg.cjs → es-DyrMaeUp.cjs} +0 -0
- /package/dist/react/chunks/{fr-Bu6_05LA.cjs → fr-C0SkloRr.cjs} +0 -0
- /package/dist/react/chunks/{fr-CbvudRKC.mjs → fr-DkZpFQ1Q.mjs} +0 -0
- /package/dist/react/chunks/{hi-dKtSIE95.cjs → hi-B9TK87T8.cjs} +0 -0
- /package/dist/react/chunks/{hi-DrJIuj_0.mjs → hi-DrTcPkiY.mjs} +0 -0
- /package/dist/react/chunks/{ja-BtZsSYAE.mjs → ja-Dgahn--0.mjs} +0 -0
- /package/dist/react/chunks/{ja-6AwwCaS_.cjs → ja-LjzlAhAT.cjs} +0 -0
- /package/dist/react/chunks/{zh-BZsPG6yc.mjs → zh-BezRHDHs.mjs} +0 -0
- /package/dist/react/chunks/{zh-C6onHYpa.cjs → zh-CcodKi4U.cjs} +0 -0
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache stores for the server content client.
|
|
3
|
+
*
|
|
4
|
+
* One small interface, so the same client caches in process memory, Redis,
|
|
5
|
+
* Cloudflare KV or Upstash without the client knowing which. Every store keeps
|
|
6
|
+
* a tag index so a webhook can drop what changed without scanning.
|
|
7
|
+
*/
|
|
8
|
+
/** What the client stores per read. */
|
|
9
|
+
interface CacheEntry<T = unknown> {
|
|
10
|
+
/** The parsed response body. */
|
|
11
|
+
data: T;
|
|
12
|
+
/** The platform's ETag, sent back as `If-None-Match` on revalidation. */
|
|
13
|
+
etag?: string;
|
|
14
|
+
/** Epoch ms after which the entry must be revalidated before use. */
|
|
15
|
+
freshUntil: number;
|
|
16
|
+
/**
|
|
17
|
+
* Epoch ms until which a stale entry may still be served while one
|
|
18
|
+
* background request revalidates it (`Cache-Control: stale-while-revalidate`).
|
|
19
|
+
*/
|
|
20
|
+
staleUntil?: number;
|
|
21
|
+
/** Tags this entry answers to (see `contentTags`). */
|
|
22
|
+
tags: string[];
|
|
23
|
+
}
|
|
24
|
+
interface CacheStore {
|
|
25
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
26
|
+
/** `ttlSeconds` is how long the store keeps the entry at all (beyond `freshUntil`, for ETag revalidation). */
|
|
27
|
+
set(key: string, entry: CacheEntry, ttlSeconds: number): Promise<void>;
|
|
28
|
+
delete(key: string): Promise<void>;
|
|
29
|
+
/** Drop every entry carrying `tag`. */
|
|
30
|
+
deleteByTag(tag: string): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
interface MemoryCacheOptions {
|
|
33
|
+
/** Oldest entries are evicted past this size. Default 1000. */
|
|
34
|
+
maxEntries?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Process-local cache. The default. Fine for a single server or a
|
|
38
|
+
* long-lived serverless instance; use `redisCache` or `kvCache` to share
|
|
39
|
+
* entries across instances.
|
|
40
|
+
*/
|
|
41
|
+
declare function memoryCache(options?: MemoryCacheOptions): CacheStore;
|
|
42
|
+
/**
|
|
43
|
+
* The subset of a Redis client the store needs. Matches ioredis
|
|
44
|
+
* (`set(k, v, 'EX', s)`, `sadd`, `smembers`) and node-redis v4+
|
|
45
|
+
* (`set(k, v, { EX: s })`, `sAdd`, `sMembers`); the store detects which.
|
|
46
|
+
*/
|
|
47
|
+
interface RedisLikeClient {
|
|
48
|
+
get(key: string): Promise<string | null>;
|
|
49
|
+
set(key: string, value: string, ...args: any[]): Promise<unknown>;
|
|
50
|
+
del(...keys: any[]): Promise<unknown>;
|
|
51
|
+
expire?(key: string, seconds: number): Promise<unknown>;
|
|
52
|
+
sadd?(key: string, ...members: string[]): Promise<unknown>;
|
|
53
|
+
smembers?(key: string): Promise<string[]>;
|
|
54
|
+
sAdd?(key: string, members: string | string[]): Promise<unknown>;
|
|
55
|
+
sMembers?(key: string): Promise<string[]>;
|
|
56
|
+
}
|
|
57
|
+
interface RedisCacheOptions {
|
|
58
|
+
/** Key prefix. Default `buildbase:sdk:`. */
|
|
59
|
+
prefix?: string;
|
|
60
|
+
}
|
|
61
|
+
declare function redisCache(client: RedisLikeClient, options?: RedisCacheOptions): CacheStore;
|
|
62
|
+
/**
|
|
63
|
+
* A key-value binding. Cloudflare KV (`get`, `put(k, v, { expirationTtl })`,
|
|
64
|
+
* `delete`) and Upstash Redis REST (`get`, `set(k, v, { ex })`, `del`) both fit.
|
|
65
|
+
*/
|
|
66
|
+
interface KvLikeStore {
|
|
67
|
+
get(key: string, ...args: any[]): Promise<unknown>;
|
|
68
|
+
put?(key: string, value: string, options?: {
|
|
69
|
+
expirationTtl?: number;
|
|
70
|
+
}): Promise<unknown>;
|
|
71
|
+
set?(key: string, value: string, options?: {
|
|
72
|
+
ex?: number;
|
|
73
|
+
}): Promise<unknown>;
|
|
74
|
+
delete?(key: string): Promise<unknown>;
|
|
75
|
+
del?(key: string): Promise<unknown>;
|
|
76
|
+
}
|
|
77
|
+
interface KvCacheOptions {
|
|
78
|
+
prefix?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Cache on a KV binding. KV has no sets, so each tag's keys are kept in one
|
|
82
|
+
* JSON value; concurrent writes can lose a member, which only means that
|
|
83
|
+
* entry waits out its freshness window instead of being dropped early.
|
|
84
|
+
*/
|
|
85
|
+
declare function kvCache(kv: KvLikeStore, options?: KvCacheOptions): CacheStore;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Response shapes for the content modules, mirrored from the platform's
|
|
89
|
+
* `@ord/shared` types. Kept permissive (`[key: string]: unknown`) because the
|
|
90
|
+
* platform adds fields over time and a consumer should not break on them.
|
|
91
|
+
*/
|
|
92
|
+
interface ContentBase {
|
|
93
|
+
_id: string;
|
|
94
|
+
id?: string;
|
|
95
|
+
createdAt?: string;
|
|
96
|
+
updatedAt?: string;
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}
|
|
99
|
+
interface Seo {
|
|
100
|
+
title: string;
|
|
101
|
+
description: string;
|
|
102
|
+
keywords: string[];
|
|
103
|
+
}
|
|
104
|
+
interface ContentAuthor extends ContentBase {
|
|
105
|
+
name: string;
|
|
106
|
+
username: string;
|
|
107
|
+
image: string;
|
|
108
|
+
links: Record<string, string>;
|
|
109
|
+
archived?: boolean;
|
|
110
|
+
}
|
|
111
|
+
interface ContentFolder extends ContentBase {
|
|
112
|
+
name: string;
|
|
113
|
+
slug: string;
|
|
114
|
+
path: string;
|
|
115
|
+
url?: string;
|
|
116
|
+
parentFolder: string | null;
|
|
117
|
+
subFolders: string[];
|
|
118
|
+
archived?: boolean;
|
|
119
|
+
}
|
|
120
|
+
/** A node of `GET /api/<module>/folders/tree`. */
|
|
121
|
+
interface ContentFolderTreeNode {
|
|
122
|
+
_id?: string;
|
|
123
|
+
name?: string;
|
|
124
|
+
slug?: string;
|
|
125
|
+
path?: string;
|
|
126
|
+
children?: ContentFolderTreeNode[];
|
|
127
|
+
subFolders?: ContentFolderTreeNode[] | string[];
|
|
128
|
+
[key: string]: unknown;
|
|
129
|
+
}
|
|
130
|
+
interface ContentTag extends ContentBase {
|
|
131
|
+
title: string;
|
|
132
|
+
slug: string;
|
|
133
|
+
public: boolean;
|
|
134
|
+
seo?: Seo;
|
|
135
|
+
archived?: boolean;
|
|
136
|
+
}
|
|
137
|
+
interface ContentAsset extends ContentBase {
|
|
138
|
+
name?: string;
|
|
139
|
+
mimeType?: string;
|
|
140
|
+
public?: boolean;
|
|
141
|
+
bucket?: {
|
|
142
|
+
name?: string;
|
|
143
|
+
url?: string;
|
|
144
|
+
path?: string;
|
|
145
|
+
};
|
|
146
|
+
image?: {
|
|
147
|
+
width?: number;
|
|
148
|
+
height?: number;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** A blog post (`/api/blogs`). */
|
|
152
|
+
interface BlogPost extends ContentBase {
|
|
153
|
+
title: string;
|
|
154
|
+
slug: string;
|
|
155
|
+
description: string;
|
|
156
|
+
content: string;
|
|
157
|
+
image: string;
|
|
158
|
+
url?: string;
|
|
159
|
+
published: boolean;
|
|
160
|
+
publishedAt: string | null;
|
|
161
|
+
seo: Seo;
|
|
162
|
+
folder: string | ContentFolder;
|
|
163
|
+
authors: string[] | ContentAuthor[];
|
|
164
|
+
tags: string[] | ContentTag[];
|
|
165
|
+
assetFile?: string | ContentAsset;
|
|
166
|
+
suggestedBlogs?: string[];
|
|
167
|
+
}
|
|
168
|
+
/** A docs page (`/api/docs`). Same shape as a blog post. */
|
|
169
|
+
interface DocPage extends ContentBase {
|
|
170
|
+
title: string;
|
|
171
|
+
slug: string;
|
|
172
|
+
description: string;
|
|
173
|
+
content: string;
|
|
174
|
+
image: string;
|
|
175
|
+
url?: string;
|
|
176
|
+
published: boolean;
|
|
177
|
+
publishedAt: string | null;
|
|
178
|
+
seo: Seo;
|
|
179
|
+
folder: string | ContentFolder;
|
|
180
|
+
authors: string[] | ContentAuthor[];
|
|
181
|
+
tags: string[] | ContentTag[];
|
|
182
|
+
assetFile?: string | ContentAsset;
|
|
183
|
+
suggestedDocs?: string[];
|
|
184
|
+
}
|
|
185
|
+
interface Faq extends ContentBase {
|
|
186
|
+
question: string;
|
|
187
|
+
answer: string;
|
|
188
|
+
faqId: string;
|
|
189
|
+
archived: boolean;
|
|
190
|
+
}
|
|
191
|
+
interface FaqCollection extends ContentBase {
|
|
192
|
+
title: string;
|
|
193
|
+
description: string;
|
|
194
|
+
slug: string;
|
|
195
|
+
faqs: string[] | Faq[];
|
|
196
|
+
archived: boolean;
|
|
197
|
+
}
|
|
198
|
+
interface Testimonial extends ContentBase {
|
|
199
|
+
name: string;
|
|
200
|
+
content: string;
|
|
201
|
+
position: string;
|
|
202
|
+
avatar: string;
|
|
203
|
+
company: {
|
|
204
|
+
name: string;
|
|
205
|
+
website: string;
|
|
206
|
+
logo: string;
|
|
207
|
+
};
|
|
208
|
+
published: boolean;
|
|
209
|
+
publishedAt: string | null;
|
|
210
|
+
archived: boolean;
|
|
211
|
+
}
|
|
212
|
+
interface RichContent extends ContentBase {
|
|
213
|
+
title: string;
|
|
214
|
+
slug: string;
|
|
215
|
+
content: string;
|
|
216
|
+
archived: boolean;
|
|
217
|
+
}
|
|
218
|
+
interface CollectionField {
|
|
219
|
+
slug: string;
|
|
220
|
+
title: string;
|
|
221
|
+
helpText: string;
|
|
222
|
+
type: 'text' | 'rich-text' | 'number' | 'bool' | 'date' | 'color' | 'link' | 'email' | string;
|
|
223
|
+
required: boolean;
|
|
224
|
+
defaultValue: string | number | boolean;
|
|
225
|
+
}
|
|
226
|
+
interface CustomCollection extends ContentBase {
|
|
227
|
+
name: string;
|
|
228
|
+
slug: string;
|
|
229
|
+
collectionId: string;
|
|
230
|
+
archived: boolean;
|
|
231
|
+
}
|
|
232
|
+
/** A record of a collection version. `data` is keyed by field slug. */
|
|
233
|
+
interface CollectionRecord<TData extends Record<string, unknown> = Record<string, unknown>> extends ContentBase {
|
|
234
|
+
recordId: string;
|
|
235
|
+
collectionId: string | CustomCollection;
|
|
236
|
+
versionId: string | Record<string, unknown>;
|
|
237
|
+
formId?: string;
|
|
238
|
+
archived: boolean;
|
|
239
|
+
data: TData;
|
|
240
|
+
}
|
|
241
|
+
/** The platform's paginated list envelope. */
|
|
242
|
+
interface Paginated<T> {
|
|
243
|
+
docs: T[];
|
|
244
|
+
totalDocs: number;
|
|
245
|
+
limit: number;
|
|
246
|
+
page: number;
|
|
247
|
+
totalPages: number;
|
|
248
|
+
hasNextPage: boolean;
|
|
249
|
+
hasPrevPage: boolean;
|
|
250
|
+
nextPage: number | null;
|
|
251
|
+
prevPage: number | null;
|
|
252
|
+
pagingCounter?: number;
|
|
253
|
+
}
|
|
254
|
+
/** Common list options. `filter`/`sort` are sent bracket-encoded, the only shape the platform's validators accept. */
|
|
255
|
+
interface ListOptions {
|
|
256
|
+
/** 1-based page. */
|
|
257
|
+
page?: number;
|
|
258
|
+
/** Page size (the platform's default is 10). */
|
|
259
|
+
limit?: number;
|
|
260
|
+
/** Mongo-style filter, e.g. `{ tags: '<tagId>' }`. */
|
|
261
|
+
filter?: Record<string, unknown>;
|
|
262
|
+
/** e.g. `{ publishedAt: -1 }`. */
|
|
263
|
+
sort?: Record<string, 1 | -1>;
|
|
264
|
+
}
|
|
265
|
+
interface PublishableListOptions extends ListOptions {
|
|
266
|
+
/**
|
|
267
|
+
* Include unpublished posts. Default `false`: a server rendering a public
|
|
268
|
+
* page almost never wants drafts, and the platform returns them unless told
|
|
269
|
+
* otherwise.
|
|
270
|
+
*/
|
|
271
|
+
includeDrafts?: boolean;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Kept in its own module so the browser stub bundles this string and nothing else. */
|
|
275
|
+
declare const SERVER_ONLY_MESSAGE: string;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Server-only client for BuildBase content: blogs, docs, FAQs, testimonials,
|
|
279
|
+
* rich content and collection data.
|
|
280
|
+
*
|
|
281
|
+
* It authenticates with an org API token (`<orgId>:<secret>`), which must never
|
|
282
|
+
* reach a browser, so it refuses to run in one. It uses only the standard
|
|
283
|
+
* `fetch` API: Node 18+, Bun, Deno, Cloudflare Workers and edge runtimes all
|
|
284
|
+
* work unchanged.
|
|
285
|
+
*
|
|
286
|
+
* Every read is cached (see `CacheStore`) and revalidated with `If-None-Match`,
|
|
287
|
+
* so a page render costs nothing while the entry is fresh and a single
|
|
288
|
+
* `304 Not Modified` once it is not. Content webhooks drop entries by tag the
|
|
289
|
+
* moment something changes (see `handleContentWebhook`).
|
|
290
|
+
*/
|
|
291
|
+
|
|
292
|
+
interface RequestContext {
|
|
293
|
+
/** Path including the query string, e.g. `/api/blogs?filter[published]=true`. */
|
|
294
|
+
path: string;
|
|
295
|
+
/** Cache tags the read will be stored under. */
|
|
296
|
+
tags: string[];
|
|
297
|
+
}
|
|
298
|
+
interface ServerClientOptions {
|
|
299
|
+
/** e.g. `https://api.console.buildbase.app`. */
|
|
300
|
+
serverUrl: string;
|
|
301
|
+
/** Org API token, `<orgId>:<secret>` (console, Settings, API tokens). Pass it from your server's environment. */
|
|
302
|
+
apiToken: string;
|
|
303
|
+
/** Where reads are cached. Default: `memoryCache()`. `false` disables caching (every call hits the API). */
|
|
304
|
+
cache?: CacheStore | false;
|
|
305
|
+
/** A `fetch` implementation. Default: the global `fetch`. */
|
|
306
|
+
fetch?: typeof fetch;
|
|
307
|
+
/** Per-request timeout. Default 10 000 ms. */
|
|
308
|
+
timeoutMs?: number;
|
|
309
|
+
/** Freshness when the response carries no `Cache-Control: max-age`. Default 60 s. */
|
|
310
|
+
defaultTtlSeconds?: number;
|
|
311
|
+
/** How long a stale entry is kept for ETag revalidation and error fallback. Default 86 400 s. */
|
|
312
|
+
retainSeconds?: number;
|
|
313
|
+
/** Serve the last good copy when the API errors or is unreachable. Default true. 401/403 always throw. */
|
|
314
|
+
staleOnError?: boolean;
|
|
315
|
+
/**
|
|
316
|
+
* Extra `fetch` init, merged into every request. A function gets the read's
|
|
317
|
+
* path and tags; this is how framework caches plug in, for example Next.js
|
|
318
|
+
* `{ next: { tags, revalidate } }` (see `@buildbase/sdk/server/next`).
|
|
319
|
+
*/
|
|
320
|
+
requestInit?: RequestInit | ((ctx: RequestContext) => RequestInit);
|
|
321
|
+
}
|
|
322
|
+
declare class BuildBaseContentError extends Error {
|
|
323
|
+
readonly status: number;
|
|
324
|
+
readonly url: string;
|
|
325
|
+
readonly body?: unknown | undefined;
|
|
326
|
+
constructor(message: string, status: number, url: string, body?: unknown | undefined);
|
|
327
|
+
}
|
|
328
|
+
/** The cache tags a result was stored under, for wiring into a framework cache. */
|
|
329
|
+
declare function getContentTags(result: unknown): string[];
|
|
330
|
+
declare function createServerClient(options: ServerClientOptions): {
|
|
331
|
+
blogs: {
|
|
332
|
+
/** Published posts (drafts with `includeDrafts: true`). */
|
|
333
|
+
list(opts?: PublishableListOptions): Promise<Paginated<BlogPost>>;
|
|
334
|
+
/** One post by id, or `null`. */
|
|
335
|
+
get(id: string): Promise<BlogPost | null>;
|
|
336
|
+
/** One published post by its path (`folder/slug`), or `null`. */
|
|
337
|
+
byPath(path: string, opts?: {
|
|
338
|
+
draftPreview?: boolean;
|
|
339
|
+
}): Promise<BlogPost | null>;
|
|
340
|
+
/** The folder tree, for navigation. */
|
|
341
|
+
tree(opts?: {
|
|
342
|
+
folder?: string;
|
|
343
|
+
}): Promise<ContentFolderTreeNode[]>;
|
|
344
|
+
/** Tags of this module. */
|
|
345
|
+
tags(opts?: ListOptions): Promise<Paginated<ContentTag>>;
|
|
346
|
+
/** Authors of this module. */
|
|
347
|
+
authors(opts?: ListOptions): Promise<Paginated<ContentAuthor>>;
|
|
348
|
+
};
|
|
349
|
+
docs: {
|
|
350
|
+
/** Published posts (drafts with `includeDrafts: true`). */
|
|
351
|
+
list(opts?: PublishableListOptions): Promise<Paginated<DocPage>>;
|
|
352
|
+
/** One post by id, or `null`. */
|
|
353
|
+
get(id: string): Promise<DocPage | null>;
|
|
354
|
+
/** One published post by its path (`folder/slug`), or `null`. */
|
|
355
|
+
byPath(path: string, opts?: {
|
|
356
|
+
draftPreview?: boolean;
|
|
357
|
+
}): Promise<DocPage | null>;
|
|
358
|
+
/** The folder tree, for navigation. */
|
|
359
|
+
tree(opts?: {
|
|
360
|
+
folder?: string;
|
|
361
|
+
}): Promise<ContentFolderTreeNode[]>;
|
|
362
|
+
/** Tags of this module. */
|
|
363
|
+
tags(opts?: ListOptions): Promise<Paginated<ContentTag>>;
|
|
364
|
+
/** Authors of this module. */
|
|
365
|
+
authors(opts?: ListOptions): Promise<Paginated<ContentAuthor>>;
|
|
366
|
+
};
|
|
367
|
+
faqs: {
|
|
368
|
+
/** FAQ collections (groups). */
|
|
369
|
+
collections(opts?: ListOptions): Promise<Paginated<FaqCollection>>;
|
|
370
|
+
/** One collection by slug or id, or `null`. */
|
|
371
|
+
collection: (slugOrId: string) => Promise<FaqCollection | null>;
|
|
372
|
+
/** The questions of a collection (by slug or id), or `[]` when it does not exist. */
|
|
373
|
+
questions(slugOrId: string): Promise<Faq[]>;
|
|
374
|
+
};
|
|
375
|
+
testimonials: {
|
|
376
|
+
/** Published testimonials (all with `includeUnpublished: true`). */
|
|
377
|
+
list(opts?: ListOptions & {
|
|
378
|
+
includeUnpublished?: boolean;
|
|
379
|
+
}): Promise<Paginated<Testimonial>>;
|
|
380
|
+
/** One testimonial by id, or `null`. */
|
|
381
|
+
get(id: string): Promise<Testimonial | null>;
|
|
382
|
+
};
|
|
383
|
+
richContent: {
|
|
384
|
+
/** One block by slug, or `null`. */
|
|
385
|
+
get(slug: string): Promise<RichContent | null>;
|
|
386
|
+
list(opts?: ListOptions): Promise<Paginated<RichContent>>;
|
|
387
|
+
};
|
|
388
|
+
collections: {
|
|
389
|
+
/** Collections (their schemas), for discovery. */
|
|
390
|
+
list(opts?: ListOptions): Promise<Paginated<CustomCollection>>;
|
|
391
|
+
/**
|
|
392
|
+
* Every record of a collection's live version (or of `version`), as an
|
|
393
|
+
* array. Records are keyed by field slug in `record.data`.
|
|
394
|
+
*/
|
|
395
|
+
data<TData extends Record<string, unknown> = Record<string, unknown>>(slug: string, opts?: {
|
|
396
|
+
version?: number;
|
|
397
|
+
}): Promise<CollectionRecord<TData>[]>;
|
|
398
|
+
};
|
|
399
|
+
/** Drop every cached read carrying `tag` (see `contentTags`). */
|
|
400
|
+
revalidate(tag: string): Promise<void>;
|
|
401
|
+
/** Drop everything this client cached. */
|
|
402
|
+
revalidateAll(): Promise<void>;
|
|
403
|
+
/** The store this client caches into (`null` when caching is off). */
|
|
404
|
+
cache: CacheStore | null;
|
|
405
|
+
};
|
|
406
|
+
type ServerClient = ReturnType<typeof createServerClient>;
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Cache-tag vocabulary shared with the platform's content webhook events.
|
|
410
|
+
*
|
|
411
|
+
* A webhook for a content change carries `data.tags`, built from the same
|
|
412
|
+
* strings, so a server that cached a read under these tags can drop exactly
|
|
413
|
+
* what changed:
|
|
414
|
+
*
|
|
415
|
+
* - `buildbase:<module>` every list of that module
|
|
416
|
+
* - `buildbase:<module>:<slug>` one item, by slug
|
|
417
|
+
* - `buildbase:<module>:id:<id>` one item, by id
|
|
418
|
+
* - `buildbase:collection:<slug>` any record change in that collection
|
|
419
|
+
* (record events also carry `buildbase:collection:id:<collectionId>`)
|
|
420
|
+
* - `buildbase:all` everything this SDK cached (added to every entry)
|
|
421
|
+
*/
|
|
422
|
+
type ContentModule = 'blog' | 'doc' | 'faq' | 'faq_collection' | 'testimonial' | 'rich_content' | 'collection';
|
|
423
|
+
declare const CONTENT_MODULES: readonly ContentModule[];
|
|
424
|
+
declare const ALL_TAG = "buildbase:all";
|
|
425
|
+
declare const contentTags: {
|
|
426
|
+
/** Every list of a module. */
|
|
427
|
+
module: (module: ContentModule) => string;
|
|
428
|
+
/** One item by slug. */
|
|
429
|
+
slug: (module: ContentModule, slug: string) => string;
|
|
430
|
+
/** One item by id. */
|
|
431
|
+
id: (module: ContentModule, id: string) => string;
|
|
432
|
+
/** Every record of one collection. */
|
|
433
|
+
collection: (collectionSlug: string) => string;
|
|
434
|
+
all: string;
|
|
435
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* Webhook events the platform sends for content changes. Anything else that
|
|
438
|
+
* reaches a content webhook route is acknowledged and ignored.
|
|
439
|
+
*/
|
|
440
|
+
declare const CONTENT_WEBHOOK_EVENTS: readonly ["blog.published", "blog.updated", "blog.unpublished", "blog.deleted", "doc.published", "doc.updated", "doc.unpublished", "doc.deleted", "faq.updated", "faq_collection.updated", "testimonial.updated", "testimonial.deleted", "rich_content.updated", "rich_content.deleted", "collection.version_published", "collection.record_created", "collection.record_updated", "collection.record_deleted"];
|
|
441
|
+
type ContentWebhookEvent = (typeof CONTENT_WEBHOOK_EVENTS)[number];
|
|
442
|
+
declare function isContentWebhookEvent(event: string): event is ContentWebhookEvent;
|
|
443
|
+
/** The data every content webhook event carries. */
|
|
444
|
+
interface ContentWebhookData {
|
|
445
|
+
module: ContentModule;
|
|
446
|
+
id?: string | null;
|
|
447
|
+
slug?: string | null;
|
|
448
|
+
path?: string | null;
|
|
449
|
+
tags?: string[];
|
|
450
|
+
[key: string]: unknown;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* The tags to drop for a content event. Uses the platform's `data.tags` when
|
|
454
|
+
* present; otherwise rebuilds them from module/slug/id so an older payload
|
|
455
|
+
* still invalidates the right entries.
|
|
456
|
+
*/
|
|
457
|
+
declare function tagsForContentEvent(event: string, data: Partial<ContentWebhookData> | undefined): string[];
|
|
458
|
+
|
|
459
|
+
interface ContentWebhookEventPayload {
|
|
460
|
+
event: string;
|
|
461
|
+
timestamp: number;
|
|
462
|
+
data: ContentWebhookData;
|
|
463
|
+
}
|
|
464
|
+
interface HandleContentWebhookOptions {
|
|
465
|
+
/** The endpoint's signing secret (console, Settings, Webhooks). */
|
|
466
|
+
secret: string;
|
|
467
|
+
/** Drop matching entries from this store (pass `client.cache`). */
|
|
468
|
+
cache?: Pick<CacheStore, 'deleteByTag'> | null;
|
|
469
|
+
/** Or drop them however your framework caches, e.g. Next.js `revalidateTag`. */
|
|
470
|
+
revalidate?: (tags: string[]) => void | Promise<void>;
|
|
471
|
+
/** Called after invalidation, for logging or rebuild triggers. */
|
|
472
|
+
onEvent?: (event: ContentWebhookEventPayload, tags: string[]) => void | Promise<void>;
|
|
473
|
+
/** Signature age window. Default 300 s. */
|
|
474
|
+
maxAgeSeconds?: number;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Verify a BuildBase webhook and invalidate the cache entries it names.
|
|
478
|
+
* Answers 401 on a bad signature, 200 otherwise (including for non-content
|
|
479
|
+
* events, which are acknowledged and ignored so the platform does not retry).
|
|
480
|
+
*/
|
|
481
|
+
declare function handleContentWebhook(request: Request, options: HandleContentWebhookOptions): Promise<Response>;
|
|
482
|
+
/** `handleContentWebhook` bound to its options, ready to export as a route handler. */
|
|
483
|
+
declare function createContentWebhookHandler(options: HandleContentWebhookOptions): (request: Request) => Promise<Response>;
|
|
484
|
+
/** The parts of Node's IncomingMessage the adapter reads. Express and Fastify requests fit. */
|
|
485
|
+
interface NodeRequestLike {
|
|
486
|
+
method?: string;
|
|
487
|
+
url?: string;
|
|
488
|
+
originalUrl?: string;
|
|
489
|
+
headers: Record<string, string | string[] | undefined>;
|
|
490
|
+
/** Express `express.raw()` puts a Buffer here; Fastify with a raw-body plugin a string. */
|
|
491
|
+
body?: unknown;
|
|
492
|
+
rawBody?: unknown;
|
|
493
|
+
on?(event: 'data' | 'end' | 'error', listener: (arg?: any) => void): unknown;
|
|
494
|
+
readableEnded?: boolean;
|
|
495
|
+
}
|
|
496
|
+
interface NodeResponseLike {
|
|
497
|
+
statusCode: number;
|
|
498
|
+
setHeader(name: string, value: string): unknown;
|
|
499
|
+
end(body?: string | Uint8Array): unknown;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Adapt a `(Request) => Response` handler to Node's `(req, res)`:
|
|
503
|
+
*
|
|
504
|
+
* ```ts
|
|
505
|
+
* app.post('/webhooks/buildbase', express.raw({ type: 'application/json' }),
|
|
506
|
+
* toNodeHandler(createContentWebhookHandler({ secret, cache: client.cache })));
|
|
507
|
+
* ```
|
|
508
|
+
*/
|
|
509
|
+
declare function toNodeHandler(handler: (request: Request) => Promise<Response>): (req: NodeRequestLike, res: NodeResponseLike) => Promise<void>;
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Build-time pull: fetch every published piece of content once and write it
|
|
513
|
+
* to a JSON file the site bundles. For static sites, single-page apps and any
|
|
514
|
+
* stack without a backend of its own: the token lives in CI, never in the site.
|
|
515
|
+
*/
|
|
516
|
+
|
|
517
|
+
declare const PULL_MODULES: readonly ["blogs", "docs", "faqs", "testimonials", "richContent", "collections"];
|
|
518
|
+
type PullModule = (typeof PULL_MODULES)[number];
|
|
519
|
+
interface PullOptions {
|
|
520
|
+
/** Which modules to pull. Default: all. */
|
|
521
|
+
modules?: readonly PullModule[];
|
|
522
|
+
/** Collection slugs to pull records for. Default: every live collection. */
|
|
523
|
+
collections?: string[];
|
|
524
|
+
/** Page size used while paging through lists. Default 100. */
|
|
525
|
+
pageSize?: number;
|
|
526
|
+
}
|
|
527
|
+
interface PulledContent {
|
|
528
|
+
generatedAt: string;
|
|
529
|
+
blogs?: BlogPost[];
|
|
530
|
+
docs?: DocPage[];
|
|
531
|
+
faqs?: Array<FaqCollection & {
|
|
532
|
+
questions: Faq[];
|
|
533
|
+
}>;
|
|
534
|
+
testimonials?: Testimonial[];
|
|
535
|
+
richContent?: RichContent[];
|
|
536
|
+
collections?: Record<string, CollectionRecord[]>;
|
|
537
|
+
}
|
|
538
|
+
declare function pullContent(client: ServerClient, options?: PullOptions): Promise<PulledContent>;
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Query-string encoding the platform's validators accept.
|
|
542
|
+
*
|
|
543
|
+
* The org API validates `filter`, `sort` and similar parameters as objects,
|
|
544
|
+
* and its query parser only builds objects from bracket keys
|
|
545
|
+
* (`filter[published]=true`). A JSON-encoded `filter={"published":true}`
|
|
546
|
+
* is refused as "invalid field filter".
|
|
547
|
+
*/
|
|
548
|
+
type Primitive = string | number | boolean;
|
|
549
|
+
type QueryValue = Primitive | null | undefined | QueryValue[] | {
|
|
550
|
+
[key: string]: QueryValue;
|
|
551
|
+
};
|
|
552
|
+
/** Encode `{ filter: { published: true }, $limit: 20 }` into a stable query string (keys sorted, so cache keys are too). */
|
|
553
|
+
declare function encodeQuery(query: Record<string, QueryValue> | undefined): string;
|
|
554
|
+
|
|
555
|
+
export { ALL_TAG, BuildBaseContentError, CONTENT_MODULES, CONTENT_WEBHOOK_EVENTS, PULL_MODULES, SERVER_ONLY_MESSAGE, contentTags, createContentWebhookHandler, createServerClient, encodeQuery, getContentTags, handleContentWebhook, isContentWebhookEvent, kvCache, memoryCache, pullContent, redisCache, tagsForContentEvent, toNodeHandler };
|
|
556
|
+
export type { BlogPost, CacheEntry, CacheStore, CollectionField, CollectionRecord, ContentAsset, ContentAuthor, ContentBase, ContentFolder, ContentFolderTreeNode, ContentModule, ContentTag, ContentWebhookData, ContentWebhookEvent, ContentWebhookEventPayload, CustomCollection, DocPage, Faq, FaqCollection, HandleContentWebhookOptions, KvCacheOptions, KvLikeStore, ListOptions, MemoryCacheOptions, Paginated, PublishableListOptions, PullModule, PullOptions, PulledContent, RedisCacheOptions, RedisLikeClient, RequestContext, RichContent, Seo, ServerClient, ServerClientOptions, Testimonial };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(t={}){const e=Math.max(1,t.maxEntries??1e3),n=new Map,o=new Map,a=t=>{const e=n.get(t);e&&(n.delete(t),((t,e)=>{for(const n of e.tags){const e=o.get(n);e&&(e.delete(t),0===e.size&&o.delete(n))}})(t,e.entry))};return{async get(t){const e=n.get(t);if(e){if(!(e.expiresAt<=Date.now()))return n.delete(t),n.set(t,e),e.entry;a(t)}},async set(t,s,r){a(t),n.set(t,{entry:s,expiresAt:Date.now()+1e3*r});for(const e of s.tags){let n=o.get(e);n||o.set(e,n=new Set),n.add(t)}for(;n.size>e;){const t=n.keys().next().value;if(void 0===t)break;a(t)}},async delete(t){a(t)},async deleteByTag(t){const e=o.get(t);if(e)for(const t of[...e])a(t)}}}function e(t,e={}){const n=e.prefix??"buildbase:sdk:",o="function"==typeof t.sAdd&&"function"==typeof t.sMembers,a=t=>`${n}e:${t}`,s=t=>`${n}t:${t}`,r=(e,n)=>o?t.sAdd(e,n):t.sadd(e,n),i=e=>0===e.length?Promise.resolve():o?t.del(e):t.del(...e);if(!o&&("function"!=typeof t.sadd||"function"!=typeof t.smembers))throw new Error("redisCache: the client needs set commands (sadd/smembers or sAdd/sMembers)");return{async get(e){const n=await t.get(a(e));if(n)try{return JSON.parse(n)}catch{return}},async set(e,n,i){const c=Math.max(1,Math.ceil(i));await((e,n,a)=>o?t.set(e,n,{EX:a}):t.set(e,n,"EX",a))(a(e),JSON.stringify(n),c);for(const o of n.tags)await r(s(o),e),"function"==typeof t.expire&&await t.expire(s(o),c)},async delete(t){await i([a(t)])},async deleteByTag(e){const n=await(r=s(e),o?t.sMembers(r):t.smembers(r));var r;await i([...n.map(a),s(e)])}}}function n(t,e={}){const n=e.prefix??"buildbase:sdk:",o=t=>`${n}e:${t}`,a=t=>`${n}t:${t}`,s=t=>Math.max(60,Math.ceil(t)),r=async e=>{const n=await t.get(e);if(null!=n){if("string"==typeof n)try{return JSON.parse(n)}catch{return}return n}},i=async(e,n,o)=>{const a=JSON.stringify(n);if("function"==typeof t.put)await t.put(e,a,{expirationTtl:s(o)});else{if("function"!=typeof t.set)throw new Error("kvCache: the binding needs put() or set()");await t.set(e,a,{ex:s(o)})}},c=async e=>{"function"==typeof t.delete?await t.delete(e):"function"==typeof t.del&&await t.del(e)};return{get:async t=>r(o(t)),async set(t,e,n){await i(o(t),e,n);for(const o of e.tags){const e=await r(a(o))??[];e.includes(t)||e.push(t),await i(a(o),e,n)}},async delete(t){await c(o(t))},async deleteByTag(t){const e=await r(a(t))??[];await Promise.all(e.map(t=>c(o(t)))),await c(a(t))}}}function o(t,e,n){if(null!=n)if(Array.isArray(n))n.forEach((n,a)=>o(t,`${e}[${a}]`,n));else if("object"!=typeof n)t.append(e,String(n));else for(const[a,s]of Object.entries(n))o(t,`${e}[${a}]`,s)}function a(t){if(!t)return"";const e=new URLSearchParams;for(const n of Object.keys(t).sort())o(e,n,t[n]);e.sort();const n=e.toString();return n?`?${n}`:""}const s=["blog","doc","faq","faq_collection","testimonial","rich_content","collection"],r="buildbase:all",i={module:t=>`buildbase:${t}`,slug:(t,e)=>`buildbase:${t}:${e}`,id:(t,e)=>`buildbase:${t}:id:${e}`,collection:t=>`buildbase:collection:${t}`,all:r},c=["blog.published","blog.updated","blog.unpublished","blog.deleted","doc.published","doc.updated","doc.unpublished","doc.deleted","faq.updated","faq_collection.updated","testimonial.updated","testimonial.deleted","rich_content.updated","rich_content.deleted","collection.version_published","collection.record_created","collection.record_updated","collection.record_deleted"],l=new Set(c);function u(t){return l.has(t)}function d(t,e){if(e&&Array.isArray(e.tags)&&e.tags.length>0)return[...new Set(e.tags.filter(t=>"string"==typeof t))];const n=e?.module??t.split(".")[0];if(!s.includes(n))return[];const o=[i.module(n)];return e?.slug&&o.push(i.slug(n,e.slug)),e?.id&&o.push(i.id(n,e.id)),"faq.updated"===t&&o.push(i.module("faq_collection")),o}const f="@buildbase/sdk/server holds your org API token and must only run on a server. Import it from server code (an API route, a server component, a build script), never from code that ships to the browser.";class h extends Error{constructor(t,e,n,o){super(t),this.status=e,this.url=n,this.body=o,this.name="BuildBaseContentError"}}const g=/^[0-9a-f]{24}$/i,p=new WeakMap;function y(t){return t&&"object"==typeof t?p.get(t)??[]:[]}function w(t){return Array.isArray(t)?t:t&&"object"==typeof t&&Array.isArray(t.docs)?t.docs:[]}function m(t){const e=t.split("/").filter(Boolean);return e[e.length-1]??t}function b(t={},e={}){const n={...e,...t.filter};return{$page:t.page,$limit:t.limit,filter:Object.keys(n).length>0?n:void 0,sort:t.sort}}function v(e){if("undefined"!=typeof window&&"undefined"!=typeof document)throw new Error(f);if(!e||"string"!=typeof e.serverUrl||!e.serverUrl)throw new Error("createServerClient: `serverUrl` is required");if("string"!=typeof e.apiToken||!e.apiToken.includes(":"))throw new Error("createServerClient: `apiToken` must be an org API token of the form <orgId>:<secret> (console, Settings, API tokens)");const n=e.serverUrl.replace(/\/+$/,""),o=e.apiToken,s=o.slice(0,o.indexOf(":")),c=!1===e.cache?null:e.cache??t(),l=e.fetch??((t,e)=>globalThis.fetch(t,e)),u=e.timeoutMs??1e4,d=e.defaultTtlSeconds??60,y=Math.max(e.retainSeconds??86400,1),v=e.staleOnError??!0,$=new Map,q=(t,e)=>(t&&"object"==typeof t&&p.set(t,e),t);async function A(t,a,s){const r="function"==typeof e.requestInit?e.requestInit({path:t,tags:a}):e.requestInit??{},i=new Headers(r.headers);i.set("Authorization",`Bearer ${o}`),i.set("Accept","application/json"),s&&i.set("If-None-Match",s);const c=new AbortController,d=setTimeout(()=>c.abort(),u);try{return await l(`${n}${t}`,{...r,method:"GET",headers:i,signal:r.signal??c.signal})}finally{clearTimeout(d)}}async function x(t){const e=`${t.path}${a(t.query)}`,o=`${s}:${e}`,i=c?await c.get(o):void 0,l=Date.now();if(i&&i.freshUntil>l)return q(i.data,i.tags);const u=()=>{const a=async function(){let a;try{a=await A(e,t.tags,i?.etag),304!==a.status||i||(a=await A(e,t.tags))}catch(t){if(v&&i)return q(i.data,i.tags);throw new h(`BuildBase request failed: ${t instanceof Error?t.message:String(t)}`,0,`${n}${e}`)}const s=function(t,e){const n=t.headers.get("cache-control");if(!n)return e;const o=n.toLowerCase().split(",").map(t=>t.trim());if(o.includes("no-store"))return null;if(o.includes("no-cache"))return 0;const a=o.find(t=>t.startsWith("max-age="));if(a){const t=Number(a.slice(8));if(Number.isFinite(t)&&t>=0)return t}return e}(a,d),l=function(t){const e=t.headers.get("cache-control");if(!e)return 0;const n=e.toLowerCase().split(",").map(t=>t.trim()).find(t=>t.startsWith("stale-while-revalidate=")),o=n?Number(n.slice(23)):0;return Number.isFinite(o)&&o>0?o:0}(a);if(304===a.status&&i){const t={...i,etag:a.headers.get("etag")??i.etag,freshUntil:Date.now()+1e3*(s??0),staleUntil:l>0?Date.now()+1e3*((s??0)+l):void 0};return c&&await c.set(o,t,y),q(i.data,i.tags)}if(404===a.status&&t.nullOnNotFound)return null;if(!a.ok){const o=await a.text().catch(()=>"");let s=o;try{s=o?JSON.parse(o):void 0}catch{}if(v&&i&&401!==a.status&&403!==a.status)return q(i.data,i.tags);const r=s?.message??`BuildBase responded ${a.status} for ${t.path}`;throw new h(r,a.status,`${n}${e}`,s)}const u=(f=await a.json().catch(()=>null),f&&"object"==typeof f&&!Array.isArray(f)&&"data"in f&&"success"in f&&!("docs"in f)?f.data:f);var f;if(null==u&&t.nullOnNotFound)return null;const g=t.transform?t.transform(u):u,p=[...new Set([...t.tags,...t.tagsFrom?t.tagsFrom(g):[],r])];c&&null!==s&&await c.set(o,{data:g,etag:a.headers.get("etag")??void 0,freshUntil:Date.now()+1e3*s,staleUntil:l>0?Date.now()+1e3*(s+l):void 0,tags:p},Math.max(y,s));return q(g,p)}();return $.set(o,a),a.then(()=>$.delete(o),()=>$.delete(o)),a};if(i&&void 0!==i.staleUntil&&i.staleUntil>l)return $.has(o)||u().catch(()=>{}),q(i.data,i.tags);const f=$.get(o);return f||u()}const U=t=>e=>{const n=[];return e?._id&&n.push(i.id(t,e._id)),e?.slug&&n.push(i.slug(t,e.slug)),n};function S(t,e){const n=i.module(e);return{list:(e={})=>x({path:`/api/${t}`,query:b(e,e.includeDrafts?{}:{published:!0}),tags:[n]}),get:n=>x({path:`/api/${t}/${encodeURIComponent(n)}`,tags:[i.id(e,n)],tagsFrom:U(e),nullOnNotFound:!0}),byPath:(n,o={})=>x({path:`/api/${t}/path`,query:{path:n,draftPreview:!!o.draftPreview||void 0},tags:[i.slug(e,m(n))],tagsFrom:U(e),nullOnNotFound:!0}),tree:(e={})=>x({path:`/api/${t}/folders/tree`,query:{folder:e.folder},tags:[n]}),tags:(e={})=>x({path:`/api/${t}/tags`,query:b(e),tags:[n]}),authors:(e={})=>x({path:`/api/${t}/authors`,query:b(e),tags:[n]})}}const N=i.module("faq_collection");async function O(t){const e=g.test(t);return x({path:e?`/api/faqs/collections/${encodeURIComponent(t)}`:`/api/faqs/collections/slug/${encodeURIComponent(t)}`,tags:[e?i.id("faq_collection",t):i.slug("faq_collection",t)],tagsFrom:U("faq_collection"),nullOnNotFound:!0})}return{blogs:S("blogs","blog"),docs:S("docs","doc"),faqs:{collections:(t={})=>x({path:"/api/faqs/collections",query:b(t,{archived:!1}),tags:[N]}),collection:O,async questions(t){const e=g.test(t);if(e&&!await O(t))return[];return await x({path:e?`/api/faqs/collections/${encodeURIComponent(t)}/faqs`:`/api/faqs/collections/slug/${encodeURIComponent(t)}/faqs`,query:{pagination:!1},tags:[i.module("faq"),e?i.id("faq_collection",t):i.slug("faq_collection",t)],transform:t=>w(t).filter(t=>!t.archived),nullOnNotFound:!0})??[]}},testimonials:{list:(t={})=>x({path:"/api/testimonials",query:b(t,t.includeUnpublished?{archived:!1}:{published:!0,archived:!1}),tags:[i.module("testimonial")]}),get:t=>x({path:`/api/testimonials/${encodeURIComponent(t)}`,tags:[i.id("testimonial",t)],nullOnNotFound:!0})},richContent:{get:t=>x({path:`/api/rich-content/slug/${encodeURIComponent(t)}`,tags:[i.slug("rich_content",t)],tagsFrom:U("rich_content"),nullOnNotFound:!0}),list:(t={})=>x({path:"/api/rich-content",query:b(t,{archived:!1}),tags:[i.module("rich_content")]})},collections:{list:(t={})=>x({path:"/api/collections",query:b(t,{archived:!1}),tags:[i.module("collection")]}),data(t,e={}){const n=void 0===e.version;return x({path:`/api/collections/data/${encodeURIComponent(t)}`,query:n?{latest:!0}:{version:e.version},tags:[i.collection(t)],transform:t=>w(t).filter(t=>!t.archived)})}},async revalidate(t){c&&await c.deleteByTag(t)},async revalidateAll(){c&&await c.deleteByTag(r)},cache:c}}const $=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),q=(t,e)=>t>>>e|t<<32-e;function A(t){let e=1779033703,n=3144134277,o=1013904242,a=2773480762,s=1359893119,r=2600822924,i=528734635,c=1541459225;const l=t.length,u=l+1,d=u+(56-u%64+64)%64+8,f=new Uint8Array(d);f.set(t,0),f[l]=128;const h=8*l,g=Math.floor(h/4294967296),p=h>>>0;f[d-8]=g>>>24&255,f[d-7]=g>>>16&255,f[d-6]=g>>>8&255,f[d-5]=255&g,f[d-4]=p>>>24&255,f[d-3]=p>>>16&255,f[d-2]=p>>>8&255,f[d-1]=255&p;const y=new Uint32Array(64);for(let t=0;t<d;t+=64){for(let e=0;e<16;e++){const n=t+4*e;y[e]=f[n]<<24|f[n+1]<<16|f[n+2]<<8|f[n+3]}for(let t=16;t<64;t++){const e=q(y[t-15],7)^q(y[t-15],18)^y[t-15]>>>3,n=q(y[t-2],17)^q(y[t-2],19)^y[t-2]>>>10;y[t]=y[t-16]+e+y[t-7]+n>>>0}let l=e,u=n,d=o,h=a,g=s,p=r,w=i,m=c;for(let t=0;t<64;t++){const e=m+(q(g,6)^q(g,11)^q(g,25))+(g&p^~g&w)+$[t]+y[t]>>>0,n=l&u^l&d^u&d;m=w,w=p,p=g,g=h+e>>>0,h=d,d=u,u=l,l=e+((q(l,2)^q(l,13)^q(l,22))+n>>>0)>>>0}e=e+l>>>0,n=n+u>>>0,o=o+d>>>0,a=a+h>>>0,s=s+g>>>0,r=r+p>>>0,i=i+w>>>0,c=c+m>>>0}const w=new Uint8Array(32),m=[e,n,o,a,s,r,i,c];for(let t=0;t<8;t++)w[4*t]=m[t]>>>24&255,w[4*t+1]=m[t]>>>16&255,w[4*t+2]=m[t]>>>8&255,w[4*t+3]=255&m[t];return w}function x(t){return(new TextEncoder).encode(t)}function U(t,e){const n=new Uint8Array(t.length+e.length);return n.set(t,0),n.set(e,t.length),n}function S(t){const{body:e,signature:n,timestamp:o,secret:a,maxAgeSeconds:s=300}=t;if(!(n&&o&&a&&e))return!1;const r="sha256=";if(!n.startsWith(r))return!1;const i=n.slice(7);if(s>0){const t=parseInt(o,10);if(isNaN(t))return!1;if(Math.abs(Math.floor(Date.now()/1e3)-t)>s)return!1}const c=function(t){if(t.length%2!=0)return null;const e=new Uint8Array(t.length/2);for(let n=0;n<e.length;n++){const o=parseInt(t.slice(2*n,2*n+2),16);if(Number.isNaN(o))return null;e[n]=o}return e}(i);if(!c)return!1;const l=function(t,e){let n=t;n.length>64&&(n=A(n));const o=new Uint8Array(64),a=new Uint8Array(64);for(let t=0;t<64;t++){const e=t<n.length?n[t]:0;o[t]=54^e,a[t]=92^e}const s=A(U(o,e));return A(U(a,s))}(x(a),x(`${o}.${e}`));return c.length===l.length&&function(t,e){if(t.length!==e.length)return!1;let n=0;for(let o=0;o<t.length;o++)n|=t[o]^e[o];return 0===n}(c,l)}const N=(t,e)=>new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}});async function O(t,e){if("POST"!==t.method)return N(405,{error:"method_not_allowed"});const n=function(t){if(!S(t))return null;try{return JSON.parse(t.body)}catch{return null}}({body:await t.text(),signature:t.headers.get("x-buildbase-signature"),timestamp:t.headers.get("x-buildbase-timestamp"),secret:e.secret,maxAgeSeconds:e.maxAgeSeconds??300});if(!n)return N(401,{error:"invalid_signature"});if(!u(n.event))return N(200,{ignored:!0,event:n.event});const o=d(n.event,n.data);return e.cache&&await Promise.all(o.map(t=>e.cache.deleteByTag(t))),e.revalidate&&await e.revalidate(o),e.onEvent&&await e.onEvent(n,o),N(200,{revalidated:o})}function E(t){return e=>O(e,t)}function T(t){return t instanceof Uint8Array}function _(t){return async(e,n)=>{let o;try{const n=await async function(t){for(const e of[t.rawBody,t.body]){if("string"==typeof e)return e;if(T(e))return(new TextDecoder).decode(e)}if(t.body&&"object"==typeof t.body)throw new Error("toNodeHandler: the body was already parsed as JSON. Mount the webhook route with a raw body (Express: express.raw({ type: 'application/json' }); Fastify: a raw-body content-type parser).");return!t.on||t.readableEnded?"":new Promise((e,n)=>{const o=[];t.on("data",t=>o.push("string"==typeof t?(new TextEncoder).encode(t):t)),t.on("end",()=>{const t=o.reduce((t,e)=>t+e.length,0),n=new Uint8Array(t);let a=0;for(const t of o)n.set(t,a),a+=t.length;e((new TextDecoder).decode(n))}),t.on("error",n)})}(e),a=new Headers;for(const[t,n]of Object.entries(e.headers))void 0!==n&&a.set(t,Array.isArray(n)?n.join(", "):n);const s=a.get("host")??"localhost",r=e.method??"POST";o=await t(new Request(`http://${s}${e.originalUrl??e.url??"/"}`,{method:r,headers:a,body:"GET"===r||"HEAD"===r?void 0:n}))}catch(t){o=new Response(JSON.stringify({error:t instanceof Error?t.message:String(t)}),{status:400,headers:{"Content-Type":"application/json"}})}n.statusCode=o.status,o.headers.forEach((t,e)=>n.setHeader(e,t)),n.end(new Uint8Array(await o.arrayBuffer()))}}const C=["blogs","docs","faqs","testimonials","richContent","collections"];async function I(t){const e=[];for(let n=1;n<=1e4;n++){const o=await t(n);e.push(...o.docs);if(!(o.hasNextPage??(void 0!==o.totalPages&&n<o.totalPages))||0===o.docs.length)break}return e}async function M(t,e={}){const n=new Set(e.modules??C),o=e.pageSize??100,a={generatedAt:(new Date).toISOString()};if(n.has("blogs")&&(a.blogs=await I(e=>t.blogs.list({page:e,limit:o}))),n.has("docs")&&(a.docs=await I(e=>t.docs.list({page:e,limit:o}))),n.has("faqs")){const e=await I(e=>t.faqs.collections({page:e,limit:o}));a.faqs=[];for(const n of e)a.faqs.push({...n,questions:await t.faqs.questions(n._id)})}if(n.has("testimonials")&&(a.testimonials=await I(e=>t.testimonials.list({page:e,limit:o}))),n.has("richContent")&&(a.richContent=await I(e=>t.richContent.list({page:e,limit:o}))),n.has("collections")){const n=e.collections??(await I(e=>t.collections.list({page:e,limit:o}))).map(t=>t.slug);a.collections={};for(const e of n)a.collections[e]=await t.collections.data(e)}return a}export{r as ALL_TAG,h as BuildBaseContentError,s as CONTENT_MODULES,c as CONTENT_WEBHOOK_EVENTS,C as PULL_MODULES,f as SERVER_ONLY_MESSAGE,i as contentTags,E as createContentWebhookHandler,v as createServerClient,a as encodeQuery,y as getContentTags,O as handleContentWebhook,u as isContentWebhookEvent,n as kvCache,t as memoryCache,M as pullContent,e as redisCache,d as tagsForContentEvent,_ as toNodeHandler};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const t=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),e=(t,e)=>t>>>e|t<<32-e;function n(n){let r=1779033703,o=3144134277,i=1013904242,a=2773480762,s=1359893119,c=2600822924,l=528734635,u=1541459225;const d=n.length,f=d+1,h=f+(56-f%64+64)%64+8,g=new Uint8Array(h);g.set(n,0),g[d]=128;const p=8*d,b=Math.floor(p/4294967296),w=p>>>0;g[h-8]=b>>>24&255,g[h-7]=b>>>16&255,g[h-6]=b>>>8&255,g[h-5]=255&b,g[h-4]=w>>>24&255,g[h-3]=w>>>16&255,g[h-2]=w>>>8&255,g[h-1]=255&w;const y=new Uint32Array(64);for(let n=0;n<h;n+=64){for(let t=0;t<16;t++){const e=n+4*t;y[t]=g[e]<<24|g[e+1]<<16|g[e+2]<<8|g[e+3]}for(let t=16;t<64;t++){const n=e(y[t-15],7)^e(y[t-15],18)^y[t-15]>>>3,r=e(y[t-2],17)^e(y[t-2],19)^y[t-2]>>>10;y[t]=y[t-16]+n+y[t-7]+r>>>0}let d=r,f=o,h=i,p=a,b=s,w=c,m=l,v=u;for(let n=0;n<64;n++){const r=v+(e(b,6)^e(b,11)^e(b,25))+(b&w^~b&m)+t[n]+y[n]>>>0,o=d&f^d&h^f&h;v=m,m=w,w=b,b=p+r>>>0,p=h,h=f,f=d,d=r+((e(d,2)^e(d,13)^e(d,22))+o>>>0)>>>0}r=r+d>>>0,o=o+f>>>0,i=i+h>>>0,a=a+p>>>0,s=s+b>>>0,c=c+w>>>0,l=l+m>>>0,u=u+v>>>0}const m=new Uint8Array(32),v=[r,o,i,a,s,c,l,u];for(let t=0;t<8;t++)m[4*t]=v[t]>>>24&255,m[4*t+1]=v[t]>>>16&255,m[4*t+2]=v[t]>>>8&255,m[4*t+3]=255&v[t];return m}function r(t){return(new TextEncoder).encode(t)}function o(t,e){const n=new Uint8Array(t.length+e.length);return n.set(t,0),n.set(e,t.length),n}function i(t){const{body:e,signature:i,timestamp:a,secret:s,maxAgeSeconds:c=300}=t;if(!(i&&a&&s&&e))return!1;const l="sha256=";if(!i.startsWith(l))return!1;const u=i.slice(7);if(c>0){const t=parseInt(a,10);if(isNaN(t))return!1;if(Math.abs(Math.floor(Date.now()/1e3)-t)>c)return!1}const d=function(t){if(t.length%2!=0)return null;const e=new Uint8Array(t.length/2);for(let n=0;n<e.length;n++){const r=parseInt(t.slice(2*n,2*n+2),16);if(Number.isNaN(r))return null;e[n]=r}return e}(u);if(!d)return!1;const f=function(t,e){let r=t;r.length>64&&(r=n(r));const i=new Uint8Array(64),a=new Uint8Array(64);for(let t=0;t<64;t++){const e=t<r.length?r[t]:0;i[t]=54^e,a[t]=92^e}const s=n(o(i,e));return n(o(a,s))}(r(s),r(`${a}.${e}`));return d.length===f.length&&function(t,e){if(t.length!==e.length)return!1;let n=0;for(let r=0;r<t.length;r++)n|=t[r]^e[r];return 0===n}(d,f)}const a=["blog","doc","faq","faq_collection","testimonial","rich_content","collection"],s=t=>`buildbase:${t}`,c=(t,e)=>`buildbase:${t}:${e}`,l=(t,e)=>`buildbase:${t}:id:${e}`,u=new Set(["blog.published","blog.updated","blog.unpublished","blog.deleted","doc.published","doc.updated","doc.unpublished","doc.deleted","faq.updated","faq_collection.updated","testimonial.updated","testimonial.deleted","rich_content.updated","rich_content.deleted","collection.version_published","collection.record_created","collection.record_updated","collection.record_deleted"]);const d=(t,e)=>new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}});async function f(t,e){if("POST"!==t.method)return d(405,{error:"method_not_allowed"});const n=function(t){if(!i(t))return null;try{return JSON.parse(t.body)}catch{return null}}({body:await t.text(),signature:t.headers.get("x-buildbase-signature"),timestamp:t.headers.get("x-buildbase-timestamp"),secret:e.secret,maxAgeSeconds:e.maxAgeSeconds??300});if(!n)return d(401,{error:"invalid_signature"});if(!function(t){return u.has(t)}(n.event))return d(200,{ignored:!0,event:n.event});const r=function(t,e){if(e&&Array.isArray(e.tags)&&e.tags.length>0)return[...new Set(e.tags.filter(t=>"string"==typeof t))];const n=e?.module??t.split(".")[0];if(!a.includes(n))return[];const r=[s(n)];return e?.slug&&r.push(c(n,e.slug)),e?.id&&r.push(l(n,e.id)),"faq.updated"===t&&r.push(s("faq_collection")),r}(n.event,n.data);return e.cache&&await Promise.all(r.map(t=>e.cache.deleteByTag(t))),e.revalidate&&await e.revalidate(r),e.onEvent&&await e.onEvent(n,r),d(200,{revalidated:r})}function h(t,e){return{next:void 0===e?{tags:t}:{tags:t,revalidate:e}}}exports.createNextWebhookHandler=function(t){return e=>f(e,{...t,async revalidate(t){const{revalidateTag:e}=await import("next/cache");for(const n of t)e(n)}})},exports.nextFetchOptions=h,exports.withNextCache=function(t={}){const e=t.revalidate??300;return{cache:!1,requestInit:t=>h(t.tags,e)}};
|