@plenary/client 1.0.1 → 1.1.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/dist/index.cjs CHANGED
@@ -30,11 +30,13 @@ __export(index_exports, {
30
30
  buildListParams: () => buildListParams,
31
31
  buildReadParams: () => buildReadParams,
32
32
  createClient: () => createClient,
33
+ entryUrl: () => entryUrl,
33
34
  errorFromResponse: () => errorFromResponse,
34
35
  isEntryWebhookPayload: () => isEntryWebhookPayload,
35
36
  isFormWebhookPayload: () => isFormWebhookPayload,
36
37
  parseWebhookPayload: () => parseWebhookPayload,
37
38
  parseWebhookRequest: () => parseWebhookRequest,
39
+ planRevalidation: () => planRevalidation,
38
40
  verifyWebhookSecret: () => verifyWebhookSecret
39
41
  });
40
42
  module.exports = __toCommonJS(index_exports);
@@ -51,7 +53,8 @@ var OPERATORS = /* @__PURE__ */ new Set([
51
53
  "$nin",
52
54
  "$like",
53
55
  "$ilike",
54
- "$is_null"
56
+ "$is_null",
57
+ "$under"
55
58
  ]);
56
59
  function isCondition(value) {
57
60
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -315,6 +318,31 @@ async function parseWebhookRequest(request, options) {
315
318
  });
316
319
  return parseWebhookPayload(body);
317
320
  }
321
+ function entryUrl(payload) {
322
+ const { slug, path } = payload.entry;
323
+ if (!slug) return null;
324
+ const dir = path && path !== "/" ? path : "";
325
+ if (!dir && slug === "home") return "/";
326
+ return `${dir}/${slug}`;
327
+ }
328
+ function planRevalidation(payload, options) {
329
+ const none = { relevant: false, test: false, paths: [] };
330
+ if (!isEntryWebhookPayload(payload)) return none;
331
+ const test = payload.event === WEBHOOK_TEST_EVENT;
332
+ if (!test && options.events && !options.events.includes(payload.event)) {
333
+ return none;
334
+ }
335
+ const modelKey = payload.model.key;
336
+ const mapped = modelKey === void 0 ? void 0 : options.paths?.[modelKey];
337
+ let paths;
338
+ if (mapped === void 0) {
339
+ paths = (options.entryUrl ?? entryUrl)(payload);
340
+ } else {
341
+ paths = typeof mapped === "function" ? mapped(payload) : mapped;
342
+ }
343
+ const pathList = paths == null ? [] : typeof paths === "string" ? [paths] : paths;
344
+ return { relevant: true, test, paths: [...new Set(pathList)] };
345
+ }
318
346
  // Annotate the CommonJS export names for ESM import in node:
319
347
  0 && (module.exports = {
320
348
  ENTRY_WEBHOOK_EVENTS,
@@ -327,11 +355,13 @@ async function parseWebhookRequest(request, options) {
327
355
  buildListParams,
328
356
  buildReadParams,
329
357
  createClient,
358
+ entryUrl,
330
359
  errorFromResponse,
331
360
  isEntryWebhookPayload,
332
361
  isFormWebhookPayload,
333
362
  parseWebhookPayload,
334
363
  parseWebhookRequest,
364
+ planRevalidation,
335
365
  verifyWebhookSecret
336
366
  });
337
367
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/query.ts","../src/errors.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["/**\n * @plenary/client — the framework-free Plenary content client: published-read\n * content fetch, public form submission, and a typed query builder. Configured\n * with a public delivery API key; no admin endpoints.\n */\n\nexport { createClient, type PlenaryClient } from \"./client.js\";\nexport {\n buildReadParams,\n buildListParams,\n type QueryBase,\n} from \"./query.js\";\nexport {\n PlenaryError,\n PlenaryNotFoundError,\n PlenaryValidationError,\n PlenaryForbiddenError,\n errorFromResponse,\n} from \"./errors.js\";\nexport {\n ENTRY_WEBHOOK_EVENTS,\n WEBHOOK_TEST_EVENT,\n PlenaryWebhookError,\n verifyWebhookSecret,\n parseWebhookPayload,\n parseWebhookRequest,\n isEntryWebhookPayload,\n isFormWebhookPayload,\n type EntryWebhookEvent,\n type WebhookActor,\n type WebhookEntryRef,\n type WebhookFormEntryRef,\n type EntryWebhookPayload,\n type FormWebhookPayload,\n type WebhookPayload,\n type ParseWebhookOptions,\n} from \"./webhook.js\";\nexport type {\n ClientConfig,\n Entry,\n Where,\n WhereCondition,\n FilterTree,\n Sort,\n SortDirection,\n Hydrate,\n ReadOptions,\n ListOptions,\n ListResult,\n PublicFormFieldType,\n PublicFormField,\n PublicFormDefinition,\n FormSubmitOptions,\n FormSubmitResult,\n MediaVariant,\n MediaDetail,\n} from \"./types.js\";\n","import type {\n Where,\n WhereCondition,\n FilterTree,\n Sort,\n Hydrate,\n ReadOptions,\n ListOptions,\n} from \"./types.js\";\n\n/** The known `where:` operators; anything else on a condition object is passed through. */\nconst OPERATORS = new Set([\n \"$eq\",\n \"$ne\",\n \"$lt\",\n \"$lte\",\n \"$gt\",\n \"$gte\",\n \"$in\",\n \"$nin\",\n \"$like\",\n \"$ilike\",\n \"$is_null\",\n]);\n\n/** A value is a condition when it's a plain object whose keys are all operators. */\nfunction isCondition(value: unknown): value is WhereCondition {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const keys = Object.keys(value);\n return keys.length > 0 && keys.every((k) => OPERATORS.has(k));\n}\n\nfunction encodeOperatorValue(op: string, value: unknown): string {\n if (op === \"$in\" || op === \"$nin\") {\n return Array.isArray(value) ? value.map(String).join(\",\") : String(value);\n }\n if (op === \"$is_null\") {\n return String(Boolean(value));\n }\n return String(value);\n}\n\nfunction appendWhere(params: URLSearchParams, where: Where): void {\n for (const [field, value] of Object.entries(where)) {\n if (isCondition(value)) {\n for (const [op, opValue] of Object.entries(value)) {\n params.append(`where:${field}.${op}`, encodeOperatorValue(op, opValue));\n }\n } else {\n // Bare value — the server assumes `$eq`.\n params.append(`where:${field}`, String(value));\n }\n }\n}\n\nfunction appendSort(params: URLSearchParams, sort: Sort): void {\n // Insertion order is the sort precedence (left-to-right).\n for (const [field, dir] of Object.entries(sort)) {\n params.append(`sort:${field}`, dir);\n }\n}\n\nfunction appendHydrate(params: URLSearchParams, hydrate: Hydrate): void {\n for (const [field, spec] of Object.entries(hydrate)) {\n params.append(`hydrate:${field}`, spec === \"*\" ? \"*\" : spec.join(\",\"));\n }\n}\n\n/** Base context every request carries: which site and locale. */\nexport interface QueryBase {\n siteKey: string;\n locale: string;\n}\n\nfunction appendCommon(params: URLSearchParams, base: QueryBase, opts: ReadOptions): void {\n params.set(\"site_key\", opts.siteKey ?? base.siteKey);\n params.set(\"locale\", opts.locale ?? base.locale);\n\n if (opts.include && opts.exclude) {\n throw new Error(\"plenary: `include` and `exclude` are mutually exclusive\");\n }\n if (opts.include?.length) params.set(\"include\", opts.include.join(\",\"));\n if (opts.exclude?.length) params.set(\"exclude\", opts.exclude.join(\",\"));\n if (opts.hydrate) appendHydrate(params, opts.hydrate);\n}\n\n/** Builds the query for a single-entry read (get / singleton). */\nexport function buildReadParams(base: QueryBase, opts: ReadOptions = {}): URLSearchParams {\n const params = new URLSearchParams();\n appendCommon(params, base, opts);\n return params;\n}\n\n/** Builds the query for a list read, including filters, sort, and pagination. */\nexport function buildListParams(base: QueryBase, opts: ListOptions = {}): URLSearchParams {\n const params = new URLSearchParams();\n appendCommon(params, base, opts);\n\n if (opts.where && opts.filters) {\n throw new Error(\"plenary: `where` and `filters` are mutually exclusive\");\n }\n if (opts.filters) {\n params.set(\"filters\", JSON.stringify(opts.filters satisfies FilterTree));\n } else if (opts.where) {\n appendWhere(params, opts.where);\n }\n\n if (opts.sort) appendSort(params, opts.sort);\n if (opts.limit !== undefined) params.set(\"limit\", String(opts.limit));\n if (opts.offset !== undefined) params.set(\"offset\", String(opts.offset));\n if (opts.count) params.set(\"count\", \"true\");\n\n return params;\n}\n","/** The server's error envelope: `{ error: { code, vars? } }`. */\ninterface ServerErrorBody {\n error?: { code?: string; vars?: Record<string, unknown> };\n}\n\n/** Base error for a non-2xx response from the CMS. */\nexport class PlenaryError extends Error {\n readonly status: number;\n /** The server's i18n error code (e.g. `entry_query_limit_exceeded`), if any. */\n readonly code: string;\n /** Interpolation variables the server attached to the code. */\n readonly vars?: Record<string, unknown>;\n\n constructor(status: number, code: string, vars?: Record<string, unknown>) {\n super(`Plenary request failed (${status}): ${code}`);\n this.name = \"PlenaryError\";\n this.status = status;\n this.code = code;\n this.vars = vars;\n }\n}\n\n/** A 404 — model or entry not found (or hidden by a private model). */\nexport class PlenaryNotFoundError extends PlenaryError {\n constructor(code: string, vars?: Record<string, unknown>) {\n super(404, code, vars);\n this.name = \"PlenaryNotFoundError\";\n }\n}\n\n/** A 400/422 — malformed query or invalid input. */\nexport class PlenaryValidationError extends PlenaryError {\n constructor(status: number, code: string, vars?: Record<string, unknown>) {\n super(status, code, vars);\n this.name = \"PlenaryValidationError\";\n }\n}\n\n/** A 403 — the key may not read the requested site/resource. */\nexport class PlenaryForbiddenError extends PlenaryError {\n constructor(code: string, vars?: Record<string, unknown>) {\n super(403, code, vars);\n this.name = \"PlenaryForbiddenError\";\n }\n}\n\n/** Builds the right error subclass from a failed response's status and body. */\nexport function errorFromResponse(status: number, body: unknown): PlenaryError {\n const parsed = body as ServerErrorBody | null;\n const code = parsed?.error?.code ?? `http_${status}`;\n const vars = parsed?.error?.vars;\n switch (status) {\n case 404:\n return new PlenaryNotFoundError(code, vars);\n case 403:\n return new PlenaryForbiddenError(code, vars);\n case 400:\n case 422:\n return new PlenaryValidationError(status, code, vars);\n default:\n return new PlenaryError(status, code, vars);\n }\n}\n","import type {\n ClientConfig,\n Entry,\n ReadOptions,\n ListOptions,\n ListResult,\n PublicFormDefinition,\n FormSubmitOptions,\n FormSubmitResult,\n MediaDetail,\n} from \"./types.js\";\nimport { buildReadParams, buildListParams, type QueryBase } from \"./query.js\";\nimport { errorFromResponse } from \"./errors.js\";\n\n/** The content client returned by {@link createClient}. */\nexport interface PlenaryClient {\n /** Fetch a singleton model's entry. */\n getSingleton<TData = Record<string, unknown>>(\n modelKey: string,\n opts?: ReadOptions,\n ): Promise<Entry<TData>>;\n\n /** Fetch one entry of a collection/page by slug or UUID. */\n getEntry<TData = Record<string, unknown>>(\n modelKey: string,\n slugOrId: string,\n opts?: ReadOptions,\n ): Promise<Entry<TData>>;\n\n /** List entries of a collection/page model. */\n listEntries<TData = Record<string, unknown>>(\n modelKey: string,\n opts?: ListOptions,\n ): Promise<ListResult<TData>>;\n\n /**\n * Fetch a form's render-safe definition — the field list a renderer needs,\n * readable with a public delivery key.\n */\n getForm(formKey: string): Promise<PublicFormDefinition>;\n\n /** Submit a public form. */\n submitForm(\n formKey: string,\n data: Record<string, unknown>,\n opts?: FormSubmitOptions,\n ): Promise<FormSubmitResult>;\n\n /**\n * Fetch a media item's detail (URLs + variants). Requires a key that may read\n * media; a public delivery key cannot, so this is used in authoring/preview\n * contexts or by sites configured with a media-capable key.\n */\n getMedia(mediaId: string): Promise<MediaDetail>;\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}${path}`;\n}\n\nexport function createClient(config: ClientConfig): PlenaryClient {\n const base: QueryBase = { siteKey: config.siteKey, locale: config.locale };\n const doFetch = config.fetch ?? globalThis.fetch;\n\n async function request<T>(\n method: string,\n path: string,\n params: URLSearchParams,\n body?: unknown,\n ): Promise<{ data: T; response: Response }> {\n const url = `${joinUrl(config.baseUrl, path)}?${params.toString()}`;\n const headers: Record<string, string> = { \"X-API-KEY\": config.apiKey };\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const response = await doFetch(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n const parsed = response.status === 204 ? null : await response.json().catch(() => null);\n if (!response.ok) {\n throw errorFromResponse(response.status, parsed);\n }\n return { data: parsed as T, response };\n }\n\n return {\n async getSingleton<TData>(modelKey: string, opts: ReadOptions = {}) {\n const params = buildReadParams(base, opts);\n const { data } = await request<Entry<TData>>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}`,\n params,\n );\n return data;\n },\n\n async getEntry<TData>(modelKey: string, slugOrId: string, opts: ReadOptions = {}) {\n const params = buildReadParams(base, opts);\n const { data } = await request<Entry<TData>>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}/${encodeURIComponent(slugOrId)}`,\n params,\n );\n return data;\n },\n\n async listEntries<TData>(modelKey: string, opts: ListOptions = {}) {\n const params = buildListParams(base, opts);\n const { data, response } = await request<Entry<TData>[]>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}`,\n params,\n );\n const result: ListResult<TData> = { items: data };\n const total = response.headers.get(\"X-Total-Count\");\n if (total !== null) result.total = Number(total);\n return result;\n },\n\n async getForm(formKey: string) {\n const { data } = await request<PublicFormDefinition>(\n \"GET\",\n `/api/v1/forms/${encodeURIComponent(formKey)}`,\n new URLSearchParams(),\n );\n return data;\n },\n\n async submitForm(\n formKey: string,\n data: Record<string, unknown>,\n opts: FormSubmitOptions = {},\n ) {\n const params = new URLSearchParams();\n params.set(\"site_key\", opts.siteKey ?? base.siteKey);\n const body: Record<string, unknown> = { data };\n if (opts.website !== undefined) body.website = opts.website;\n if (opts.ts !== undefined) body.ts = opts.ts;\n const { data: result } = await request<FormSubmitResult>(\n \"POST\",\n `/api/v1/forms/${encodeURIComponent(formKey)}/entries`,\n params,\n body,\n );\n return result;\n },\n\n async getMedia(mediaId: string) {\n const { data } = await request<MediaDetail>(\n \"GET\",\n `/api/v1/media/${encodeURIComponent(mediaId)}`,\n new URLSearchParams(),\n );\n return data;\n },\n };\n}\n","/**\n * Webhook consumer helpers: the payload types Plenary POSTs\n * and framework-neutral verification/parsing. Plenary does not sign requests\n * yet; verification is a shared-secret comparison against a header or query\n * parameter. When HMAC signing ships, it slots in here without changing the\n * call sites.\n */\n\n/** The five entry lifecycle events a content-model webhook can subscribe to. */\nexport type EntryWebhookEvent =\n | \"entry.created\"\n | \"entry.updated\"\n | \"entry.published\"\n | \"entry.unpublished\"\n | \"entry.deleted\";\n\nexport const ENTRY_WEBHOOK_EVENTS: readonly EntryWebhookEvent[] = [\n \"entry.created\",\n \"entry.updated\",\n \"entry.published\",\n \"entry.unpublished\",\n \"entry.deleted\",\n];\n\n/** The event carried by the console's Test button. The body is otherwise an\n * ordinary entry or form payload, built from the model's most recent entry. */\nexport const WEBHOOK_TEST_EVENT = \"webhook.test\";\n\n/** Who caused the event. `label` survives the actor's deletion. */\nexport interface WebhookActor {\n type: \"user\" | \"api_key\" | \"system\";\n id?: string;\n label: string;\n}\n\n/** The entry's identity. Field selection never touches these. */\nexport interface WebhookEntryRef {\n id: string;\n site_key?: string;\n locale?: string;\n slug?: string;\n /** Directory path of the entry's folder (\"/\" at the model root). Does NOT\n * include the entry's own slug — join them to build a URL. */\n path?: string;\n status?: string;\n created_at?: string;\n updated_at?: string;\n published_at?: string;\n}\n\n/** A form entry's identity: no slug, locale, path, or publish state. */\nexport interface WebhookFormEntryRef {\n id: string;\n site_key?: string;\n status?: string;\n created_at?: string;\n source?: { ip?: string; user_agent?: string; referer?: string };\n}\n\n/** Payload for every `entry.*` event (and entry-shaped `webhook.test`). */\nexport interface EntryWebhookPayload<TData = Record<string, unknown>> {\n event: EntryWebhookEvent | typeof WEBHOOK_TEST_EVENT;\n delivery_id: string;\n occurred_at: string;\n webhook: { id: string; name?: string };\n model: { id: string; key?: string };\n entry: WebhookEntryRef;\n actor: WebhookActor;\n /** Paths that differ from the previous revision. Always present; empty for\n * `entry.created` and `entry.deleted`. */\n changed: string[];\n revision_id?: string;\n /** Field values per the webhook's field selection; absent for \"no field data\". */\n data?: TData;\n}\n\n/** Payload for `form.submitted` (and form-shaped `webhook.test`). */\nexport interface FormWebhookPayload<TData = Record<string, unknown>> {\n event: \"form.submitted\" | typeof WEBHOOK_TEST_EVENT;\n delivery_id: string;\n occurred_at: string;\n webhook: { id: string; name?: string };\n form: { id: string; key?: string };\n form_entry: WebhookFormEntryRef;\n data?: TData;\n}\n\nexport type WebhookPayload = EntryWebhookPayload | FormWebhookPayload;\n\nexport function isEntryWebhookPayload(p: WebhookPayload): p is EntryWebhookPayload {\n return \"entry\" in p;\n}\n\nexport function isFormWebhookPayload(p: WebhookPayload): p is FormWebhookPayload {\n return \"form_entry\" in p;\n}\n\n/** A request that is not a valid webhook delivery. `status` is the HTTP status\n * an endpoint should answer with — 4xx makes the delivery fail fast in the\n * console's delivery log instead of being retried for seven hours. */\nexport class PlenaryWebhookError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = \"PlenaryWebhookError\";\n this.status = status;\n }\n}\n\n/** Constant-time comparison of a provided secret against the expected one.\n * An empty or missing `secret` never matches, so an unset environment\n * variable can't turn into \"accept an empty header\". */\nexport function verifyWebhookSecret(\n provided: string | null | undefined,\n secret: string,\n): boolean {\n if (typeof provided !== \"string\" || !secret) return false;\n const enc = new TextEncoder();\n const a = enc.encode(provided);\n const b = enc.encode(secret);\n let diff = a.length ^ b.length;\n const len = Math.max(a.length, b.length);\n for (let i = 0; i < len; i++) diff |= (a[i] ?? 0) ^ (b[i] ?? 0);\n return diff === 0;\n}\n\n/** Shape-checks a parsed JSON body and types it. Throws {@link PlenaryWebhookError}\n * (400) for anything that is not a Plenary webhook payload. */\nexport function parseWebhookPayload(body: unknown): WebhookPayload {\n const p = body as Record<string, unknown> | null;\n if (\n p === null ||\n typeof p !== \"object\" ||\n typeof p[\"event\"] !== \"string\" ||\n typeof p[\"delivery_id\"] !== \"string\" ||\n !(\"entry\" in p || \"form_entry\" in p)\n ) {\n throw new PlenaryWebhookError(400, \"Not a Plenary webhook payload\");\n }\n return p as unknown as WebhookPayload;\n}\n\n/** Options for {@link parseWebhookRequest}. */\nexport interface ParseWebhookOptions {\n /** The shared secret configured on the webhook (as a secret header or query\n * parameter in the console). */\n secret: string;\n /** Header the secret arrives in. Default `x-plenary-secret`. */\n secretHeader?: string;\n /** Query parameter checked when the header is absent. Default `secret`. */\n secretQuery?: string;\n}\n\n/**\n * Verifies and parses a webhook delivery from a Fetch-API `Request`:\n * POST only, secret in the configured header or query parameter, JSON body of\n * one of the two payload shapes. Throws {@link PlenaryWebhookError} with the\n * status the endpoint should answer (405, 401, or 400).\n */\nexport async function parseWebhookRequest(\n request: Request,\n options: ParseWebhookOptions,\n): Promise<WebhookPayload> {\n if (request.method !== \"POST\") {\n throw new PlenaryWebhookError(405, \"Method not allowed\");\n }\n if (!options.secret) {\n throw new PlenaryWebhookError(500, \"Webhook secret is not configured\");\n }\n const header = options.secretHeader ?? \"x-plenary-secret\";\n const query = options.secretQuery ?? \"secret\";\n const provided =\n request.headers.get(header) ?? new URL(request.url).searchParams.get(query);\n if (!verifyWebhookSecret(provided, options.secret)) {\n throw new PlenaryWebhookError(401, \"Invalid webhook secret\");\n }\n const body = await request.json().catch(() => {\n throw new PlenaryWebhookError(400, \"Body is not valid JSON\");\n });\n return parseWebhookPayload(body);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/query.ts","../src/errors.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["/**\n * @plenary/client — the framework-free Plenary content client: published-read\n * content fetch, public form submission, and a typed query builder. Configured\n * with a public delivery API key; no admin endpoints.\n */\n\nexport { createClient, type PlenaryClient } from \"./client.js\";\nexport {\n buildReadParams,\n buildListParams,\n type QueryBase,\n} from \"./query.js\";\nexport {\n PlenaryError,\n PlenaryNotFoundError,\n PlenaryValidationError,\n PlenaryForbiddenError,\n errorFromResponse,\n} from \"./errors.js\";\nexport {\n ENTRY_WEBHOOK_EVENTS,\n WEBHOOK_TEST_EVENT,\n PlenaryWebhookError,\n verifyWebhookSecret,\n parseWebhookPayload,\n parseWebhookRequest,\n isEntryWebhookPayload,\n isFormWebhookPayload,\n entryUrl,\n planRevalidation,\n type RevalidatePaths,\n type RevalidateOptions,\n type RevalidationPlan,\n type EntryWebhookEvent,\n type WebhookActor,\n type WebhookEntryRef,\n type WebhookFormEntryRef,\n type EntryWebhookPayload,\n type FormWebhookPayload,\n type WebhookPayload,\n type ParseWebhookOptions,\n} from \"./webhook.js\";\nexport type {\n ClientConfig,\n Entry,\n EntryDraft,\n Where,\n WhereCondition,\n FilterTree,\n Sort,\n SortDirection,\n Hydrate,\n ReadOptions,\n ListOptions,\n ListResult,\n PublicFormFieldType,\n PublicFormField,\n PublicFormDefinition,\n FormSubmitOptions,\n FormSubmitResult,\n MediaVariant,\n MediaDetail,\n} from \"./types.js\";\n","import type {\n Where,\n WhereCondition,\n FilterTree,\n Sort,\n Hydrate,\n ReadOptions,\n ListOptions,\n} from \"./types.js\";\n\n/** The known `where:` operators; anything else on a condition object is passed through. */\nconst OPERATORS = new Set([\n \"$eq\",\n \"$ne\",\n \"$lt\",\n \"$lte\",\n \"$gt\",\n \"$gte\",\n \"$in\",\n \"$nin\",\n \"$like\",\n \"$ilike\",\n \"$is_null\",\n \"$under\",\n]);\n\n/** A value is a condition when it's a plain object whose keys are all operators. */\nfunction isCondition(value: unknown): value is WhereCondition {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const keys = Object.keys(value);\n return keys.length > 0 && keys.every((k) => OPERATORS.has(k));\n}\n\nfunction encodeOperatorValue(op: string, value: unknown): string {\n if (op === \"$in\" || op === \"$nin\") {\n return Array.isArray(value) ? value.map(String).join(\",\") : String(value);\n }\n if (op === \"$is_null\") {\n return String(Boolean(value));\n }\n return String(value);\n}\n\nfunction appendWhere(params: URLSearchParams, where: Where): void {\n for (const [field, value] of Object.entries(where)) {\n if (isCondition(value)) {\n for (const [op, opValue] of Object.entries(value)) {\n params.append(`where:${field}.${op}`, encodeOperatorValue(op, opValue));\n }\n } else {\n // Bare value — the server assumes `$eq`.\n params.append(`where:${field}`, String(value));\n }\n }\n}\n\nfunction appendSort(params: URLSearchParams, sort: Sort): void {\n // Insertion order is the sort precedence (left-to-right).\n for (const [field, dir] of Object.entries(sort)) {\n params.append(`sort:${field}`, dir);\n }\n}\n\nfunction appendHydrate(params: URLSearchParams, hydrate: Hydrate): void {\n for (const [field, spec] of Object.entries(hydrate)) {\n params.append(`hydrate:${field}`, spec === \"*\" ? \"*\" : spec.join(\",\"));\n }\n}\n\n/** Base context every request carries: which site and locale. */\nexport interface QueryBase {\n siteKey: string;\n locale: string;\n}\n\nfunction appendCommon(params: URLSearchParams, base: QueryBase, opts: ReadOptions): void {\n params.set(\"site_key\", opts.siteKey ?? base.siteKey);\n params.set(\"locale\", opts.locale ?? base.locale);\n\n if (opts.include && opts.exclude) {\n throw new Error(\"plenary: `include` and `exclude` are mutually exclusive\");\n }\n if (opts.include?.length) params.set(\"include\", opts.include.join(\",\"));\n if (opts.exclude?.length) params.set(\"exclude\", opts.exclude.join(\",\"));\n if (opts.hydrate) appendHydrate(params, opts.hydrate);\n}\n\n/** Builds the query for a single-entry read (get / singleton). */\nexport function buildReadParams(base: QueryBase, opts: ReadOptions = {}): URLSearchParams {\n const params = new URLSearchParams();\n appendCommon(params, base, opts);\n return params;\n}\n\n/** Builds the query for a list read, including filters, sort, and pagination. */\nexport function buildListParams(base: QueryBase, opts: ListOptions = {}): URLSearchParams {\n const params = new URLSearchParams();\n appendCommon(params, base, opts);\n\n if (opts.where && opts.filters) {\n throw new Error(\"plenary: `where` and `filters` are mutually exclusive\");\n }\n if (opts.filters) {\n params.set(\"filters\", JSON.stringify(opts.filters satisfies FilterTree));\n } else if (opts.where) {\n appendWhere(params, opts.where);\n }\n\n if (opts.sort) appendSort(params, opts.sort);\n if (opts.limit !== undefined) params.set(\"limit\", String(opts.limit));\n if (opts.offset !== undefined) params.set(\"offset\", String(opts.offset));\n if (opts.count) params.set(\"count\", \"true\");\n\n return params;\n}\n","/** The server's error envelope: `{ error: { code, vars? } }`. */\ninterface ServerErrorBody {\n error?: { code?: string; vars?: Record<string, unknown> };\n}\n\n/** Base error for a non-2xx response from the CMS. */\nexport class PlenaryError extends Error {\n readonly status: number;\n /** The server's i18n error code (e.g. `entry_query_limit_exceeded`), if any. */\n readonly code: string;\n /** Interpolation variables the server attached to the code. */\n readonly vars?: Record<string, unknown>;\n\n constructor(status: number, code: string, vars?: Record<string, unknown>) {\n super(`Plenary request failed (${status}): ${code}`);\n this.name = \"PlenaryError\";\n this.status = status;\n this.code = code;\n this.vars = vars;\n }\n}\n\n/** A 404 — model or entry not found (or hidden by a private model). */\nexport class PlenaryNotFoundError extends PlenaryError {\n constructor(code: string, vars?: Record<string, unknown>) {\n super(404, code, vars);\n this.name = \"PlenaryNotFoundError\";\n }\n}\n\n/** A 400/422 — malformed query or invalid input. */\nexport class PlenaryValidationError extends PlenaryError {\n constructor(status: number, code: string, vars?: Record<string, unknown>) {\n super(status, code, vars);\n this.name = \"PlenaryValidationError\";\n }\n}\n\n/** A 403 — the key may not read the requested site/resource. */\nexport class PlenaryForbiddenError extends PlenaryError {\n constructor(code: string, vars?: Record<string, unknown>) {\n super(403, code, vars);\n this.name = \"PlenaryForbiddenError\";\n }\n}\n\n/** Builds the right error subclass from a failed response's status and body. */\nexport function errorFromResponse(status: number, body: unknown): PlenaryError {\n const parsed = body as ServerErrorBody | null;\n const code = parsed?.error?.code ?? `http_${status}`;\n const vars = parsed?.error?.vars;\n switch (status) {\n case 404:\n return new PlenaryNotFoundError(code, vars);\n case 403:\n return new PlenaryForbiddenError(code, vars);\n case 400:\n case 422:\n return new PlenaryValidationError(status, code, vars);\n default:\n return new PlenaryError(status, code, vars);\n }\n}\n","import type {\n ClientConfig,\n Entry,\n ReadOptions,\n ListOptions,\n ListResult,\n PublicFormDefinition,\n FormSubmitOptions,\n FormSubmitResult,\n MediaDetail,\n} from \"./types.js\";\nimport { buildReadParams, buildListParams, type QueryBase } from \"./query.js\";\nimport { errorFromResponse } from \"./errors.js\";\n\n/** The content client returned by {@link createClient}. */\nexport interface PlenaryClient {\n /** Fetch a singleton model's entry. */\n getSingleton<TData = Record<string, unknown>>(\n modelKey: string,\n opts?: ReadOptions,\n ): Promise<Entry<TData>>;\n\n /** Fetch one entry of a collection/page by slug or UUID. */\n getEntry<TData = Record<string, unknown>>(\n modelKey: string,\n slugOrId: string,\n opts?: ReadOptions,\n ): Promise<Entry<TData>>;\n\n /** List entries of a collection/page model. */\n listEntries<TData = Record<string, unknown>>(\n modelKey: string,\n opts?: ListOptions,\n ): Promise<ListResult<TData>>;\n\n /**\n * Fetch a form's render-safe definition — the field list a renderer needs,\n * readable with a public delivery key.\n */\n getForm(formKey: string): Promise<PublicFormDefinition>;\n\n /** Submit a public form. */\n submitForm(\n formKey: string,\n data: Record<string, unknown>,\n opts?: FormSubmitOptions,\n ): Promise<FormSubmitResult>;\n\n /**\n * Fetch a media item's detail (URLs + variants). Requires a key that may read\n * media; a public delivery key cannot, so this is used in authoring/preview\n * contexts or by sites configured with a media-capable key.\n */\n getMedia(mediaId: string): Promise<MediaDetail>;\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}${path}`;\n}\n\nexport function createClient(config: ClientConfig): PlenaryClient {\n const base: QueryBase = { siteKey: config.siteKey, locale: config.locale };\n const doFetch = config.fetch ?? globalThis.fetch;\n\n async function request<T>(\n method: string,\n path: string,\n params: URLSearchParams,\n body?: unknown,\n ): Promise<{ data: T; response: Response }> {\n const url = `${joinUrl(config.baseUrl, path)}?${params.toString()}`;\n const headers: Record<string, string> = { \"X-API-KEY\": config.apiKey };\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const response = await doFetch(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n const parsed = response.status === 204 ? null : await response.json().catch(() => null);\n if (!response.ok) {\n throw errorFromResponse(response.status, parsed);\n }\n return { data: parsed as T, response };\n }\n\n return {\n async getSingleton<TData>(modelKey: string, opts: ReadOptions = {}) {\n const params = buildReadParams(base, opts);\n const { data } = await request<Entry<TData>>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}`,\n params,\n );\n return data;\n },\n\n async getEntry<TData>(modelKey: string, slugOrId: string, opts: ReadOptions = {}) {\n const params = buildReadParams(base, opts);\n const { data } = await request<Entry<TData>>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}/${encodeURIComponent(slugOrId)}`,\n params,\n );\n return data;\n },\n\n async listEntries<TData>(modelKey: string, opts: ListOptions = {}) {\n const params = buildListParams(base, opts);\n const { data, response } = await request<Entry<TData>[]>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}`,\n params,\n );\n const result: ListResult<TData> = { items: data };\n const total = response.headers.get(\"X-Total-Count\");\n if (total !== null) result.total = Number(total);\n return result;\n },\n\n async getForm(formKey: string) {\n const { data } = await request<PublicFormDefinition>(\n \"GET\",\n `/api/v1/forms/${encodeURIComponent(formKey)}`,\n new URLSearchParams(),\n );\n return data;\n },\n\n async submitForm(\n formKey: string,\n data: Record<string, unknown>,\n opts: FormSubmitOptions = {},\n ) {\n const params = new URLSearchParams();\n params.set(\"site_key\", opts.siteKey ?? base.siteKey);\n const body: Record<string, unknown> = { data };\n if (opts.website !== undefined) body.website = opts.website;\n if (opts.ts !== undefined) body.ts = opts.ts;\n const { data: result } = await request<FormSubmitResult>(\n \"POST\",\n `/api/v1/forms/${encodeURIComponent(formKey)}/entries`,\n params,\n body,\n );\n return result;\n },\n\n async getMedia(mediaId: string) {\n const { data } = await request<MediaDetail>(\n \"GET\",\n `/api/v1/media/${encodeURIComponent(mediaId)}`,\n new URLSearchParams(),\n );\n return data;\n },\n };\n}\n","/**\n * Webhook consumer helpers: the payload types Plenary POSTs\n * and framework-neutral verification/parsing. Plenary does not sign requests\n * yet; verification is a shared-secret comparison against a header or query\n * parameter. When HMAC signing ships, it slots in here without changing the\n * call sites.\n */\n\n/** The five entry lifecycle events a content-model webhook can subscribe to. */\nexport type EntryWebhookEvent =\n | \"entry.created\"\n | \"entry.updated\"\n | \"entry.published\"\n | \"entry.unpublished\"\n | \"entry.deleted\";\n\nexport const ENTRY_WEBHOOK_EVENTS: readonly EntryWebhookEvent[] = [\n \"entry.created\",\n \"entry.updated\",\n \"entry.published\",\n \"entry.unpublished\",\n \"entry.deleted\",\n];\n\n/** The event carried by the console's Test button. The body is otherwise an\n * ordinary entry or form payload, built from the model's most recent entry. */\nexport const WEBHOOK_TEST_EVENT = \"webhook.test\";\n\n/** Who caused the event. `label` survives the actor's deletion. */\nexport interface WebhookActor {\n type: \"user\" | \"api_key\" | \"system\";\n id?: string;\n label: string;\n}\n\n/** The entry's identity. Field selection never touches these. */\nexport interface WebhookEntryRef {\n id: string;\n site_key?: string;\n locale?: string;\n slug?: string;\n /** Directory path of the entry's folder (\"/\" at the model root). Does NOT\n * include the entry's own slug — join them to build a URL. */\n path?: string;\n status?: string;\n created_at?: string;\n updated_at?: string;\n published_at?: string;\n}\n\n/** A form entry's identity: no slug, locale, path, or publish state. */\nexport interface WebhookFormEntryRef {\n id: string;\n site_key?: string;\n status?: string;\n created_at?: string;\n source?: { ip?: string; user_agent?: string; referer?: string };\n}\n\n/** Payload for every `entry.*` event (and entry-shaped `webhook.test`). */\nexport interface EntryWebhookPayload<TData = Record<string, unknown>> {\n event: EntryWebhookEvent | typeof WEBHOOK_TEST_EVENT;\n delivery_id: string;\n occurred_at: string;\n webhook: { id: string; name?: string };\n model: { id: string; key?: string };\n entry: WebhookEntryRef;\n actor: WebhookActor;\n /** Paths that differ from the previous revision. Always present; empty for\n * `entry.created` and `entry.deleted`. */\n changed: string[];\n revision_id?: string;\n /** Field values per the webhook's field selection; absent for \"no field data\". */\n data?: TData;\n}\n\n/** Payload for `form.submitted` (and form-shaped `webhook.test`). */\nexport interface FormWebhookPayload<TData = Record<string, unknown>> {\n event: \"form.submitted\" | typeof WEBHOOK_TEST_EVENT;\n delivery_id: string;\n occurred_at: string;\n webhook: { id: string; name?: string };\n form: { id: string; key?: string };\n form_entry: WebhookFormEntryRef;\n data?: TData;\n}\n\nexport type WebhookPayload = EntryWebhookPayload | FormWebhookPayload;\n\nexport function isEntryWebhookPayload(p: WebhookPayload): p is EntryWebhookPayload {\n return \"entry\" in p;\n}\n\nexport function isFormWebhookPayload(p: WebhookPayload): p is FormWebhookPayload {\n return \"form_entry\" in p;\n}\n\n/** A request that is not a valid webhook delivery. `status` is the HTTP status\n * an endpoint should answer with — 4xx makes the delivery fail fast in the\n * console's delivery log instead of being retried for seven hours. */\nexport class PlenaryWebhookError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = \"PlenaryWebhookError\";\n this.status = status;\n }\n}\n\n/** Constant-time comparison of a provided secret against the expected one.\n * An empty or missing `secret` never matches, so an unset environment\n * variable can't turn into \"accept an empty header\". */\nexport function verifyWebhookSecret(\n provided: string | null | undefined,\n secret: string,\n): boolean {\n if (typeof provided !== \"string\" || !secret) return false;\n const enc = new TextEncoder();\n const a = enc.encode(provided);\n const b = enc.encode(secret);\n let diff = a.length ^ b.length;\n const len = Math.max(a.length, b.length);\n for (let i = 0; i < len; i++) diff |= (a[i] ?? 0) ^ (b[i] ?? 0);\n return diff === 0;\n}\n\n/** Shape-checks a parsed JSON body and types it. Throws {@link PlenaryWebhookError}\n * (400) for anything that is not a Plenary webhook payload. */\nexport function parseWebhookPayload(body: unknown): WebhookPayload {\n const p = body as Record<string, unknown> | null;\n if (\n p === null ||\n typeof p !== \"object\" ||\n typeof p[\"event\"] !== \"string\" ||\n typeof p[\"delivery_id\"] !== \"string\" ||\n !(\"entry\" in p || \"form_entry\" in p)\n ) {\n throw new PlenaryWebhookError(400, \"Not a Plenary webhook payload\");\n }\n return p as unknown as WebhookPayload;\n}\n\n/** Options for {@link parseWebhookRequest}. */\nexport interface ParseWebhookOptions {\n /** The shared secret configured on the webhook (as a secret header or query\n * parameter in the console). */\n secret: string;\n /** Header the secret arrives in. Default `x-plenary-secret`. */\n secretHeader?: string;\n /** Query parameter checked when the header is absent. Default `secret`. */\n secretQuery?: string;\n}\n\n/**\n * Verifies and parses a webhook delivery from a Fetch-API `Request`:\n * POST only, secret in the configured header or query parameter, JSON body of\n * one of the two payload shapes. Throws {@link PlenaryWebhookError} with the\n * status the endpoint should answer (405, 401, or 400).\n */\nexport async function parseWebhookRequest(\n request: Request,\n options: ParseWebhookOptions,\n): Promise<WebhookPayload> {\n if (request.method !== \"POST\") {\n throw new PlenaryWebhookError(405, \"Method not allowed\");\n }\n if (!options.secret) {\n throw new PlenaryWebhookError(500, \"Webhook secret is not configured\");\n }\n const header = options.secretHeader ?? \"x-plenary-secret\";\n const query = options.secretQuery ?? \"secret\";\n const provided =\n request.headers.get(header) ?? new URL(request.url).searchParams.get(query);\n if (!verifyWebhookSecret(provided, options.secret)) {\n throw new PlenaryWebhookError(401, \"Invalid webhook secret\");\n }\n const body = await request.json().catch(() => {\n throw new PlenaryWebhookError(400, \"Body is not valid JSON\");\n });\n return parseWebhookPayload(body);\n}\n\n/**\n * Revalidation planning — which site URLs one entry webhook affects. Shared by\n * the framework adapters (`@plenary/next`, `@plenary/nuxt`), which own the\n * cache calls; this part is pure.\n */\n\n/** Paths for one model: a fixed path, a list, or a function of the payload.\n * A function may return `null`/`undefined` to revalidate nothing. */\nexport type RevalidatePaths =\n | string\n | string[]\n | ((payload: EntryWebhookPayload) => string | string[] | null | undefined);\n\nexport interface RevalidateOptions extends ParseWebhookOptions {\n /**\n * Extra or overriding paths per model key. A mapped model replaces the\n * default URL entirely — include `entryUrl(payload)` (exported) in the\n * returned list to keep it.\n */\n paths?: Record<string, RevalidatePaths>;\n /** Builds the default URL for unmapped models. Defaults to joining the\n * entry's folder `path` and `slug`; return `null` to skip. */\n entryUrl?: (payload: EntryWebhookPayload) => string | null | undefined;\n /** Entry events that trigger revalidation. Default: all five — revalidation\n * is idempotent and cheap, so over-firing beats a stale page. */\n events?: EntryWebhookEvent[];\n}\n\n/** Default URL for an entry: folder path + slug (`/blog` + `spring-sale` →\n * `/blog/spring-sale`). A root-folder entry slugged `home` is the site's home\n * page, `/`. Entries without a slug (singletons) get no default. */\nexport function entryUrl(payload: EntryWebhookPayload): string | null {\n const { slug, path } = payload.entry;\n if (!slug) return null;\n const dir = path && path !== \"/\" ? path : \"\";\n if (!dir && slug === \"home\") return \"/\";\n return `${dir}/${slug}`;\n}\n\nexport interface RevalidationPlan {\n /** Whether this payload is one the handler acts on. */\n relevant: boolean;\n /** True for the console's Test button — report, don't revalidate. */\n test: boolean;\n paths: string[];\n}\n\n/** Decides which paths one payload revalidates. Form payloads and entry\n * events outside `options.events` are not relevant. */\nexport function planRevalidation(\n payload: WebhookPayload,\n options: RevalidateOptions,\n): RevalidationPlan {\n const none = { relevant: false, test: false, paths: [] };\n if (!isEntryWebhookPayload(payload)) return none;\n\n const test = payload.event === WEBHOOK_TEST_EVENT;\n if (!test && options.events && !options.events.includes(payload.event as EntryWebhookEvent)) {\n return none;\n }\n\n const modelKey = payload.model.key;\n const mapped = modelKey === undefined ? undefined : options.paths?.[modelKey];\n let paths: string | string[] | null | undefined;\n if (mapped === undefined) {\n paths = (options.entryUrl ?? entryUrl)(payload);\n } else {\n paths = typeof mapped === \"function\" ? mapped(payload) : mapped;\n }\n const pathList = paths == null ? [] : typeof paths === \"string\" ? [paths] : paths;\n\n return { relevant: true, test, paths: [...new Set(pathList)] };\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -35,6 +35,20 @@ interface Entry<TData = Record<string, unknown>> {
35
35
  path: string;
36
36
  /** Field values keyed by field key; a `blocks` field holds a component tree. */
37
37
  data: TData;
38
+ /**
39
+ * Changes saved to a published entry that are not live yet, or null (absent
40
+ * on a read through a public key). `data` and `slug` stay the live copy
41
+ * until the entry is published again. A public key flagged `include_drafts`
42
+ * reads the pending content in `data` and `slug` instead and never sees
43
+ * this key.
44
+ */
45
+ draft?: EntryDraft | null;
46
+ }
47
+ /** The pending changes on a published entry: the content a publish would make live. */
48
+ interface EntryDraft<TData = Record<string, unknown>> {
49
+ data: TData;
50
+ slug: string | null;
51
+ updated_at: string;
38
52
  }
39
53
  /** Comparison operators supported by `where:` and filter trees. */
40
54
  interface WhereCondition {
@@ -54,6 +68,8 @@ interface WhereCondition {
54
68
  $ilike?: string;
55
69
  /** Whether the value is null. */
56
70
  $is_null?: boolean;
71
+ /** `path` only: the folder and everything beneath it (`/blog` matches `/blog/campaigns`). */
72
+ $under?: string;
57
73
  }
58
74
  /**
59
75
  * A flat `where:` map: field path → a bare value (shorthand for `$eq`) or a
@@ -350,5 +366,41 @@ interface ParseWebhookOptions {
350
366
  * status the endpoint should answer (405, 401, or 400).
351
367
  */
352
368
  declare function parseWebhookRequest(request: Request, options: ParseWebhookOptions): Promise<WebhookPayload>;
369
+ /**
370
+ * Revalidation planning — which site URLs one entry webhook affects. Shared by
371
+ * the framework adapters (`@plenary/next`, `@plenary/nuxt`), which own the
372
+ * cache calls; this part is pure.
373
+ */
374
+ /** Paths for one model: a fixed path, a list, or a function of the payload.
375
+ * A function may return `null`/`undefined` to revalidate nothing. */
376
+ type RevalidatePaths = string | string[] | ((payload: EntryWebhookPayload) => string | string[] | null | undefined);
377
+ interface RevalidateOptions extends ParseWebhookOptions {
378
+ /**
379
+ * Extra or overriding paths per model key. A mapped model replaces the
380
+ * default URL entirely — include `entryUrl(payload)` (exported) in the
381
+ * returned list to keep it.
382
+ */
383
+ paths?: Record<string, RevalidatePaths>;
384
+ /** Builds the default URL for unmapped models. Defaults to joining the
385
+ * entry's folder `path` and `slug`; return `null` to skip. */
386
+ entryUrl?: (payload: EntryWebhookPayload) => string | null | undefined;
387
+ /** Entry events that trigger revalidation. Default: all five — revalidation
388
+ * is idempotent and cheap, so over-firing beats a stale page. */
389
+ events?: EntryWebhookEvent[];
390
+ }
391
+ /** Default URL for an entry: folder path + slug (`/blog` + `spring-sale` →
392
+ * `/blog/spring-sale`). A root-folder entry slugged `home` is the site's home
393
+ * page, `/`. Entries without a slug (singletons) get no default. */
394
+ declare function entryUrl(payload: EntryWebhookPayload): string | null;
395
+ interface RevalidationPlan {
396
+ /** Whether this payload is one the handler acts on. */
397
+ relevant: boolean;
398
+ /** True for the console's Test button — report, don't revalidate. */
399
+ test: boolean;
400
+ paths: string[];
401
+ }
402
+ /** Decides which paths one payload revalidates. Form payloads and entry
403
+ * events outside `options.events` are not relevant. */
404
+ declare function planRevalidation(payload: WebhookPayload, options: RevalidateOptions): RevalidationPlan;
353
405
 
354
- export { type ClientConfig, ENTRY_WEBHOOK_EVENTS, type Entry, type EntryWebhookEvent, type EntryWebhookPayload, type FilterTree, type FormSubmitOptions, type FormSubmitResult, type FormWebhookPayload, type Hydrate, type ListOptions, type ListResult, type MediaDetail, type MediaVariant, type ParseWebhookOptions, type PlenaryClient, PlenaryError, PlenaryForbiddenError, PlenaryNotFoundError, PlenaryValidationError, PlenaryWebhookError, type PublicFormDefinition, type PublicFormField, type PublicFormFieldType, type QueryBase, type ReadOptions, type Sort, type SortDirection, WEBHOOK_TEST_EVENT, type WebhookActor, type WebhookEntryRef, type WebhookFormEntryRef, type WebhookPayload, type Where, type WhereCondition, buildListParams, buildReadParams, createClient, errorFromResponse, isEntryWebhookPayload, isFormWebhookPayload, parseWebhookPayload, parseWebhookRequest, verifyWebhookSecret };
406
+ export { type ClientConfig, ENTRY_WEBHOOK_EVENTS, type Entry, type EntryDraft, type EntryWebhookEvent, type EntryWebhookPayload, type FilterTree, type FormSubmitOptions, type FormSubmitResult, type FormWebhookPayload, type Hydrate, type ListOptions, type ListResult, type MediaDetail, type MediaVariant, type ParseWebhookOptions, type PlenaryClient, PlenaryError, PlenaryForbiddenError, PlenaryNotFoundError, PlenaryValidationError, PlenaryWebhookError, type PublicFormDefinition, type PublicFormField, type PublicFormFieldType, type QueryBase, type ReadOptions, type RevalidateOptions, type RevalidatePaths, type RevalidationPlan, type Sort, type SortDirection, WEBHOOK_TEST_EVENT, type WebhookActor, type WebhookEntryRef, type WebhookFormEntryRef, type WebhookPayload, type Where, type WhereCondition, buildListParams, buildReadParams, createClient, entryUrl, errorFromResponse, isEntryWebhookPayload, isFormWebhookPayload, parseWebhookPayload, parseWebhookRequest, planRevalidation, verifyWebhookSecret };
package/dist/index.d.ts CHANGED
@@ -35,6 +35,20 @@ interface Entry<TData = Record<string, unknown>> {
35
35
  path: string;
36
36
  /** Field values keyed by field key; a `blocks` field holds a component tree. */
37
37
  data: TData;
38
+ /**
39
+ * Changes saved to a published entry that are not live yet, or null (absent
40
+ * on a read through a public key). `data` and `slug` stay the live copy
41
+ * until the entry is published again. A public key flagged `include_drafts`
42
+ * reads the pending content in `data` and `slug` instead and never sees
43
+ * this key.
44
+ */
45
+ draft?: EntryDraft | null;
46
+ }
47
+ /** The pending changes on a published entry: the content a publish would make live. */
48
+ interface EntryDraft<TData = Record<string, unknown>> {
49
+ data: TData;
50
+ slug: string | null;
51
+ updated_at: string;
38
52
  }
39
53
  /** Comparison operators supported by `where:` and filter trees. */
40
54
  interface WhereCondition {
@@ -54,6 +68,8 @@ interface WhereCondition {
54
68
  $ilike?: string;
55
69
  /** Whether the value is null. */
56
70
  $is_null?: boolean;
71
+ /** `path` only: the folder and everything beneath it (`/blog` matches `/blog/campaigns`). */
72
+ $under?: string;
57
73
  }
58
74
  /**
59
75
  * A flat `where:` map: field path → a bare value (shorthand for `$eq`) or a
@@ -350,5 +366,41 @@ interface ParseWebhookOptions {
350
366
  * status the endpoint should answer (405, 401, or 400).
351
367
  */
352
368
  declare function parseWebhookRequest(request: Request, options: ParseWebhookOptions): Promise<WebhookPayload>;
369
+ /**
370
+ * Revalidation planning — which site URLs one entry webhook affects. Shared by
371
+ * the framework adapters (`@plenary/next`, `@plenary/nuxt`), which own the
372
+ * cache calls; this part is pure.
373
+ */
374
+ /** Paths for one model: a fixed path, a list, or a function of the payload.
375
+ * A function may return `null`/`undefined` to revalidate nothing. */
376
+ type RevalidatePaths = string | string[] | ((payload: EntryWebhookPayload) => string | string[] | null | undefined);
377
+ interface RevalidateOptions extends ParseWebhookOptions {
378
+ /**
379
+ * Extra or overriding paths per model key. A mapped model replaces the
380
+ * default URL entirely — include `entryUrl(payload)` (exported) in the
381
+ * returned list to keep it.
382
+ */
383
+ paths?: Record<string, RevalidatePaths>;
384
+ /** Builds the default URL for unmapped models. Defaults to joining the
385
+ * entry's folder `path` and `slug`; return `null` to skip. */
386
+ entryUrl?: (payload: EntryWebhookPayload) => string | null | undefined;
387
+ /** Entry events that trigger revalidation. Default: all five — revalidation
388
+ * is idempotent and cheap, so over-firing beats a stale page. */
389
+ events?: EntryWebhookEvent[];
390
+ }
391
+ /** Default URL for an entry: folder path + slug (`/blog` + `spring-sale` →
392
+ * `/blog/spring-sale`). A root-folder entry slugged `home` is the site's home
393
+ * page, `/`. Entries without a slug (singletons) get no default. */
394
+ declare function entryUrl(payload: EntryWebhookPayload): string | null;
395
+ interface RevalidationPlan {
396
+ /** Whether this payload is one the handler acts on. */
397
+ relevant: boolean;
398
+ /** True for the console's Test button — report, don't revalidate. */
399
+ test: boolean;
400
+ paths: string[];
401
+ }
402
+ /** Decides which paths one payload revalidates. Form payloads and entry
403
+ * events outside `options.events` are not relevant. */
404
+ declare function planRevalidation(payload: WebhookPayload, options: RevalidateOptions): RevalidationPlan;
353
405
 
354
- export { type ClientConfig, ENTRY_WEBHOOK_EVENTS, type Entry, type EntryWebhookEvent, type EntryWebhookPayload, type FilterTree, type FormSubmitOptions, type FormSubmitResult, type FormWebhookPayload, type Hydrate, type ListOptions, type ListResult, type MediaDetail, type MediaVariant, type ParseWebhookOptions, type PlenaryClient, PlenaryError, PlenaryForbiddenError, PlenaryNotFoundError, PlenaryValidationError, PlenaryWebhookError, type PublicFormDefinition, type PublicFormField, type PublicFormFieldType, type QueryBase, type ReadOptions, type Sort, type SortDirection, WEBHOOK_TEST_EVENT, type WebhookActor, type WebhookEntryRef, type WebhookFormEntryRef, type WebhookPayload, type Where, type WhereCondition, buildListParams, buildReadParams, createClient, errorFromResponse, isEntryWebhookPayload, isFormWebhookPayload, parseWebhookPayload, parseWebhookRequest, verifyWebhookSecret };
406
+ export { type ClientConfig, ENTRY_WEBHOOK_EVENTS, type Entry, type EntryDraft, type EntryWebhookEvent, type EntryWebhookPayload, type FilterTree, type FormSubmitOptions, type FormSubmitResult, type FormWebhookPayload, type Hydrate, type ListOptions, type ListResult, type MediaDetail, type MediaVariant, type ParseWebhookOptions, type PlenaryClient, PlenaryError, PlenaryForbiddenError, PlenaryNotFoundError, PlenaryValidationError, PlenaryWebhookError, type PublicFormDefinition, type PublicFormField, type PublicFormFieldType, type QueryBase, type ReadOptions, type RevalidateOptions, type RevalidatePaths, type RevalidationPlan, type Sort, type SortDirection, WEBHOOK_TEST_EVENT, type WebhookActor, type WebhookEntryRef, type WebhookFormEntryRef, type WebhookPayload, type Where, type WhereCondition, buildListParams, buildReadParams, createClient, entryUrl, errorFromResponse, isEntryWebhookPayload, isFormWebhookPayload, parseWebhookPayload, parseWebhookRequest, planRevalidation, verifyWebhookSecret };
package/dist/index.js CHANGED
@@ -10,7 +10,8 @@ var OPERATORS = /* @__PURE__ */ new Set([
10
10
  "$nin",
11
11
  "$like",
12
12
  "$ilike",
13
- "$is_null"
13
+ "$is_null",
14
+ "$under"
14
15
  ]);
15
16
  function isCondition(value) {
16
17
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -274,6 +275,31 @@ async function parseWebhookRequest(request, options) {
274
275
  });
275
276
  return parseWebhookPayload(body);
276
277
  }
278
+ function entryUrl(payload) {
279
+ const { slug, path } = payload.entry;
280
+ if (!slug) return null;
281
+ const dir = path && path !== "/" ? path : "";
282
+ if (!dir && slug === "home") return "/";
283
+ return `${dir}/${slug}`;
284
+ }
285
+ function planRevalidation(payload, options) {
286
+ const none = { relevant: false, test: false, paths: [] };
287
+ if (!isEntryWebhookPayload(payload)) return none;
288
+ const test = payload.event === WEBHOOK_TEST_EVENT;
289
+ if (!test && options.events && !options.events.includes(payload.event)) {
290
+ return none;
291
+ }
292
+ const modelKey = payload.model.key;
293
+ const mapped = modelKey === void 0 ? void 0 : options.paths?.[modelKey];
294
+ let paths;
295
+ if (mapped === void 0) {
296
+ paths = (options.entryUrl ?? entryUrl)(payload);
297
+ } else {
298
+ paths = typeof mapped === "function" ? mapped(payload) : mapped;
299
+ }
300
+ const pathList = paths == null ? [] : typeof paths === "string" ? [paths] : paths;
301
+ return { relevant: true, test, paths: [...new Set(pathList)] };
302
+ }
277
303
  export {
278
304
  ENTRY_WEBHOOK_EVENTS,
279
305
  PlenaryError,
@@ -285,11 +311,13 @@ export {
285
311
  buildListParams,
286
312
  buildReadParams,
287
313
  createClient,
314
+ entryUrl,
288
315
  errorFromResponse,
289
316
  isEntryWebhookPayload,
290
317
  isFormWebhookPayload,
291
318
  parseWebhookPayload,
292
319
  parseWebhookRequest,
320
+ planRevalidation,
293
321
  verifyWebhookSecret
294
322
  };
295
323
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/query.ts","../src/errors.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["import type {\n Where,\n WhereCondition,\n FilterTree,\n Sort,\n Hydrate,\n ReadOptions,\n ListOptions,\n} from \"./types.js\";\n\n/** The known `where:` operators; anything else on a condition object is passed through. */\nconst OPERATORS = new Set([\n \"$eq\",\n \"$ne\",\n \"$lt\",\n \"$lte\",\n \"$gt\",\n \"$gte\",\n \"$in\",\n \"$nin\",\n \"$like\",\n \"$ilike\",\n \"$is_null\",\n]);\n\n/** A value is a condition when it's a plain object whose keys are all operators. */\nfunction isCondition(value: unknown): value is WhereCondition {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const keys = Object.keys(value);\n return keys.length > 0 && keys.every((k) => OPERATORS.has(k));\n}\n\nfunction encodeOperatorValue(op: string, value: unknown): string {\n if (op === \"$in\" || op === \"$nin\") {\n return Array.isArray(value) ? value.map(String).join(\",\") : String(value);\n }\n if (op === \"$is_null\") {\n return String(Boolean(value));\n }\n return String(value);\n}\n\nfunction appendWhere(params: URLSearchParams, where: Where): void {\n for (const [field, value] of Object.entries(where)) {\n if (isCondition(value)) {\n for (const [op, opValue] of Object.entries(value)) {\n params.append(`where:${field}.${op}`, encodeOperatorValue(op, opValue));\n }\n } else {\n // Bare value — the server assumes `$eq`.\n params.append(`where:${field}`, String(value));\n }\n }\n}\n\nfunction appendSort(params: URLSearchParams, sort: Sort): void {\n // Insertion order is the sort precedence (left-to-right).\n for (const [field, dir] of Object.entries(sort)) {\n params.append(`sort:${field}`, dir);\n }\n}\n\nfunction appendHydrate(params: URLSearchParams, hydrate: Hydrate): void {\n for (const [field, spec] of Object.entries(hydrate)) {\n params.append(`hydrate:${field}`, spec === \"*\" ? \"*\" : spec.join(\",\"));\n }\n}\n\n/** Base context every request carries: which site and locale. */\nexport interface QueryBase {\n siteKey: string;\n locale: string;\n}\n\nfunction appendCommon(params: URLSearchParams, base: QueryBase, opts: ReadOptions): void {\n params.set(\"site_key\", opts.siteKey ?? base.siteKey);\n params.set(\"locale\", opts.locale ?? base.locale);\n\n if (opts.include && opts.exclude) {\n throw new Error(\"plenary: `include` and `exclude` are mutually exclusive\");\n }\n if (opts.include?.length) params.set(\"include\", opts.include.join(\",\"));\n if (opts.exclude?.length) params.set(\"exclude\", opts.exclude.join(\",\"));\n if (opts.hydrate) appendHydrate(params, opts.hydrate);\n}\n\n/** Builds the query for a single-entry read (get / singleton). */\nexport function buildReadParams(base: QueryBase, opts: ReadOptions = {}): URLSearchParams {\n const params = new URLSearchParams();\n appendCommon(params, base, opts);\n return params;\n}\n\n/** Builds the query for a list read, including filters, sort, and pagination. */\nexport function buildListParams(base: QueryBase, opts: ListOptions = {}): URLSearchParams {\n const params = new URLSearchParams();\n appendCommon(params, base, opts);\n\n if (opts.where && opts.filters) {\n throw new Error(\"plenary: `where` and `filters` are mutually exclusive\");\n }\n if (opts.filters) {\n params.set(\"filters\", JSON.stringify(opts.filters satisfies FilterTree));\n } else if (opts.where) {\n appendWhere(params, opts.where);\n }\n\n if (opts.sort) appendSort(params, opts.sort);\n if (opts.limit !== undefined) params.set(\"limit\", String(opts.limit));\n if (opts.offset !== undefined) params.set(\"offset\", String(opts.offset));\n if (opts.count) params.set(\"count\", \"true\");\n\n return params;\n}\n","/** The server's error envelope: `{ error: { code, vars? } }`. */\ninterface ServerErrorBody {\n error?: { code?: string; vars?: Record<string, unknown> };\n}\n\n/** Base error for a non-2xx response from the CMS. */\nexport class PlenaryError extends Error {\n readonly status: number;\n /** The server's i18n error code (e.g. `entry_query_limit_exceeded`), if any. */\n readonly code: string;\n /** Interpolation variables the server attached to the code. */\n readonly vars?: Record<string, unknown>;\n\n constructor(status: number, code: string, vars?: Record<string, unknown>) {\n super(`Plenary request failed (${status}): ${code}`);\n this.name = \"PlenaryError\";\n this.status = status;\n this.code = code;\n this.vars = vars;\n }\n}\n\n/** A 404 — model or entry not found (or hidden by a private model). */\nexport class PlenaryNotFoundError extends PlenaryError {\n constructor(code: string, vars?: Record<string, unknown>) {\n super(404, code, vars);\n this.name = \"PlenaryNotFoundError\";\n }\n}\n\n/** A 400/422 — malformed query or invalid input. */\nexport class PlenaryValidationError extends PlenaryError {\n constructor(status: number, code: string, vars?: Record<string, unknown>) {\n super(status, code, vars);\n this.name = \"PlenaryValidationError\";\n }\n}\n\n/** A 403 — the key may not read the requested site/resource. */\nexport class PlenaryForbiddenError extends PlenaryError {\n constructor(code: string, vars?: Record<string, unknown>) {\n super(403, code, vars);\n this.name = \"PlenaryForbiddenError\";\n }\n}\n\n/** Builds the right error subclass from a failed response's status and body. */\nexport function errorFromResponse(status: number, body: unknown): PlenaryError {\n const parsed = body as ServerErrorBody | null;\n const code = parsed?.error?.code ?? `http_${status}`;\n const vars = parsed?.error?.vars;\n switch (status) {\n case 404:\n return new PlenaryNotFoundError(code, vars);\n case 403:\n return new PlenaryForbiddenError(code, vars);\n case 400:\n case 422:\n return new PlenaryValidationError(status, code, vars);\n default:\n return new PlenaryError(status, code, vars);\n }\n}\n","import type {\n ClientConfig,\n Entry,\n ReadOptions,\n ListOptions,\n ListResult,\n PublicFormDefinition,\n FormSubmitOptions,\n FormSubmitResult,\n MediaDetail,\n} from \"./types.js\";\nimport { buildReadParams, buildListParams, type QueryBase } from \"./query.js\";\nimport { errorFromResponse } from \"./errors.js\";\n\n/** The content client returned by {@link createClient}. */\nexport interface PlenaryClient {\n /** Fetch a singleton model's entry. */\n getSingleton<TData = Record<string, unknown>>(\n modelKey: string,\n opts?: ReadOptions,\n ): Promise<Entry<TData>>;\n\n /** Fetch one entry of a collection/page by slug or UUID. */\n getEntry<TData = Record<string, unknown>>(\n modelKey: string,\n slugOrId: string,\n opts?: ReadOptions,\n ): Promise<Entry<TData>>;\n\n /** List entries of a collection/page model. */\n listEntries<TData = Record<string, unknown>>(\n modelKey: string,\n opts?: ListOptions,\n ): Promise<ListResult<TData>>;\n\n /**\n * Fetch a form's render-safe definition — the field list a renderer needs,\n * readable with a public delivery key.\n */\n getForm(formKey: string): Promise<PublicFormDefinition>;\n\n /** Submit a public form. */\n submitForm(\n formKey: string,\n data: Record<string, unknown>,\n opts?: FormSubmitOptions,\n ): Promise<FormSubmitResult>;\n\n /**\n * Fetch a media item's detail (URLs + variants). Requires a key that may read\n * media; a public delivery key cannot, so this is used in authoring/preview\n * contexts or by sites configured with a media-capable key.\n */\n getMedia(mediaId: string): Promise<MediaDetail>;\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}${path}`;\n}\n\nexport function createClient(config: ClientConfig): PlenaryClient {\n const base: QueryBase = { siteKey: config.siteKey, locale: config.locale };\n const doFetch = config.fetch ?? globalThis.fetch;\n\n async function request<T>(\n method: string,\n path: string,\n params: URLSearchParams,\n body?: unknown,\n ): Promise<{ data: T; response: Response }> {\n const url = `${joinUrl(config.baseUrl, path)}?${params.toString()}`;\n const headers: Record<string, string> = { \"X-API-KEY\": config.apiKey };\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const response = await doFetch(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n const parsed = response.status === 204 ? null : await response.json().catch(() => null);\n if (!response.ok) {\n throw errorFromResponse(response.status, parsed);\n }\n return { data: parsed as T, response };\n }\n\n return {\n async getSingleton<TData>(modelKey: string, opts: ReadOptions = {}) {\n const params = buildReadParams(base, opts);\n const { data } = await request<Entry<TData>>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}`,\n params,\n );\n return data;\n },\n\n async getEntry<TData>(modelKey: string, slugOrId: string, opts: ReadOptions = {}) {\n const params = buildReadParams(base, opts);\n const { data } = await request<Entry<TData>>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}/${encodeURIComponent(slugOrId)}`,\n params,\n );\n return data;\n },\n\n async listEntries<TData>(modelKey: string, opts: ListOptions = {}) {\n const params = buildListParams(base, opts);\n const { data, response } = await request<Entry<TData>[]>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}`,\n params,\n );\n const result: ListResult<TData> = { items: data };\n const total = response.headers.get(\"X-Total-Count\");\n if (total !== null) result.total = Number(total);\n return result;\n },\n\n async getForm(formKey: string) {\n const { data } = await request<PublicFormDefinition>(\n \"GET\",\n `/api/v1/forms/${encodeURIComponent(formKey)}`,\n new URLSearchParams(),\n );\n return data;\n },\n\n async submitForm(\n formKey: string,\n data: Record<string, unknown>,\n opts: FormSubmitOptions = {},\n ) {\n const params = new URLSearchParams();\n params.set(\"site_key\", opts.siteKey ?? base.siteKey);\n const body: Record<string, unknown> = { data };\n if (opts.website !== undefined) body.website = opts.website;\n if (opts.ts !== undefined) body.ts = opts.ts;\n const { data: result } = await request<FormSubmitResult>(\n \"POST\",\n `/api/v1/forms/${encodeURIComponent(formKey)}/entries`,\n params,\n body,\n );\n return result;\n },\n\n async getMedia(mediaId: string) {\n const { data } = await request<MediaDetail>(\n \"GET\",\n `/api/v1/media/${encodeURIComponent(mediaId)}`,\n new URLSearchParams(),\n );\n return data;\n },\n };\n}\n","/**\n * Webhook consumer helpers: the payload types Plenary POSTs\n * and framework-neutral verification/parsing. Plenary does not sign requests\n * yet; verification is a shared-secret comparison against a header or query\n * parameter. When HMAC signing ships, it slots in here without changing the\n * call sites.\n */\n\n/** The five entry lifecycle events a content-model webhook can subscribe to. */\nexport type EntryWebhookEvent =\n | \"entry.created\"\n | \"entry.updated\"\n | \"entry.published\"\n | \"entry.unpublished\"\n | \"entry.deleted\";\n\nexport const ENTRY_WEBHOOK_EVENTS: readonly EntryWebhookEvent[] = [\n \"entry.created\",\n \"entry.updated\",\n \"entry.published\",\n \"entry.unpublished\",\n \"entry.deleted\",\n];\n\n/** The event carried by the console's Test button. The body is otherwise an\n * ordinary entry or form payload, built from the model's most recent entry. */\nexport const WEBHOOK_TEST_EVENT = \"webhook.test\";\n\n/** Who caused the event. `label` survives the actor's deletion. */\nexport interface WebhookActor {\n type: \"user\" | \"api_key\" | \"system\";\n id?: string;\n label: string;\n}\n\n/** The entry's identity. Field selection never touches these. */\nexport interface WebhookEntryRef {\n id: string;\n site_key?: string;\n locale?: string;\n slug?: string;\n /** Directory path of the entry's folder (\"/\" at the model root). Does NOT\n * include the entry's own slug — join them to build a URL. */\n path?: string;\n status?: string;\n created_at?: string;\n updated_at?: string;\n published_at?: string;\n}\n\n/** A form entry's identity: no slug, locale, path, or publish state. */\nexport interface WebhookFormEntryRef {\n id: string;\n site_key?: string;\n status?: string;\n created_at?: string;\n source?: { ip?: string; user_agent?: string; referer?: string };\n}\n\n/** Payload for every `entry.*` event (and entry-shaped `webhook.test`). */\nexport interface EntryWebhookPayload<TData = Record<string, unknown>> {\n event: EntryWebhookEvent | typeof WEBHOOK_TEST_EVENT;\n delivery_id: string;\n occurred_at: string;\n webhook: { id: string; name?: string };\n model: { id: string; key?: string };\n entry: WebhookEntryRef;\n actor: WebhookActor;\n /** Paths that differ from the previous revision. Always present; empty for\n * `entry.created` and `entry.deleted`. */\n changed: string[];\n revision_id?: string;\n /** Field values per the webhook's field selection; absent for \"no field data\". */\n data?: TData;\n}\n\n/** Payload for `form.submitted` (and form-shaped `webhook.test`). */\nexport interface FormWebhookPayload<TData = Record<string, unknown>> {\n event: \"form.submitted\" | typeof WEBHOOK_TEST_EVENT;\n delivery_id: string;\n occurred_at: string;\n webhook: { id: string; name?: string };\n form: { id: string; key?: string };\n form_entry: WebhookFormEntryRef;\n data?: TData;\n}\n\nexport type WebhookPayload = EntryWebhookPayload | FormWebhookPayload;\n\nexport function isEntryWebhookPayload(p: WebhookPayload): p is EntryWebhookPayload {\n return \"entry\" in p;\n}\n\nexport function isFormWebhookPayload(p: WebhookPayload): p is FormWebhookPayload {\n return \"form_entry\" in p;\n}\n\n/** A request that is not a valid webhook delivery. `status` is the HTTP status\n * an endpoint should answer with — 4xx makes the delivery fail fast in the\n * console's delivery log instead of being retried for seven hours. */\nexport class PlenaryWebhookError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = \"PlenaryWebhookError\";\n this.status = status;\n }\n}\n\n/** Constant-time comparison of a provided secret against the expected one.\n * An empty or missing `secret` never matches, so an unset environment\n * variable can't turn into \"accept an empty header\". */\nexport function verifyWebhookSecret(\n provided: string | null | undefined,\n secret: string,\n): boolean {\n if (typeof provided !== \"string\" || !secret) return false;\n const enc = new TextEncoder();\n const a = enc.encode(provided);\n const b = enc.encode(secret);\n let diff = a.length ^ b.length;\n const len = Math.max(a.length, b.length);\n for (let i = 0; i < len; i++) diff |= (a[i] ?? 0) ^ (b[i] ?? 0);\n return diff === 0;\n}\n\n/** Shape-checks a parsed JSON body and types it. Throws {@link PlenaryWebhookError}\n * (400) for anything that is not a Plenary webhook payload. */\nexport function parseWebhookPayload(body: unknown): WebhookPayload {\n const p = body as Record<string, unknown> | null;\n if (\n p === null ||\n typeof p !== \"object\" ||\n typeof p[\"event\"] !== \"string\" ||\n typeof p[\"delivery_id\"] !== \"string\" ||\n !(\"entry\" in p || \"form_entry\" in p)\n ) {\n throw new PlenaryWebhookError(400, \"Not a Plenary webhook payload\");\n }\n return p as unknown as WebhookPayload;\n}\n\n/** Options for {@link parseWebhookRequest}. */\nexport interface ParseWebhookOptions {\n /** The shared secret configured on the webhook (as a secret header or query\n * parameter in the console). */\n secret: string;\n /** Header the secret arrives in. Default `x-plenary-secret`. */\n secretHeader?: string;\n /** Query parameter checked when the header is absent. Default `secret`. */\n secretQuery?: string;\n}\n\n/**\n * Verifies and parses a webhook delivery from a Fetch-API `Request`:\n * POST only, secret in the configured header or query parameter, JSON body of\n * one of the two payload shapes. Throws {@link PlenaryWebhookError} with the\n * status the endpoint should answer (405, 401, or 400).\n */\nexport async function parseWebhookRequest(\n request: Request,\n options: ParseWebhookOptions,\n): Promise<WebhookPayload> {\n if (request.method !== \"POST\") {\n throw new PlenaryWebhookError(405, \"Method not allowed\");\n }\n if (!options.secret) {\n throw new PlenaryWebhookError(500, \"Webhook secret is not configured\");\n }\n const header = options.secretHeader ?? \"x-plenary-secret\";\n const query = options.secretQuery ?? \"secret\";\n const provided =\n request.headers.get(header) ?? new URL(request.url).searchParams.get(query);\n if (!verifyWebhookSecret(provided, options.secret)) {\n throw new PlenaryWebhookError(401, \"Invalid webhook secret\");\n }\n const body = await request.json().catch(() => {\n throw new PlenaryWebhookError(400, \"Body is not valid JSON\");\n });\n return parseWebhookPayload(body);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/query.ts","../src/errors.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["import type {\n Where,\n WhereCondition,\n FilterTree,\n Sort,\n Hydrate,\n ReadOptions,\n ListOptions,\n} from \"./types.js\";\n\n/** The known `where:` operators; anything else on a condition object is passed through. */\nconst OPERATORS = new Set([\n \"$eq\",\n \"$ne\",\n \"$lt\",\n \"$lte\",\n \"$gt\",\n \"$gte\",\n \"$in\",\n \"$nin\",\n \"$like\",\n \"$ilike\",\n \"$is_null\",\n \"$under\",\n]);\n\n/** A value is a condition when it's a plain object whose keys are all operators. */\nfunction isCondition(value: unknown): value is WhereCondition {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const keys = Object.keys(value);\n return keys.length > 0 && keys.every((k) => OPERATORS.has(k));\n}\n\nfunction encodeOperatorValue(op: string, value: unknown): string {\n if (op === \"$in\" || op === \"$nin\") {\n return Array.isArray(value) ? value.map(String).join(\",\") : String(value);\n }\n if (op === \"$is_null\") {\n return String(Boolean(value));\n }\n return String(value);\n}\n\nfunction appendWhere(params: URLSearchParams, where: Where): void {\n for (const [field, value] of Object.entries(where)) {\n if (isCondition(value)) {\n for (const [op, opValue] of Object.entries(value)) {\n params.append(`where:${field}.${op}`, encodeOperatorValue(op, opValue));\n }\n } else {\n // Bare value — the server assumes `$eq`.\n params.append(`where:${field}`, String(value));\n }\n }\n}\n\nfunction appendSort(params: URLSearchParams, sort: Sort): void {\n // Insertion order is the sort precedence (left-to-right).\n for (const [field, dir] of Object.entries(sort)) {\n params.append(`sort:${field}`, dir);\n }\n}\n\nfunction appendHydrate(params: URLSearchParams, hydrate: Hydrate): void {\n for (const [field, spec] of Object.entries(hydrate)) {\n params.append(`hydrate:${field}`, spec === \"*\" ? \"*\" : spec.join(\",\"));\n }\n}\n\n/** Base context every request carries: which site and locale. */\nexport interface QueryBase {\n siteKey: string;\n locale: string;\n}\n\nfunction appendCommon(params: URLSearchParams, base: QueryBase, opts: ReadOptions): void {\n params.set(\"site_key\", opts.siteKey ?? base.siteKey);\n params.set(\"locale\", opts.locale ?? base.locale);\n\n if (opts.include && opts.exclude) {\n throw new Error(\"plenary: `include` and `exclude` are mutually exclusive\");\n }\n if (opts.include?.length) params.set(\"include\", opts.include.join(\",\"));\n if (opts.exclude?.length) params.set(\"exclude\", opts.exclude.join(\",\"));\n if (opts.hydrate) appendHydrate(params, opts.hydrate);\n}\n\n/** Builds the query for a single-entry read (get / singleton). */\nexport function buildReadParams(base: QueryBase, opts: ReadOptions = {}): URLSearchParams {\n const params = new URLSearchParams();\n appendCommon(params, base, opts);\n return params;\n}\n\n/** Builds the query for a list read, including filters, sort, and pagination. */\nexport function buildListParams(base: QueryBase, opts: ListOptions = {}): URLSearchParams {\n const params = new URLSearchParams();\n appendCommon(params, base, opts);\n\n if (opts.where && opts.filters) {\n throw new Error(\"plenary: `where` and `filters` are mutually exclusive\");\n }\n if (opts.filters) {\n params.set(\"filters\", JSON.stringify(opts.filters satisfies FilterTree));\n } else if (opts.where) {\n appendWhere(params, opts.where);\n }\n\n if (opts.sort) appendSort(params, opts.sort);\n if (opts.limit !== undefined) params.set(\"limit\", String(opts.limit));\n if (opts.offset !== undefined) params.set(\"offset\", String(opts.offset));\n if (opts.count) params.set(\"count\", \"true\");\n\n return params;\n}\n","/** The server's error envelope: `{ error: { code, vars? } }`. */\ninterface ServerErrorBody {\n error?: { code?: string; vars?: Record<string, unknown> };\n}\n\n/** Base error for a non-2xx response from the CMS. */\nexport class PlenaryError extends Error {\n readonly status: number;\n /** The server's i18n error code (e.g. `entry_query_limit_exceeded`), if any. */\n readonly code: string;\n /** Interpolation variables the server attached to the code. */\n readonly vars?: Record<string, unknown>;\n\n constructor(status: number, code: string, vars?: Record<string, unknown>) {\n super(`Plenary request failed (${status}): ${code}`);\n this.name = \"PlenaryError\";\n this.status = status;\n this.code = code;\n this.vars = vars;\n }\n}\n\n/** A 404 — model or entry not found (or hidden by a private model). */\nexport class PlenaryNotFoundError extends PlenaryError {\n constructor(code: string, vars?: Record<string, unknown>) {\n super(404, code, vars);\n this.name = \"PlenaryNotFoundError\";\n }\n}\n\n/** A 400/422 — malformed query or invalid input. */\nexport class PlenaryValidationError extends PlenaryError {\n constructor(status: number, code: string, vars?: Record<string, unknown>) {\n super(status, code, vars);\n this.name = \"PlenaryValidationError\";\n }\n}\n\n/** A 403 — the key may not read the requested site/resource. */\nexport class PlenaryForbiddenError extends PlenaryError {\n constructor(code: string, vars?: Record<string, unknown>) {\n super(403, code, vars);\n this.name = \"PlenaryForbiddenError\";\n }\n}\n\n/** Builds the right error subclass from a failed response's status and body. */\nexport function errorFromResponse(status: number, body: unknown): PlenaryError {\n const parsed = body as ServerErrorBody | null;\n const code = parsed?.error?.code ?? `http_${status}`;\n const vars = parsed?.error?.vars;\n switch (status) {\n case 404:\n return new PlenaryNotFoundError(code, vars);\n case 403:\n return new PlenaryForbiddenError(code, vars);\n case 400:\n case 422:\n return new PlenaryValidationError(status, code, vars);\n default:\n return new PlenaryError(status, code, vars);\n }\n}\n","import type {\n ClientConfig,\n Entry,\n ReadOptions,\n ListOptions,\n ListResult,\n PublicFormDefinition,\n FormSubmitOptions,\n FormSubmitResult,\n MediaDetail,\n} from \"./types.js\";\nimport { buildReadParams, buildListParams, type QueryBase } from \"./query.js\";\nimport { errorFromResponse } from \"./errors.js\";\n\n/** The content client returned by {@link createClient}. */\nexport interface PlenaryClient {\n /** Fetch a singleton model's entry. */\n getSingleton<TData = Record<string, unknown>>(\n modelKey: string,\n opts?: ReadOptions,\n ): Promise<Entry<TData>>;\n\n /** Fetch one entry of a collection/page by slug or UUID. */\n getEntry<TData = Record<string, unknown>>(\n modelKey: string,\n slugOrId: string,\n opts?: ReadOptions,\n ): Promise<Entry<TData>>;\n\n /** List entries of a collection/page model. */\n listEntries<TData = Record<string, unknown>>(\n modelKey: string,\n opts?: ListOptions,\n ): Promise<ListResult<TData>>;\n\n /**\n * Fetch a form's render-safe definition — the field list a renderer needs,\n * readable with a public delivery key.\n */\n getForm(formKey: string): Promise<PublicFormDefinition>;\n\n /** Submit a public form. */\n submitForm(\n formKey: string,\n data: Record<string, unknown>,\n opts?: FormSubmitOptions,\n ): Promise<FormSubmitResult>;\n\n /**\n * Fetch a media item's detail (URLs + variants). Requires a key that may read\n * media; a public delivery key cannot, so this is used in authoring/preview\n * contexts or by sites configured with a media-capable key.\n */\n getMedia(mediaId: string): Promise<MediaDetail>;\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}${path}`;\n}\n\nexport function createClient(config: ClientConfig): PlenaryClient {\n const base: QueryBase = { siteKey: config.siteKey, locale: config.locale };\n const doFetch = config.fetch ?? globalThis.fetch;\n\n async function request<T>(\n method: string,\n path: string,\n params: URLSearchParams,\n body?: unknown,\n ): Promise<{ data: T; response: Response }> {\n const url = `${joinUrl(config.baseUrl, path)}?${params.toString()}`;\n const headers: Record<string, string> = { \"X-API-KEY\": config.apiKey };\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const response = await doFetch(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n const parsed = response.status === 204 ? null : await response.json().catch(() => null);\n if (!response.ok) {\n throw errorFromResponse(response.status, parsed);\n }\n return { data: parsed as T, response };\n }\n\n return {\n async getSingleton<TData>(modelKey: string, opts: ReadOptions = {}) {\n const params = buildReadParams(base, opts);\n const { data } = await request<Entry<TData>>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}`,\n params,\n );\n return data;\n },\n\n async getEntry<TData>(modelKey: string, slugOrId: string, opts: ReadOptions = {}) {\n const params = buildReadParams(base, opts);\n const { data } = await request<Entry<TData>>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}/${encodeURIComponent(slugOrId)}`,\n params,\n );\n return data;\n },\n\n async listEntries<TData>(modelKey: string, opts: ListOptions = {}) {\n const params = buildListParams(base, opts);\n const { data, response } = await request<Entry<TData>[]>(\n \"GET\",\n `/api/v1/content/${encodeURIComponent(modelKey)}`,\n params,\n );\n const result: ListResult<TData> = { items: data };\n const total = response.headers.get(\"X-Total-Count\");\n if (total !== null) result.total = Number(total);\n return result;\n },\n\n async getForm(formKey: string) {\n const { data } = await request<PublicFormDefinition>(\n \"GET\",\n `/api/v1/forms/${encodeURIComponent(formKey)}`,\n new URLSearchParams(),\n );\n return data;\n },\n\n async submitForm(\n formKey: string,\n data: Record<string, unknown>,\n opts: FormSubmitOptions = {},\n ) {\n const params = new URLSearchParams();\n params.set(\"site_key\", opts.siteKey ?? base.siteKey);\n const body: Record<string, unknown> = { data };\n if (opts.website !== undefined) body.website = opts.website;\n if (opts.ts !== undefined) body.ts = opts.ts;\n const { data: result } = await request<FormSubmitResult>(\n \"POST\",\n `/api/v1/forms/${encodeURIComponent(formKey)}/entries`,\n params,\n body,\n );\n return result;\n },\n\n async getMedia(mediaId: string) {\n const { data } = await request<MediaDetail>(\n \"GET\",\n `/api/v1/media/${encodeURIComponent(mediaId)}`,\n new URLSearchParams(),\n );\n return data;\n },\n };\n}\n","/**\n * Webhook consumer helpers: the payload types Plenary POSTs\n * and framework-neutral verification/parsing. Plenary does not sign requests\n * yet; verification is a shared-secret comparison against a header or query\n * parameter. When HMAC signing ships, it slots in here without changing the\n * call sites.\n */\n\n/** The five entry lifecycle events a content-model webhook can subscribe to. */\nexport type EntryWebhookEvent =\n | \"entry.created\"\n | \"entry.updated\"\n | \"entry.published\"\n | \"entry.unpublished\"\n | \"entry.deleted\";\n\nexport const ENTRY_WEBHOOK_EVENTS: readonly EntryWebhookEvent[] = [\n \"entry.created\",\n \"entry.updated\",\n \"entry.published\",\n \"entry.unpublished\",\n \"entry.deleted\",\n];\n\n/** The event carried by the console's Test button. The body is otherwise an\n * ordinary entry or form payload, built from the model's most recent entry. */\nexport const WEBHOOK_TEST_EVENT = \"webhook.test\";\n\n/** Who caused the event. `label` survives the actor's deletion. */\nexport interface WebhookActor {\n type: \"user\" | \"api_key\" | \"system\";\n id?: string;\n label: string;\n}\n\n/** The entry's identity. Field selection never touches these. */\nexport interface WebhookEntryRef {\n id: string;\n site_key?: string;\n locale?: string;\n slug?: string;\n /** Directory path of the entry's folder (\"/\" at the model root). Does NOT\n * include the entry's own slug — join them to build a URL. */\n path?: string;\n status?: string;\n created_at?: string;\n updated_at?: string;\n published_at?: string;\n}\n\n/** A form entry's identity: no slug, locale, path, or publish state. */\nexport interface WebhookFormEntryRef {\n id: string;\n site_key?: string;\n status?: string;\n created_at?: string;\n source?: { ip?: string; user_agent?: string; referer?: string };\n}\n\n/** Payload for every `entry.*` event (and entry-shaped `webhook.test`). */\nexport interface EntryWebhookPayload<TData = Record<string, unknown>> {\n event: EntryWebhookEvent | typeof WEBHOOK_TEST_EVENT;\n delivery_id: string;\n occurred_at: string;\n webhook: { id: string; name?: string };\n model: { id: string; key?: string };\n entry: WebhookEntryRef;\n actor: WebhookActor;\n /** Paths that differ from the previous revision. Always present; empty for\n * `entry.created` and `entry.deleted`. */\n changed: string[];\n revision_id?: string;\n /** Field values per the webhook's field selection; absent for \"no field data\". */\n data?: TData;\n}\n\n/** Payload for `form.submitted` (and form-shaped `webhook.test`). */\nexport interface FormWebhookPayload<TData = Record<string, unknown>> {\n event: \"form.submitted\" | typeof WEBHOOK_TEST_EVENT;\n delivery_id: string;\n occurred_at: string;\n webhook: { id: string; name?: string };\n form: { id: string; key?: string };\n form_entry: WebhookFormEntryRef;\n data?: TData;\n}\n\nexport type WebhookPayload = EntryWebhookPayload | FormWebhookPayload;\n\nexport function isEntryWebhookPayload(p: WebhookPayload): p is EntryWebhookPayload {\n return \"entry\" in p;\n}\n\nexport function isFormWebhookPayload(p: WebhookPayload): p is FormWebhookPayload {\n return \"form_entry\" in p;\n}\n\n/** A request that is not a valid webhook delivery. `status` is the HTTP status\n * an endpoint should answer with — 4xx makes the delivery fail fast in the\n * console's delivery log instead of being retried for seven hours. */\nexport class PlenaryWebhookError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = \"PlenaryWebhookError\";\n this.status = status;\n }\n}\n\n/** Constant-time comparison of a provided secret against the expected one.\n * An empty or missing `secret` never matches, so an unset environment\n * variable can't turn into \"accept an empty header\". */\nexport function verifyWebhookSecret(\n provided: string | null | undefined,\n secret: string,\n): boolean {\n if (typeof provided !== \"string\" || !secret) return false;\n const enc = new TextEncoder();\n const a = enc.encode(provided);\n const b = enc.encode(secret);\n let diff = a.length ^ b.length;\n const len = Math.max(a.length, b.length);\n for (let i = 0; i < len; i++) diff |= (a[i] ?? 0) ^ (b[i] ?? 0);\n return diff === 0;\n}\n\n/** Shape-checks a parsed JSON body and types it. Throws {@link PlenaryWebhookError}\n * (400) for anything that is not a Plenary webhook payload. */\nexport function parseWebhookPayload(body: unknown): WebhookPayload {\n const p = body as Record<string, unknown> | null;\n if (\n p === null ||\n typeof p !== \"object\" ||\n typeof p[\"event\"] !== \"string\" ||\n typeof p[\"delivery_id\"] !== \"string\" ||\n !(\"entry\" in p || \"form_entry\" in p)\n ) {\n throw new PlenaryWebhookError(400, \"Not a Plenary webhook payload\");\n }\n return p as unknown as WebhookPayload;\n}\n\n/** Options for {@link parseWebhookRequest}. */\nexport interface ParseWebhookOptions {\n /** The shared secret configured on the webhook (as a secret header or query\n * parameter in the console). */\n secret: string;\n /** Header the secret arrives in. Default `x-plenary-secret`. */\n secretHeader?: string;\n /** Query parameter checked when the header is absent. Default `secret`. */\n secretQuery?: string;\n}\n\n/**\n * Verifies and parses a webhook delivery from a Fetch-API `Request`:\n * POST only, secret in the configured header or query parameter, JSON body of\n * one of the two payload shapes. Throws {@link PlenaryWebhookError} with the\n * status the endpoint should answer (405, 401, or 400).\n */\nexport async function parseWebhookRequest(\n request: Request,\n options: ParseWebhookOptions,\n): Promise<WebhookPayload> {\n if (request.method !== \"POST\") {\n throw new PlenaryWebhookError(405, \"Method not allowed\");\n }\n if (!options.secret) {\n throw new PlenaryWebhookError(500, \"Webhook secret is not configured\");\n }\n const header = options.secretHeader ?? \"x-plenary-secret\";\n const query = options.secretQuery ?? \"secret\";\n const provided =\n request.headers.get(header) ?? new URL(request.url).searchParams.get(query);\n if (!verifyWebhookSecret(provided, options.secret)) {\n throw new PlenaryWebhookError(401, \"Invalid webhook secret\");\n }\n const body = await request.json().catch(() => {\n throw new PlenaryWebhookError(400, \"Body is not valid JSON\");\n });\n return parseWebhookPayload(body);\n}\n\n/**\n * Revalidation planning — which site URLs one entry webhook affects. Shared by\n * the framework adapters (`@plenary/next`, `@plenary/nuxt`), which own the\n * cache calls; this part is pure.\n */\n\n/** Paths for one model: a fixed path, a list, or a function of the payload.\n * A function may return `null`/`undefined` to revalidate nothing. */\nexport type RevalidatePaths =\n | string\n | string[]\n | ((payload: EntryWebhookPayload) => string | string[] | null | undefined);\n\nexport interface RevalidateOptions extends ParseWebhookOptions {\n /**\n * Extra or overriding paths per model key. A mapped model replaces the\n * default URL entirely — include `entryUrl(payload)` (exported) in the\n * returned list to keep it.\n */\n paths?: Record<string, RevalidatePaths>;\n /** Builds the default URL for unmapped models. Defaults to joining the\n * entry's folder `path` and `slug`; return `null` to skip. */\n entryUrl?: (payload: EntryWebhookPayload) => string | null | undefined;\n /** Entry events that trigger revalidation. Default: all five — revalidation\n * is idempotent and cheap, so over-firing beats a stale page. */\n events?: EntryWebhookEvent[];\n}\n\n/** Default URL for an entry: folder path + slug (`/blog` + `spring-sale` →\n * `/blog/spring-sale`). A root-folder entry slugged `home` is the site's home\n * page, `/`. Entries without a slug (singletons) get no default. */\nexport function entryUrl(payload: EntryWebhookPayload): string | null {\n const { slug, path } = payload.entry;\n if (!slug) return null;\n const dir = path && path !== \"/\" ? path : \"\";\n if (!dir && slug === \"home\") return \"/\";\n return `${dir}/${slug}`;\n}\n\nexport interface RevalidationPlan {\n /** Whether this payload is one the handler acts on. */\n relevant: boolean;\n /** True for the console's Test button — report, don't revalidate. */\n test: boolean;\n paths: string[];\n}\n\n/** Decides which paths one payload revalidates. Form payloads and entry\n * events outside `options.events` are not relevant. */\nexport function planRevalidation(\n payload: WebhookPayload,\n options: RevalidateOptions,\n): RevalidationPlan {\n const none = { relevant: false, test: false, paths: [] };\n if (!isEntryWebhookPayload(payload)) return none;\n\n const test = payload.event === WEBHOOK_TEST_EVENT;\n if (!test && options.events && !options.events.includes(payload.event as EntryWebhookEvent)) {\n return none;\n }\n\n const modelKey = payload.model.key;\n const mapped = modelKey === undefined ? undefined : options.paths?.[modelKey];\n let paths: string | string[] | null | undefined;\n if (mapped === undefined) {\n paths = (options.entryUrl ?? entryUrl)(payload);\n } else {\n paths = typeof mapped === \"function\" ? mapped(payload) : mapped;\n }\n const pathList = paths == null ? [] : typeof paths === \"string\" ? [paths] : paths;\n\n return { relevant: true, test, paths: [...new Set(pathList)] };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plenary/client",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Plenary framework-free API client: content fetch, form submission, and query builder.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -18,7 +18,7 @@
18
18
  "dist"
19
19
  ],
20
20
  "dependencies": {
21
- "@plenary/core": "1.0.1"
21
+ "@plenary/core": "1.1.0"
22
22
  },
23
23
  "license": "MIT",
24
24
  "scripts": {