@createcms/core 0.1.1
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/README.md +169 -0
- package/dist/ab-edge/index.cjs +214 -0
- package/dist/ab-edge/index.d.cts +121 -0
- package/dist/ab-edge/index.d.ts +121 -0
- package/dist/ab-edge/index.js +205 -0
- package/dist/bin/createcms.js +3082 -0
- package/dist/db.cjs +496 -0
- package/dist/db.d.cts +128 -0
- package/dist/db.d.ts +128 -0
- package/dist/db.js +488 -0
- package/dist/index.cjs +13789 -0
- package/dist/index.d.cts +10277 -0
- package/dist/index.d.ts +10277 -0
- package/dist/index.js +13737 -0
- package/dist/nanoid.cjs +50 -0
- package/dist/nanoid.d.cts +29 -0
- package/dist/nanoid.d.ts +29 -0
- package/dist/nanoid.js +47 -0
- package/dist/next/index.cjs +60 -0
- package/dist/next/index.d.cts +141 -0
- package/dist/next/index.d.ts +141 -0
- package/dist/next/index.js +58 -0
- package/dist/next/middleware.cjs +113 -0
- package/dist/next/middleware.d.cts +77 -0
- package/dist/next/middleware.d.ts +77 -0
- package/dist/next/middleware.js +111 -0
- package/dist/plugins/ab-test/analytics/upstash.cjs +345 -0
- package/dist/plugins/ab-test/analytics/upstash.d.cts +193 -0
- package/dist/plugins/ab-test/analytics/upstash.d.ts +193 -0
- package/dist/plugins/ab-test/analytics/upstash.js +343 -0
- package/dist/plugins/ab-test/client.cjs +686 -0
- package/dist/plugins/ab-test/client.d.cts +233 -0
- package/dist/plugins/ab-test/client.d.ts +233 -0
- package/dist/plugins/ab-test/client.js +684 -0
- package/dist/plugins/ab-test/index.cjs +3400 -0
- package/dist/plugins/ab-test/index.d.cts +1131 -0
- package/dist/plugins/ab-test/index.d.ts +1131 -0
- package/dist/plugins/ab-test/index.js +3367 -0
- package/dist/plugins/client.cjs +20 -0
- package/dist/plugins/client.d.cts +3 -0
- package/dist/plugins/client.d.ts +3 -0
- package/dist/plugins/client.js +3 -0
- package/dist/plugins/consent/client.cjs +315 -0
- package/dist/plugins/consent/client.d.cts +145 -0
- package/dist/plugins/consent/client.d.ts +145 -0
- package/dist/plugins/consent/client.js +313 -0
- package/dist/plugins/consent/index.cjs +267 -0
- package/dist/plugins/consent/index.d.cts +618 -0
- package/dist/plugins/consent/index.d.ts +618 -0
- package/dist/plugins/consent/index.js +258 -0
- package/dist/plugins/i18n/index.cjs +2177 -0
- package/dist/plugins/i18n/index.d.cts +562 -0
- package/dist/plugins/i18n/index.d.ts +562 -0
- package/dist/plugins/i18n/index.js +2150 -0
- package/dist/plugins/media-optimize/index.cjs +315 -0
- package/dist/plugins/media-optimize/index.d.cts +144 -0
- package/dist/plugins/media-optimize/index.d.ts +144 -0
- package/dist/plugins/media-optimize/index.js +311 -0
- package/dist/plugins/multi-tenant/index.cjs +210 -0
- package/dist/plugins/multi-tenant/index.d.cts +431 -0
- package/dist/plugins/multi-tenant/index.d.ts +431 -0
- package/dist/plugins/multi-tenant/index.js +207 -0
- package/dist/plugins/server.cjs +24 -0
- package/dist/plugins/server.d.cts +3 -0
- package/dist/plugins/server.d.ts +3 -0
- package/dist/plugins/server.js +3 -0
- package/dist/react/blocks.cjs +233 -0
- package/dist/react/blocks.d.cts +320 -0
- package/dist/react/blocks.d.ts +320 -0
- package/dist/react/blocks.js +226 -0
- package/dist/react/index.cjs +901 -0
- package/dist/react/index.d.cts +992 -0
- package/dist/react/index.d.ts +992 -0
- package/dist/react/index.js +872 -0
- package/dist/react/tracking.cjs +243 -0
- package/dist/react/tracking.d.cts +364 -0
- package/dist/react/tracking.d.ts +364 -0
- package/dist/react/tracking.js +216 -0
- package/dist/react/variant.cjs +59 -0
- package/dist/react/variant.d.cts +26 -0
- package/dist/react/variant.d.ts +26 -0
- package/dist/react/variant.js +57 -0
- package/package.json +303 -0
|
@@ -0,0 +1,992 @@
|
|
|
1
|
+
import { WritableAtom, ReadableAtom } from 'nanostores';
|
|
2
|
+
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
3
|
+
import { AnyPgTable, PgDatabase } from 'drizzle-orm/pg-core';
|
|
4
|
+
import { AnyColumn, SQL } from 'drizzle-orm';
|
|
5
|
+
import { Endpoint, Middleware } from 'better-call';
|
|
6
|
+
export { BlockComponentProps, BlockProps, BlocksMap, BlocksRenderer, createBlocksMap, createBlocksRenderer, createContentRenderer, extractBlockEvents } from './blocks.cjs';
|
|
7
|
+
export { pickVariant } from './variant.cjs';
|
|
8
|
+
|
|
9
|
+
type ResolvedUserConfig = {
|
|
10
|
+
table: AnyPgTable;
|
|
11
|
+
tableName: string;
|
|
12
|
+
schemaName: string | null;
|
|
13
|
+
idColumn: AnyColumn;
|
|
14
|
+
/** The camelCase key used in the Drizzle table definition (e.g. "id"). */
|
|
15
|
+
idColumnKey: string;
|
|
16
|
+
/** The actual database column name (e.g. "id" or "user_id"). */
|
|
17
|
+
idColumnDbName: string;
|
|
18
|
+
allColumns: Record<string, AnyColumn>;
|
|
19
|
+
/** Allowlist (camelCase keys) of columns exposable via `withUser`. */
|
|
20
|
+
exposeColumns: string[];
|
|
21
|
+
sqlTableRef: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type DrizzleInstance = PgDatabase<any, Record<string, unknown>, any>;
|
|
25
|
+
|
|
26
|
+
declare const notificationTypeEnum: drizzle_orm_pg_core.PgEnum<["mention", "comment", "threadResolved", "approvalRequested", "approvalApproved", "approvalRejected", "mergeRequestOpened", "mergeRequestMerged", "mergeRequestClosed", "mergeRequestReopened", "published", "custom"]>;
|
|
27
|
+
|
|
28
|
+
type NotificationType = (typeof notificationTypeEnum.enumValues)[number];
|
|
29
|
+
type NotificationPayload = {
|
|
30
|
+
id: string;
|
|
31
|
+
recipientId: string;
|
|
32
|
+
actorId: string | null;
|
|
33
|
+
type: NotificationType;
|
|
34
|
+
title: string;
|
|
35
|
+
body: string | null;
|
|
36
|
+
resourceType: string | null;
|
|
37
|
+
resourceId: string | null;
|
|
38
|
+
collection: string | null;
|
|
39
|
+
meta: Record<string, unknown> | null;
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
};
|
|
42
|
+
type OnNotificationHandler = (notification: NotificationPayload) => void | Promise<void>;
|
|
43
|
+
type NotificationInput = Omit<NotificationPayload, 'id' | 'createdAt'>;
|
|
44
|
+
|
|
45
|
+
type NotificationService = ReturnType<typeof createNotificationService>;
|
|
46
|
+
declare function createNotificationService(db: DrizzleInstance, handlers: OnNotificationHandler[]): {
|
|
47
|
+
notify(input: NotificationInput): Promise<NotificationPayload>;
|
|
48
|
+
notifyMany(inputs: NotificationInput[]): Promise<NotificationPayload[]>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Per-request scope produced by a ScopeConditionFactory.
|
|
53
|
+
* `where` — appended to SELECT/UPDATE/DELETE queries.
|
|
54
|
+
* `insertColumns` — snake_case column name → value pairs merged directly
|
|
55
|
+
* into the raw SQL INSERT via `scopedInsert` / `scopedInsertBatch`.
|
|
56
|
+
*/
|
|
57
|
+
type TableScope = {
|
|
58
|
+
where?: SQL;
|
|
59
|
+
insertColumns?: Record<string, unknown>;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* `roots` scope additionally supports a per-NEW-ENTRY column contributor: a
|
|
63
|
+
* plugin can compute fresh insert columns once per newly-created logical entry
|
|
64
|
+
* (e.g. a freshly minted translation-group id), which the static `insertColumns`
|
|
65
|
+
* channel (same value on every row) can't express. Called once per
|
|
66
|
+
* createRoot / root-duplication. Generic — core names no column. (Seam D.)
|
|
67
|
+
*/
|
|
68
|
+
type RootTableScope = TableScope & {
|
|
69
|
+
newEntryColumns?: () => Record<string, unknown>;
|
|
70
|
+
/**
|
|
71
|
+
* Scope columns to EXCLUDE from cross-scope read filtering — columns the
|
|
72
|
+
* plugin varies INDEPENDENTLY of a query so that cross-scope reads (a
|
|
73
|
+
* reference/host/usage that legitimately spans them) must not filter on them.
|
|
74
|
+
* The i18n plugin declares `['language']` (a host/reference in any sibling
|
|
75
|
+
* language still counts; the read path already resolved a specific sibling).
|
|
76
|
+
* Generic — core names no column; passed to `rootScopeConditions` as its
|
|
77
|
+
* `exclude`. Empty/absent → every scope column filters. (Seam D6.)
|
|
78
|
+
*/
|
|
79
|
+
crossScopeExclude?: readonly string[];
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* A plugin-provided resolver for reference values (rootId / group-key strings),
|
|
83
|
+
* carried on the resolved scope and consumed by the read path and the A/B
|
|
84
|
+
* co-render walk. Core ships an IDENTITY default (`coreReferenceResolver`)
|
|
85
|
+
* reproducing the single-language, no-plugin behaviour byte-for-byte; the i18n
|
|
86
|
+
* plugin supplies a real one that understands translation groups + the fallback
|
|
87
|
+
* chain. Core never names any i18n concept — it knows only this interface.
|
|
88
|
+
*
|
|
89
|
+
* `db` AND `scopeColumns` are passed PER CALL (not closed over): `db` so a
|
|
90
|
+
* caller inside a transaction (e.g. the A/B →running guard under FOR UPDATE)
|
|
91
|
+
* resolves against its own tx handle; `scopeColumns` because the MERGED root
|
|
92
|
+
* scope columns (tenant + language) exist only AFTER every scope factory has
|
|
93
|
+
* run — the i18n factory that builds the resolver sees only its OWN column at
|
|
94
|
+
* build time. The resolver therefore closes over just its resolution POLICY
|
|
95
|
+
* (e.g. the i18n active language + fallback chain). `scopeColumns` is the
|
|
96
|
+
* scope predicate; the resolver excludes its own cross-scope columns.
|
|
97
|
+
* (Seam B.)
|
|
98
|
+
*/
|
|
99
|
+
type ReferenceResolver = {
|
|
100
|
+
/**
|
|
101
|
+
* Read-time render pick: stored reference value → the ONE rootId it renders
|
|
102
|
+
* as (omit a key to leave it unresolved). Identity default: `value → value`.
|
|
103
|
+
*/
|
|
104
|
+
resolveRenderTargets(db: DrizzleInstance, scopeColumns: Record<string, unknown> | undefined, collection: string, storedValues: string[]): Promise<Map<string, string>>;
|
|
105
|
+
/**
|
|
106
|
+
* Conflict superset: stored reference keys → ALL rootIds they could render as
|
|
107
|
+
* (a group key expands to its whole group). Used by the A/B co-render walk;
|
|
108
|
+
* collection-agnostic (a reference may target any collection). Identity
|
|
109
|
+
* default: the existing, non-archived roots among `storedKeys` (by id).
|
|
110
|
+
*/
|
|
111
|
+
resolveConflictTargets(db: DrizzleInstance, scopeColumns: Record<string, unknown> | undefined, storedKeys: string[]): Promise<string[]>;
|
|
112
|
+
/** rootIds → all their group siblings. Identity default: the input rootIds. */
|
|
113
|
+
expandGroup(db: DrizzleInstance, scopeColumns: Record<string, unknown> | undefined, rootIds: string[]): Promise<string[]>;
|
|
114
|
+
/** rootIds → the group keys a host could embed them by. Default: `[]`. */
|
|
115
|
+
groupKeysFor(db: DrizzleInstance, scopeColumns: Record<string, unknown> | undefined, rootIds: string[]): Promise<string[]>;
|
|
116
|
+
};
|
|
117
|
+
/** One variant branch of a running A/B test on a referenced root. */
|
|
118
|
+
type RunningAbTestVariant = {
|
|
119
|
+
branchId: string;
|
|
120
|
+
isControl: boolean;
|
|
121
|
+
};
|
|
122
|
+
/** A running A/B test on one root: the test plus its variant branches. */
|
|
123
|
+
type RunningAbTest = {
|
|
124
|
+
testId: string;
|
|
125
|
+
trafficPercentage: number;
|
|
126
|
+
variants: RunningAbTestVariant[];
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* A plugin-provided resolver that reports which referenced roots currently have
|
|
130
|
+
* a RUNNING A/B test (with that test's variant branches). Carried on the
|
|
131
|
+
* resolved scope and consumed by the read path's reference loader to fan the one
|
|
132
|
+
* XOR-guaranteed varying block's branches out to the client (AB_FANOUT F2). Core
|
|
133
|
+
* ships NO default — when absent (no ab-test plugin) the read path assumes no
|
|
134
|
+
* running tests and every embed stays on its deterministic single pick (F0).
|
|
135
|
+
* Core never names any A/B concept beyond this interface. (Seam F.)
|
|
136
|
+
*/
|
|
137
|
+
type AbTestResolver = {
|
|
138
|
+
/**
|
|
139
|
+
* The subset of `rootIds` that have a running test, each mapped to its test +
|
|
140
|
+
* variant branches. `db` AND `scopeColumns` are passed PER CALL (same
|
|
141
|
+
* rationale as {@link ReferenceResolver}). The caller passes already
|
|
142
|
+
* render-resolved (active-language) rootIds, so this needs no group expansion.
|
|
143
|
+
*/
|
|
144
|
+
runningTests(db: DrizzleInstance, scopeColumns: Record<string, unknown> | undefined, rootIds: string[]): Promise<Map<string, RunningAbTest>>;
|
|
145
|
+
};
|
|
146
|
+
type ResolvedScope = {
|
|
147
|
+
roots?: RootTableScope;
|
|
148
|
+
assets?: TableScope;
|
|
149
|
+
assetFolders?: TableScope;
|
|
150
|
+
redirects?: TableScope;
|
|
151
|
+
/**
|
|
152
|
+
* Plugin-provided reference resolver (i18n translation-group resolution). When
|
|
153
|
+
* absent, callers use core's identity default. Generic — see `ReferenceResolver`.
|
|
154
|
+
*/
|
|
155
|
+
referenceResolver?: ReferenceResolver;
|
|
156
|
+
/**
|
|
157
|
+
* Plugin-provided running-A/B-test resolver (AB_FANOUT F2 server fan-out).
|
|
158
|
+
* When absent, the read path assumes no running tests. Generic — see
|
|
159
|
+
* {@link AbTestResolver}.
|
|
160
|
+
*/
|
|
161
|
+
abTestResolver?: AbTestResolver;
|
|
162
|
+
/**
|
|
163
|
+
* Opaque per-plugin context slots, keyed by plugin id. Core never reads it;
|
|
164
|
+
* each plugin stashes its own per-request context here from a scope factory
|
|
165
|
+
* and reads it back via its own exported accessor. Merged generically in
|
|
166
|
+
* computeScope (shallow, last-writer-wins per slot).
|
|
167
|
+
*/
|
|
168
|
+
pluginContext?: Record<string, unknown>;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Factory registered by plugins during `init`.
|
|
172
|
+
* Called once per request with the middleware result to produce
|
|
173
|
+
* table-level WHERE conditions and extra INSERT values.
|
|
174
|
+
*/
|
|
175
|
+
type ScopeConditionFactory = (mwResult: MiddlewareResult) => ResolvedScope;
|
|
176
|
+
type BlockTypes = {
|
|
177
|
+
string: string;
|
|
178
|
+
number: number;
|
|
179
|
+
boolean: boolean;
|
|
180
|
+
date: string;
|
|
181
|
+
richText: string;
|
|
182
|
+
image: string;
|
|
183
|
+
select: string;
|
|
184
|
+
reference: string;
|
|
185
|
+
};
|
|
186
|
+
type BlockPropertyType = keyof BlockTypes;
|
|
187
|
+
type SelectOption = {
|
|
188
|
+
readonly label: string;
|
|
189
|
+
readonly value: string;
|
|
190
|
+
};
|
|
191
|
+
type BlockPropertySpec<T extends BlockPropertyType> = {
|
|
192
|
+
type: T;
|
|
193
|
+
required?: boolean;
|
|
194
|
+
defaultValue?: BlockTypes[T];
|
|
195
|
+
label: string;
|
|
196
|
+
description?: string;
|
|
197
|
+
placeholder?: string;
|
|
198
|
+
} & (T extends 'select' ? {
|
|
199
|
+
options: readonly SelectOption[];
|
|
200
|
+
} : {}) & (T extends 'reference' ? {
|
|
201
|
+
collection: string;
|
|
202
|
+
} : {});
|
|
203
|
+
/** Discriminated union over all concrete block-property specs. */
|
|
204
|
+
type BlockProperty = {
|
|
205
|
+
[K in BlockPropertyType]: BlockPropertySpec<K>;
|
|
206
|
+
}[BlockPropertyType];
|
|
207
|
+
/** Scalar property subset usable as an event parameter (no references/media). */
|
|
208
|
+
type ScalarBlockProperty = Extract<BlockProperty, {
|
|
209
|
+
type: 'string' | 'number' | 'boolean' | 'select' | 'date';
|
|
210
|
+
}>;
|
|
211
|
+
/**
|
|
212
|
+
* Declares a meaningful event a functional block can emit (e.g. a form's
|
|
213
|
+
* `submitSuccess`). Living on the block DEFINITION makes it the single source of
|
|
214
|
+
* truth for the typed `fire(...)` union, the test-creation goal picker, and the
|
|
215
|
+
* analytics wire name. `name` overrides the GA4/dataLayer wire name (defaults to
|
|
216
|
+
* `cms_<blockType>_<eventKey>`, computed by the measurement layer). Whether an
|
|
217
|
+
* event counts as a conversion is decided per test in the UI, not here.
|
|
218
|
+
*/
|
|
219
|
+
type EventDeclaration = {
|
|
220
|
+
/** Analytics wire-name override (snake_case). Defaults to cms_<type>_<key>. */
|
|
221
|
+
name?: string;
|
|
222
|
+
/** Typed parameters carried with the event (scalar only). */
|
|
223
|
+
params?: Record<string, ScalarBlockProperty>;
|
|
224
|
+
/** Human label for the goal picker. */
|
|
225
|
+
label?: string;
|
|
226
|
+
};
|
|
227
|
+
type BlockDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>, TEvents extends Record<string, EventDeclaration> = Record<string, never>> = {
|
|
228
|
+
properties: TProps;
|
|
229
|
+
label: string;
|
|
230
|
+
description?: string;
|
|
231
|
+
previewImageUrl?: string;
|
|
232
|
+
/** Events this (functional) block can emit — see {@link EventDeclaration}. */
|
|
233
|
+
events?: TEvents;
|
|
234
|
+
} & ({
|
|
235
|
+
allowChildren?: false;
|
|
236
|
+
} | {
|
|
237
|
+
allowChildren: true;
|
|
238
|
+
allowedChildBlocks?: string[];
|
|
239
|
+
});
|
|
240
|
+
type AnyBlockDefinition = BlockDefinition<Record<string, BlockProperty>, Record<string, EventDeclaration>>;
|
|
241
|
+
type RootDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>> = {
|
|
242
|
+
properties: TProps;
|
|
243
|
+
};
|
|
244
|
+
type SlugConfig = {
|
|
245
|
+
enabled: false;
|
|
246
|
+
} | {
|
|
247
|
+
enabled: true;
|
|
248
|
+
root: string;
|
|
249
|
+
allowRoot?: boolean;
|
|
250
|
+
normalize?: boolean;
|
|
251
|
+
nested?: boolean;
|
|
252
|
+
};
|
|
253
|
+
type CollectionDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>, TBlocks extends Record<string, AnyBlockDefinition> = Record<string, AnyBlockDefinition>> = {
|
|
254
|
+
slug?: SlugConfig;
|
|
255
|
+
root: RootDefinition<TProps>;
|
|
256
|
+
blocks?: TBlocks;
|
|
257
|
+
label: string;
|
|
258
|
+
description?: string;
|
|
259
|
+
/**
|
|
260
|
+
* Marks this collection as one whose roots are meant to be EMBEDDED into other
|
|
261
|
+
* roots via a `reference` property (a "reusable block" library). Purely an
|
|
262
|
+
* ergonomic hint — it informs editor pickers and which endpoints to surface; it
|
|
263
|
+
* NEVER gates safety (the delete-in-use guard protects every referenced root
|
|
264
|
+
* regardless of this flag). Any collection can still be a reference target.
|
|
265
|
+
*/
|
|
266
|
+
reusableBlock?: boolean;
|
|
267
|
+
};
|
|
268
|
+
type AnyCollectionDefinition = CollectionDefinition<Record<string, BlockProperty>, Record<string, AnyBlockDefinition>>;
|
|
269
|
+
type CollectionWithName = Omit<AnyCollectionDefinition, 'blocks'> & {
|
|
270
|
+
name: string;
|
|
271
|
+
blocks: Record<string, AnyBlockDefinition>;
|
|
272
|
+
};
|
|
273
|
+
type DataRetentionConfig = {
|
|
274
|
+
keepDays: number;
|
|
275
|
+
keepMinCommits: number;
|
|
276
|
+
/**
|
|
277
|
+
* Grace period (days) before a soft-archived root (`archivedAt`) is physically
|
|
278
|
+
* hard-deleted by pruning. Defaults to `keepDays` when omitted — a trash
|
|
279
|
+
* window after which the page and its whole history are reclaimed.
|
|
280
|
+
*/
|
|
281
|
+
archiveKeepDays?: number;
|
|
282
|
+
};
|
|
283
|
+
/** Result that user middleware can return to extend context */
|
|
284
|
+
type MiddlewareResult = {
|
|
285
|
+
userId?: string;
|
|
286
|
+
[key: string]: unknown;
|
|
287
|
+
};
|
|
288
|
+
/** Base ctx injected by withCMSContext middleware. */
|
|
289
|
+
type CMSProcedureCtx = {
|
|
290
|
+
db: DrizzleInstance;
|
|
291
|
+
collections: Record<string, CollectionWithName>;
|
|
292
|
+
dataRetention?: DataRetentionConfig;
|
|
293
|
+
scopeConditions?: ScopeConditionFactory[];
|
|
294
|
+
notificationService?: NotificationService;
|
|
295
|
+
resolvedUser?: ResolvedUserConfig;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
type SchemaNamespace = 'cms';
|
|
299
|
+
type ColumnScalarType = 'text' | 'boolean' | 'integer' | 'timestamp' | 'jsonb' | 'tsvector';
|
|
300
|
+
type ColumnType<EnumTarget extends string = string> = ColumnScalarType | {
|
|
301
|
+
enum: EnumTarget;
|
|
302
|
+
};
|
|
303
|
+
type DefaultValue = {
|
|
304
|
+
kind: 'literal';
|
|
305
|
+
value: boolean | number | string | string[] | Record<string, unknown>;
|
|
306
|
+
} | {
|
|
307
|
+
kind: 'sql';
|
|
308
|
+
value: string;
|
|
309
|
+
};
|
|
310
|
+
type ForeignKeyAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default';
|
|
311
|
+
type IndexUsing = 'btree' | 'gin';
|
|
312
|
+
type ColumnDefinition<ReferenceTarget extends string = string, EnumTarget extends string = string> = {
|
|
313
|
+
type: ColumnType<EnumTarget>;
|
|
314
|
+
columnName?: string;
|
|
315
|
+
notNull?: boolean;
|
|
316
|
+
primaryKey?: boolean;
|
|
317
|
+
unique?: boolean;
|
|
318
|
+
default?: DefaultValue;
|
|
319
|
+
defaultId?: boolean;
|
|
320
|
+
defaultIdPrefix?: string;
|
|
321
|
+
defaultNow?: boolean;
|
|
322
|
+
jsonType?: string;
|
|
323
|
+
references?: {
|
|
324
|
+
table: ReferenceTarget;
|
|
325
|
+
column: string;
|
|
326
|
+
onDelete?: ForeignKeyAction;
|
|
327
|
+
onUpdate?: ForeignKeyAction;
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
type TableColumns<ReferenceTarget extends string = string, EnumTarget extends string = string> = Record<string, ColumnDefinition<ReferenceTarget, EnumTarget>>;
|
|
331
|
+
type IndexDefinition<ColumnName extends string> = {
|
|
332
|
+
columns: readonly ColumnName[];
|
|
333
|
+
unique?: boolean;
|
|
334
|
+
using?: IndexUsing;
|
|
335
|
+
where?: string;
|
|
336
|
+
};
|
|
337
|
+
type CompositePrimaryKey<ColumnName extends string> = {
|
|
338
|
+
columns: readonly ColumnName[];
|
|
339
|
+
};
|
|
340
|
+
type TableLevelForeignKey<ColumnName extends string = string> = {
|
|
341
|
+
columns: readonly ColumnName[];
|
|
342
|
+
foreignTable: string;
|
|
343
|
+
foreignColumns: readonly string[];
|
|
344
|
+
name?: string;
|
|
345
|
+
onDelete?: ForeignKeyAction;
|
|
346
|
+
onUpdate?: ForeignKeyAction;
|
|
347
|
+
};
|
|
348
|
+
type TableDefinition<Columns extends TableColumns = TableColumns, ReferenceTarget extends string = string, EnumTarget extends string = string> = {
|
|
349
|
+
tableName?: string;
|
|
350
|
+
indexPrefix?: string;
|
|
351
|
+
columns: Columns;
|
|
352
|
+
indexes?: Record<string, IndexDefinition<Extract<keyof Columns, string>>>;
|
|
353
|
+
compositePrimaryKey?: CompositePrimaryKey<Extract<keyof Columns, string>>;
|
|
354
|
+
foreignKeys?: TableLevelForeignKey<Extract<keyof Columns, string>>[];
|
|
355
|
+
};
|
|
356
|
+
type EnumDefinition = {
|
|
357
|
+
values: readonly string[];
|
|
358
|
+
enumName?: string;
|
|
359
|
+
};
|
|
360
|
+
type EnumMap = Record<string, EnumDefinition>;
|
|
361
|
+
type TableMap = Record<string, TableDefinition>;
|
|
362
|
+
type SchemaModule<_Namespace extends SchemaNamespace = SchemaNamespace, Tables extends TableMap = {}, Enums extends EnumMap = {}, Extensions = {}> = {
|
|
363
|
+
enums?: Enums;
|
|
364
|
+
tables?: Tables;
|
|
365
|
+
extend?: Extensions;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
type Awaitable<T> = T | Promise<T>;
|
|
369
|
+
/**
|
|
370
|
+
* Endpoint key used as a hook action identifier.
|
|
371
|
+
* Accepts any string so internal plumbing doesn't need the full union,
|
|
372
|
+
* but the exported `CMSEndpointKey` (from `index.ts`) provides a narrowed
|
|
373
|
+
* union with autocomplete for hook authors.
|
|
374
|
+
*/
|
|
375
|
+
type CMSHookAction = string & {};
|
|
376
|
+
type CMSBeforeHookContext = {
|
|
377
|
+
action: CMSHookAction;
|
|
378
|
+
collection: string;
|
|
379
|
+
db: DrizzleInstance;
|
|
380
|
+
input: Record<string, unknown>;
|
|
381
|
+
/**
|
|
382
|
+
* The resolved per-request scope (tenant/i18n), available to before-hooks so
|
|
383
|
+
* they can tenant-scope any cross-resource reads they perform. Set by the
|
|
384
|
+
* endpoint wrapper before hooks run; may be undefined for hooks invoked
|
|
385
|
+
* outside that path.
|
|
386
|
+
*/
|
|
387
|
+
scope?: ResolvedScope;
|
|
388
|
+
};
|
|
389
|
+
type CMSAfterHookContext = CMSBeforeHookContext & {
|
|
390
|
+
result: unknown;
|
|
391
|
+
};
|
|
392
|
+
type CMSBeforeHook = {
|
|
393
|
+
action: CMSHookAction | '*';
|
|
394
|
+
collection?: string;
|
|
395
|
+
handler: (ctx: CMSBeforeHookContext) => Promise<void | {
|
|
396
|
+
override?: Record<string, unknown>;
|
|
397
|
+
}>;
|
|
398
|
+
};
|
|
399
|
+
type CMSAfterHook = {
|
|
400
|
+
action: CMSHookAction | '*';
|
|
401
|
+
collection?: string;
|
|
402
|
+
handler: (ctx: CMSAfterHookContext) => Promise<void | {
|
|
403
|
+
response: unknown;
|
|
404
|
+
}>;
|
|
405
|
+
};
|
|
406
|
+
type CMSHooks = {
|
|
407
|
+
before?: CMSBeforeHook[];
|
|
408
|
+
after?: CMSAfterHook[];
|
|
409
|
+
};
|
|
410
|
+
type CMSPluginContext = CMSProcedureCtx & {
|
|
411
|
+
collections: Record<string, CollectionWithName>;
|
|
412
|
+
};
|
|
413
|
+
type CMSCoreRootPruningPlan = {
|
|
414
|
+
rootId: string;
|
|
415
|
+
deletableCommitIds: string[];
|
|
416
|
+
deletableBlockVersionIds: string[];
|
|
417
|
+
deletableSnapshotCount: number;
|
|
418
|
+
deletableMergeRequestIds: string[];
|
|
419
|
+
deletableApprovalIds: string[];
|
|
420
|
+
initialCommitId: string;
|
|
421
|
+
};
|
|
422
|
+
type CMSPluginPruningMetrics = Record<string, number>;
|
|
423
|
+
type CMSPluginRootPruningPlan<TData = unknown> = {
|
|
424
|
+
rootId: string;
|
|
425
|
+
data?: TData;
|
|
426
|
+
metrics?: CMSPluginPruningMetrics;
|
|
427
|
+
};
|
|
428
|
+
type CMSPluginPruningPlanContext = Omit<CMSPluginContext, 'dataRetention'> & {
|
|
429
|
+
db: DrizzleInstance;
|
|
430
|
+
dataRetention: DataRetentionConfig;
|
|
431
|
+
rootPlan: CMSCoreRootPruningPlan;
|
|
432
|
+
};
|
|
433
|
+
type CMSPluginPruningExecuteContext<TData = unknown> = Omit<CMSPluginContext, 'db' | 'dataRetention'> & {
|
|
434
|
+
tx: DrizzleInstance;
|
|
435
|
+
dataRetention: DataRetentionConfig;
|
|
436
|
+
rootPlan: CMSCoreRootPruningPlan;
|
|
437
|
+
pluginPlan: CMSPluginRootPruningPlan<TData>;
|
|
438
|
+
};
|
|
439
|
+
type CMSPluginPruningExecuteResult = {
|
|
440
|
+
metrics?: CMSPluginPruningMetrics;
|
|
441
|
+
};
|
|
442
|
+
type CMSPluginPruning<TData = unknown> = {
|
|
443
|
+
plan: (ctx: CMSPluginPruningPlanContext) => Promise<CMSPluginRootPruningPlan<TData> | null>;
|
|
444
|
+
execute?: (ctx: CMSPluginPruningExecuteContext<TData>) => Promise<CMSPluginPruningExecuteResult | void>;
|
|
445
|
+
};
|
|
446
|
+
type CMSPluginInitOptions = {
|
|
447
|
+
hooks?: CMSHooks;
|
|
448
|
+
};
|
|
449
|
+
type CMSPluginInitResult = {
|
|
450
|
+
context?: Record<string, unknown>;
|
|
451
|
+
options?: Partial<CMSPluginInitOptions>;
|
|
452
|
+
};
|
|
453
|
+
/**
|
|
454
|
+
* A CMS plugin can extend the system with custom endpoints, hooks,
|
|
455
|
+
* middleware, database tables, and data-retention pruning logic.
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```ts
|
|
459
|
+
* const myPlugin: CMSPlugin = {
|
|
460
|
+
* id: 'my-plugin',
|
|
461
|
+
* endpoints: { ... },
|
|
462
|
+
* hooks: { before: [...], after: [...] },
|
|
463
|
+
* async init(ctx) { return { context: { myService } }; },
|
|
464
|
+
* };
|
|
465
|
+
* ```
|
|
466
|
+
*/
|
|
467
|
+
type CMSPlugin<TPruningData = unknown> = {
|
|
468
|
+
id: string;
|
|
469
|
+
endpoints?: Record<string, Endpoint>;
|
|
470
|
+
/**
|
|
471
|
+
* Per-COLLECTION endpoints contributed by the plugin: called once per
|
|
472
|
+
* collection during API assembly, returning routes merged into THAT
|
|
473
|
+
* collection's endpoint record (so they surface at `cms.api.<collection>.x`,
|
|
474
|
+
* not the flat `cms.api.<pluginId>.x`). The per-collection analogue of the
|
|
475
|
+
* flat `endpoints` above. Generic — any plugin can attach a route to every
|
|
476
|
+
* collection (the i18n plugin uses it for createTranslation / listTranslations).
|
|
477
|
+
* (Seam A.)
|
|
478
|
+
*/
|
|
479
|
+
collectionEndpoints?: (def: CollectionWithName, ctx: CMSPluginContext) => Record<string, Endpoint>;
|
|
480
|
+
hooks?: {
|
|
481
|
+
before?: CMSBeforeHook[];
|
|
482
|
+
after?: CMSAfterHook[];
|
|
483
|
+
};
|
|
484
|
+
middlewares?: {
|
|
485
|
+
path: string;
|
|
486
|
+
middleware: Middleware;
|
|
487
|
+
}[];
|
|
488
|
+
schema?: SchemaModule;
|
|
489
|
+
pruning?: CMSPluginPruning<TPruningData>;
|
|
490
|
+
init?: (ctx: CMSPluginContext) => Awaitable<CMSPluginInitResult | void>;
|
|
491
|
+
onRequest?: (request: Request, ctx: CMSPluginContext) => Promise<{
|
|
492
|
+
response: Response;
|
|
493
|
+
} | {
|
|
494
|
+
request: Request;
|
|
495
|
+
} | void>;
|
|
496
|
+
onResponse?: (response: Response, ctx: CMSPluginContext) => Promise<{
|
|
497
|
+
response: Response;
|
|
498
|
+
} | void>;
|
|
499
|
+
onNotification?: OnNotificationHandler;
|
|
500
|
+
$ERROR_CODES?: Record<string, {
|
|
501
|
+
status: number;
|
|
502
|
+
message: string;
|
|
503
|
+
}>;
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
type CMSFetch = (path: string, options?: {
|
|
507
|
+
method?: string;
|
|
508
|
+
body?: unknown;
|
|
509
|
+
query?: unknown;
|
|
510
|
+
/**
|
|
511
|
+
* Forwarded to the underlying `fetch` (better-call → @better-fetch → native
|
|
512
|
+
* `fetch`). Set for fire-and-forget analytics beacons (the A/B event ingest)
|
|
513
|
+
* so the POST is NOT cancelled when the page unloads/navigates mid-request.
|
|
514
|
+
*/
|
|
515
|
+
keepalive?: boolean;
|
|
516
|
+
}) => Promise<unknown>;
|
|
517
|
+
type CMSAtomListener = {
|
|
518
|
+
matcher: (path: string) => boolean;
|
|
519
|
+
signal: string;
|
|
520
|
+
callback?: (path: string) => void;
|
|
521
|
+
};
|
|
522
|
+
interface CMSClientStore {
|
|
523
|
+
notify: (signal: string) => void;
|
|
524
|
+
listen: (signal: string, listener: () => void) => void;
|
|
525
|
+
atoms: Record<string, WritableAtom<unknown>>;
|
|
526
|
+
}
|
|
527
|
+
interface CMSClientPlugin {
|
|
528
|
+
id: string;
|
|
529
|
+
$InferServerPlugin?: CMSPlugin;
|
|
530
|
+
init?: ($fetch: CMSFetch, $store: CMSClientStore) => Promise<{
|
|
531
|
+
context?: Record<string, unknown>;
|
|
532
|
+
} | void>;
|
|
533
|
+
getActions?: ($fetch: CMSFetch, $store: CMSClientStore, baseURL: string) => Record<string, unknown>;
|
|
534
|
+
pathMethods?: Record<string, 'GET' | 'POST'>;
|
|
535
|
+
atomListeners?: CMSAtomListener[];
|
|
536
|
+
$ERROR_CODES?: Record<string, {
|
|
537
|
+
status: number;
|
|
538
|
+
message: string;
|
|
539
|
+
}>;
|
|
540
|
+
}
|
|
541
|
+
type MediaUploadFileState = {
|
|
542
|
+
name: string;
|
|
543
|
+
progress: number;
|
|
544
|
+
status: 'pending' | 'uploading' | 'done' | 'error';
|
|
545
|
+
error?: string;
|
|
546
|
+
optimized?: boolean;
|
|
547
|
+
originalVariantId?: string;
|
|
548
|
+
result?: {
|
|
549
|
+
id: string;
|
|
550
|
+
slug: string;
|
|
551
|
+
objectKey: string;
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
type MediaUploadOptions = {
|
|
555
|
+
folderId?: string;
|
|
556
|
+
};
|
|
557
|
+
type MediaUploadState = {
|
|
558
|
+
isUploading: boolean;
|
|
559
|
+
isAborted: boolean;
|
|
560
|
+
files: MediaUploadFileState[];
|
|
561
|
+
totalProgress: number;
|
|
562
|
+
error: unknown;
|
|
563
|
+
upload: (files: File[], options?: MediaUploadOptions) => Promise<void>;
|
|
564
|
+
abort: () => void;
|
|
565
|
+
reset: () => void;
|
|
566
|
+
};
|
|
567
|
+
type QueryState<T = unknown> = {
|
|
568
|
+
data: T | null;
|
|
569
|
+
error: unknown;
|
|
570
|
+
isPending: boolean;
|
|
571
|
+
isRefetching: boolean;
|
|
572
|
+
refetch: () => Promise<void>;
|
|
573
|
+
};
|
|
574
|
+
interface CMSClientOptions {
|
|
575
|
+
baseURL: string;
|
|
576
|
+
plugins?: CMSClientPlugin[];
|
|
577
|
+
}
|
|
578
|
+
type InferApi<T> = T extends {
|
|
579
|
+
api: infer A;
|
|
580
|
+
} ? A : {};
|
|
581
|
+
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
582
|
+
type InferPluginActions<P extends CMSClientPlugin[]> = UnionToIntersection<{
|
|
583
|
+
[K in keyof P]: P[K] extends CMSClientPlugin ? ReturnType<NonNullable<P[K]['getActions']>> extends infer A ? A extends Record<string, unknown> ? A : {} : {} : {};
|
|
584
|
+
}[number]>;
|
|
585
|
+
type InferClientPluginErrorCodes<P extends CMSClientPlugin[]> = UnionToIntersection<P[number] extends infer Plug ? Plug extends CMSClientPlugin ? Plug['$ERROR_CODES'] extends Record<string, any> ? Plug['$ERROR_CODES'] : {} : {} : {}>;
|
|
586
|
+
type WithMedia<T> = T extends {
|
|
587
|
+
media: infer M;
|
|
588
|
+
} ? Omit<T, 'media'> & {
|
|
589
|
+
media: M & {
|
|
590
|
+
useUploadAssets: () => MediaUploadState;
|
|
591
|
+
};
|
|
592
|
+
} : T & {
|
|
593
|
+
media: {
|
|
594
|
+
useUploadAssets: () => MediaUploadState;
|
|
595
|
+
};
|
|
596
|
+
};
|
|
597
|
+
type CMSClientInstance<TCMS = unknown, TPlugins extends CMSClientPlugin[] = CMSClientPlugin[]> = WithMedia<InferApi<TCMS>> & InferPluginActions<TPlugins> & {
|
|
598
|
+
$fetch: CMSFetch;
|
|
599
|
+
$store: CMSClientStore;
|
|
600
|
+
$ERROR_CODES: InferClientPluginErrorCodes<TPlugins>;
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* React hook that subscribes to a nanostores atom via `useSyncExternalStore`.
|
|
605
|
+
* Re-renders the component when the atom value changes.
|
|
606
|
+
*/
|
|
607
|
+
declare function useStore<T>(store: ReadableAtom<T>): T;
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Creates a type-safe React CMS client with plugin support.
|
|
611
|
+
*
|
|
612
|
+
* Plugin `init` functions run asynchronously on creation. The returned
|
|
613
|
+
* client is usable immediately — API calls will await init completion
|
|
614
|
+
* transparently.
|
|
615
|
+
*
|
|
616
|
+
* Atom hooks are wrapped in `useStore()` so they work as React hooks:
|
|
617
|
+
*
|
|
618
|
+
* ```tsx
|
|
619
|
+
* import { createCMSClient } from '@createcms/core/react';
|
|
620
|
+
*
|
|
621
|
+
* // Preferred: curried call preserves full plugin type inference
|
|
622
|
+
* const client = createCMSClient<typeof cms>()({
|
|
623
|
+
* baseURL: '/api/cms',
|
|
624
|
+
* plugins: [mediaOptimizeClient()],
|
|
625
|
+
* });
|
|
626
|
+
*
|
|
627
|
+
* // Also works: single call (plugin types inferred only when TCMS is omitted)
|
|
628
|
+
* const client2 = createCMSClient({
|
|
629
|
+
* baseURL: '/api/cms',
|
|
630
|
+
* plugins: [mediaOptimizeClient()],
|
|
631
|
+
* });
|
|
632
|
+
*
|
|
633
|
+
* function MyComponent() {
|
|
634
|
+
* const { data, isPending } = client.useMediaLibrary();
|
|
635
|
+
* }
|
|
636
|
+
* ```
|
|
637
|
+
*/
|
|
638
|
+
declare function createCMSClient<TCMS = unknown>(options: CMSClientOptions & {
|
|
639
|
+
plugins?: CMSClientPlugin[];
|
|
640
|
+
}): CMSClientInstance<TCMS, CMSClientPlugin[]>;
|
|
641
|
+
declare function createCMSClient<TCMS = unknown>(): <const TPlugins extends CMSClientPlugin[] = CMSClientPlugin[]>(options: CMSClientOptions & {
|
|
642
|
+
plugins?: TPlugins;
|
|
643
|
+
}) => CMSClientInstance<TCMS, TPlugins>;
|
|
644
|
+
|
|
645
|
+
interface CMSQueryOptions {
|
|
646
|
+
method?: string;
|
|
647
|
+
query?: Record<string, unknown>;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Creates a reactive query atom that fetches data from a CMS endpoint.
|
|
651
|
+
* Subscribes to one or more signal atoms and refetches when they toggle.
|
|
652
|
+
*
|
|
653
|
+
* Returns a nanostores `WritableAtom` — framework-agnostic, not a React hook.
|
|
654
|
+
*/
|
|
655
|
+
declare function createCMSQuery<T = unknown>(signals: WritableAtom<boolean> | WritableAtom<boolean>[], path: string, $fetch: CMSFetch, options?: CMSQueryOptions | (() => CMSQueryOptions)): WritableAtom<QueryState<T>>;
|
|
656
|
+
|
|
657
|
+
declare const CMS_ERRORS: {
|
|
658
|
+
readonly BRANCH_NOT_FOUND: {
|
|
659
|
+
readonly status: 404;
|
|
660
|
+
readonly message: "Branch not found";
|
|
661
|
+
};
|
|
662
|
+
readonly BLOCK_NOT_FOUND: {
|
|
663
|
+
readonly status: 404;
|
|
664
|
+
readonly message: "Block not found in snapshot";
|
|
665
|
+
};
|
|
666
|
+
readonly PARENT_NOT_FOUND: {
|
|
667
|
+
readonly status: 404;
|
|
668
|
+
readonly message: "Parent block not found";
|
|
669
|
+
};
|
|
670
|
+
readonly ROOT_NOT_FOUND: {
|
|
671
|
+
readonly status: 404;
|
|
672
|
+
readonly message: "Root block not found in snapshot";
|
|
673
|
+
};
|
|
674
|
+
readonly ROOT_HAS_CHILDREN: {
|
|
675
|
+
readonly status: 400;
|
|
676
|
+
readonly message: "Cannot delete a page that has child pages; archive or move the children first";
|
|
677
|
+
};
|
|
678
|
+
readonly ROOT_IN_USE: {
|
|
679
|
+
readonly status: 409;
|
|
680
|
+
readonly message: "Cannot delete: this root is embedded as a reusable block on live pages; remove those references first";
|
|
681
|
+
};
|
|
682
|
+
readonly COMMIT_NOT_FOUND: {
|
|
683
|
+
readonly status: 404;
|
|
684
|
+
readonly message: "Commit not found";
|
|
685
|
+
};
|
|
686
|
+
readonly FOLDER_NOT_FOUND: {
|
|
687
|
+
readonly status: 404;
|
|
688
|
+
readonly message: "Folder not found";
|
|
689
|
+
};
|
|
690
|
+
readonly FOLDER_HAS_CONTENT: {
|
|
691
|
+
readonly status: 400;
|
|
692
|
+
readonly message: "Cannot delete folder that contains assets or subfolders";
|
|
693
|
+
};
|
|
694
|
+
readonly EMPTY_SNAPSHOT: {
|
|
695
|
+
readonly status: 400;
|
|
696
|
+
readonly message: "Empty snapshot — no versions found";
|
|
697
|
+
};
|
|
698
|
+
readonly BLOCK_ALREADY_DELETED: {
|
|
699
|
+
readonly status: 400;
|
|
700
|
+
readonly message: "Block is already deleted";
|
|
701
|
+
};
|
|
702
|
+
readonly TYPE_MISMATCH: {
|
|
703
|
+
readonly status: 400;
|
|
704
|
+
readonly message: "Block type does not match the expected type";
|
|
705
|
+
};
|
|
706
|
+
readonly USER_ID_REQUIRED: {
|
|
707
|
+
readonly status: 400;
|
|
708
|
+
readonly message: "userId is required for this route when neither the request nor middleware provides one";
|
|
709
|
+
};
|
|
710
|
+
readonly CANNOT_MOVE_ROOT: {
|
|
711
|
+
readonly status: 400;
|
|
712
|
+
readonly message: "Cannot move the root block";
|
|
713
|
+
};
|
|
714
|
+
readonly CANNOT_MOVE_INTO_SELF: {
|
|
715
|
+
readonly status: 400;
|
|
716
|
+
readonly message: "Cannot move an item into itself";
|
|
717
|
+
};
|
|
718
|
+
readonly CANNOT_MOVE_INTO_DESCENDANT: {
|
|
719
|
+
readonly status: 400;
|
|
720
|
+
readonly message: "Cannot move an item into its own descendant";
|
|
721
|
+
};
|
|
722
|
+
readonly MISSING_TARGET_PROPERTIES: {
|
|
723
|
+
readonly status: 400;
|
|
724
|
+
readonly message: "targetProperties is required when duplicating a root";
|
|
725
|
+
};
|
|
726
|
+
readonly BRANCH_NAME_ALREADY_EXISTS: {
|
|
727
|
+
readonly status: 400;
|
|
728
|
+
readonly message: "A branch with this name already exists for this root";
|
|
729
|
+
};
|
|
730
|
+
readonly CANNOT_RENAME_MAIN_BRANCH: {
|
|
731
|
+
readonly status: 400;
|
|
732
|
+
readonly message: "The main branch cannot be renamed";
|
|
733
|
+
};
|
|
734
|
+
readonly CANNOT_DELETE_MAIN_BRANCH: {
|
|
735
|
+
readonly status: 400;
|
|
736
|
+
readonly message: "The main branch cannot be deleted";
|
|
737
|
+
};
|
|
738
|
+
readonly BRANCH_HAS_PUBLICATIONS: {
|
|
739
|
+
readonly status: 400;
|
|
740
|
+
readonly message: "Cannot delete a branch that has active publications";
|
|
741
|
+
};
|
|
742
|
+
readonly BRANCH_HAS_OPEN_MERGE_REQUESTS: {
|
|
743
|
+
readonly status: 400;
|
|
744
|
+
readonly message: "Cannot delete a branch that is part of open merge requests";
|
|
745
|
+
};
|
|
746
|
+
readonly NO_COMMON_ANCESTOR: {
|
|
747
|
+
readonly status: 400;
|
|
748
|
+
readonly message: "The two branches share no common ancestor";
|
|
749
|
+
};
|
|
750
|
+
readonly MERGE_REQUEST_NOT_FOUND: {
|
|
751
|
+
readonly status: 404;
|
|
752
|
+
readonly message: "Merge request not found";
|
|
753
|
+
};
|
|
754
|
+
readonly MERGE_REQUEST_NOT_OPEN: {
|
|
755
|
+
readonly status: 400;
|
|
756
|
+
readonly message: "Merge request is not open";
|
|
757
|
+
};
|
|
758
|
+
readonly MERGE_REQUEST_NOT_CLOSED: {
|
|
759
|
+
readonly status: 400;
|
|
760
|
+
readonly message: "Merge request is not closed";
|
|
761
|
+
};
|
|
762
|
+
readonly MERGE_REQUEST_ALREADY_MERGED: {
|
|
763
|
+
readonly status: 400;
|
|
764
|
+
readonly message: "Merge request has already been merged and cannot be reopened";
|
|
765
|
+
};
|
|
766
|
+
readonly MERGE_REQUEST_ALREADY_EXISTS: {
|
|
767
|
+
readonly status: 400;
|
|
768
|
+
readonly message: "An open merge request already exists for this source and target branch";
|
|
769
|
+
};
|
|
770
|
+
readonly MERGE_REQUEST_OUTDATED: {
|
|
771
|
+
readonly status: 400;
|
|
772
|
+
readonly message: "Merge request is outdated because the source branch changed after it was opened";
|
|
773
|
+
};
|
|
774
|
+
readonly UNRESOLVED_CONFLICTS: {
|
|
775
|
+
readonly status: 400;
|
|
776
|
+
readonly message: "Cannot merge: there are unresolved conflicts";
|
|
777
|
+
};
|
|
778
|
+
readonly CONFLICT_NOT_FOUND: {
|
|
779
|
+
readonly status: 404;
|
|
780
|
+
readonly message: "Merge conflict not found";
|
|
781
|
+
};
|
|
782
|
+
readonly RESOLVED_VERSION_NOT_FOUND: {
|
|
783
|
+
readonly status: 404;
|
|
784
|
+
readonly message: "The provided resolvedVersionId does not reference an existing block version";
|
|
785
|
+
};
|
|
786
|
+
readonly APPROVAL_NOT_FOUND: {
|
|
787
|
+
readonly status: 404;
|
|
788
|
+
readonly message: "Approval not found";
|
|
789
|
+
};
|
|
790
|
+
readonly APPROVAL_ALREADY_REQUESTED: {
|
|
791
|
+
readonly status: 400;
|
|
792
|
+
readonly message: "An approval has already been requested from this reviewer";
|
|
793
|
+
};
|
|
794
|
+
readonly APPROVAL_NOT_PENDING: {
|
|
795
|
+
readonly status: 400;
|
|
796
|
+
readonly message: "Approval is not pending";
|
|
797
|
+
};
|
|
798
|
+
readonly APPROVAL_REVIEWER_MISMATCH: {
|
|
799
|
+
readonly status: 403;
|
|
800
|
+
readonly message: "Only the requested reviewer can approve or reject this request";
|
|
801
|
+
};
|
|
802
|
+
readonly APPROVAL_STALE: {
|
|
803
|
+
readonly status: 400;
|
|
804
|
+
readonly message: "Approval is stale: the branch has advanced past the approved commit";
|
|
805
|
+
};
|
|
806
|
+
readonly MERGE_APPROVAL_REQUIRED: {
|
|
807
|
+
readonly status: 400;
|
|
808
|
+
readonly message: "Cannot merge: approval is required before execution";
|
|
809
|
+
};
|
|
810
|
+
readonly PUBLICATION_APPROVAL_REQUIRED: {
|
|
811
|
+
readonly status: 400;
|
|
812
|
+
readonly message: "Cannot publish: approval is required before publication";
|
|
813
|
+
};
|
|
814
|
+
readonly APPROVALS_NOT_FULLY_APPROVED: {
|
|
815
|
+
readonly status: 400;
|
|
816
|
+
readonly message: "Cannot proceed: not all requested approvals are approved";
|
|
817
|
+
};
|
|
818
|
+
readonly BRANCHES_NOT_SAME_ROOT: {
|
|
819
|
+
readonly status: 400;
|
|
820
|
+
readonly message: "Source and target branches must belong to the same root";
|
|
821
|
+
};
|
|
822
|
+
readonly PUBLICATION_NOT_FOUND: {
|
|
823
|
+
readonly status: 404;
|
|
824
|
+
readonly message: "Publication not found for this branch";
|
|
825
|
+
};
|
|
826
|
+
readonly PUBLISHED_CONTENT_NOT_FOUND: {
|
|
827
|
+
readonly status: 404;
|
|
828
|
+
readonly message: "No published content found";
|
|
829
|
+
};
|
|
830
|
+
readonly AMBIGUOUS_SLUG: {
|
|
831
|
+
readonly status: 400;
|
|
832
|
+
readonly message: "Multiple roots match this slug — use rootId for an unambiguous lookup";
|
|
833
|
+
};
|
|
834
|
+
readonly DATA_RETENTION_NOT_CONFIGURED: {
|
|
835
|
+
readonly status: 400;
|
|
836
|
+
readonly message: "dataRetention is not configured for this CMS instance";
|
|
837
|
+
};
|
|
838
|
+
readonly MISSING_REQUIRED_S3_PARAMETERS: {
|
|
839
|
+
readonly status: 400;
|
|
840
|
+
readonly message: "Missing required S3 parameters: hostname, accessKeyId, or secretAccessKey";
|
|
841
|
+
};
|
|
842
|
+
readonly UNKNOWN_S3_PROVIDER: {
|
|
843
|
+
readonly status: 400;
|
|
844
|
+
readonly message: "Unknown S3 provider specified";
|
|
845
|
+
};
|
|
846
|
+
readonly SLUG_GENERATION_FAILED: {
|
|
847
|
+
readonly status: 500;
|
|
848
|
+
readonly message: "Failed to generate a unique slug after maximum attempts";
|
|
849
|
+
};
|
|
850
|
+
readonly TOO_MANY_FILES: {
|
|
851
|
+
readonly status: 400;
|
|
852
|
+
readonly message: "Too many files in upload batch";
|
|
853
|
+
};
|
|
854
|
+
readonly FILE_TOO_LARGE: {
|
|
855
|
+
readonly status: 400;
|
|
856
|
+
readonly message: "One or more files exceed the maximum allowed size";
|
|
857
|
+
};
|
|
858
|
+
readonly INVALID_FILE_TYPE: {
|
|
859
|
+
readonly status: 400;
|
|
860
|
+
readonly message: "One or more files have a disallowed MIME type";
|
|
861
|
+
};
|
|
862
|
+
readonly UPLOAD_FAILED: {
|
|
863
|
+
readonly status: 500;
|
|
864
|
+
readonly message: "Server-side upload to S3 failed";
|
|
865
|
+
};
|
|
866
|
+
readonly SLUG_ALREADY_EXISTS: {
|
|
867
|
+
readonly status: 409;
|
|
868
|
+
readonly message: "A root with this slug on this collection with this parentRootId already exists";
|
|
869
|
+
};
|
|
870
|
+
readonly SLUG_NOT_ENABLED: {
|
|
871
|
+
readonly status: 400;
|
|
872
|
+
readonly message: "This collection does not have slugs enabled";
|
|
873
|
+
};
|
|
874
|
+
readonly REDIRECT_NOT_FOUND: {
|
|
875
|
+
readonly status: 404;
|
|
876
|
+
readonly message: "Redirect not found";
|
|
877
|
+
};
|
|
878
|
+
readonly REDIRECT_INVALID: {
|
|
879
|
+
readonly status: 400;
|
|
880
|
+
readonly message: "A redirect endpoint must be a page (rootId) or a path, matching its type";
|
|
881
|
+
};
|
|
882
|
+
readonly REDIRECT_SOURCE_EXISTS: {
|
|
883
|
+
readonly status: 409;
|
|
884
|
+
readonly message: "An active redirect already exists for this source";
|
|
885
|
+
};
|
|
886
|
+
readonly SLUG_EMPTY_NOT_ALLOWED: {
|
|
887
|
+
readonly status: 400;
|
|
888
|
+
readonly message: "Empty slug is not allowed for this collection (allowRoot is false)";
|
|
889
|
+
};
|
|
890
|
+
readonly NESTING_NOT_ENABLED: {
|
|
891
|
+
readonly status: 400;
|
|
892
|
+
readonly message: "parentRootId is not allowed — this collection does not have nested pages enabled";
|
|
893
|
+
};
|
|
894
|
+
readonly CIRCULAR_REFERENCE: {
|
|
895
|
+
readonly status: 400;
|
|
896
|
+
readonly message: "Cannot move a page under itself or one of its descendants";
|
|
897
|
+
};
|
|
898
|
+
readonly PARENT_ROOT_NOT_FOUND: {
|
|
899
|
+
readonly status: 404;
|
|
900
|
+
readonly message: "Parent root not found in this collection";
|
|
901
|
+
};
|
|
902
|
+
readonly REFERENCE_DEPTH_EXCEEDED: {
|
|
903
|
+
readonly status: 422;
|
|
904
|
+
readonly message: "Reference nesting is too deep (a reusable block embeds others past the limit)";
|
|
905
|
+
};
|
|
906
|
+
readonly ASSET_NOT_FOUND: {
|
|
907
|
+
readonly status: 404;
|
|
908
|
+
readonly message: "Asset not found";
|
|
909
|
+
};
|
|
910
|
+
readonly VARIABLE_NOT_FOUND: {
|
|
911
|
+
readonly status: 404;
|
|
912
|
+
readonly message: "Variable not found";
|
|
913
|
+
};
|
|
914
|
+
readonly VARIABLE_KEY_EXISTS: {
|
|
915
|
+
readonly status: 409;
|
|
916
|
+
readonly message: "A variable with this key already exists";
|
|
917
|
+
};
|
|
918
|
+
readonly VARIABLE_IN_USE: {
|
|
919
|
+
readonly status: 409;
|
|
920
|
+
readonly message: "Cannot delete variable: it is still in use";
|
|
921
|
+
};
|
|
922
|
+
readonly TEMPLATE_NOT_FOUND: {
|
|
923
|
+
readonly status: 404;
|
|
924
|
+
readonly message: "Template not found";
|
|
925
|
+
};
|
|
926
|
+
readonly TEMPLATE_KEY_EXISTS: {
|
|
927
|
+
readonly status: 409;
|
|
928
|
+
readonly message: "A template for this collection/block/property combination already exists";
|
|
929
|
+
};
|
|
930
|
+
readonly ASSET_ACCESS_DENIED: {
|
|
931
|
+
readonly status: 403;
|
|
932
|
+
readonly message: "This asset is private and requires authentication";
|
|
933
|
+
};
|
|
934
|
+
readonly COMMENT_THREAD_NOT_FOUND: {
|
|
935
|
+
readonly status: 404;
|
|
936
|
+
readonly message: "Comment thread not found";
|
|
937
|
+
};
|
|
938
|
+
readonly COMMENT_THREAD_ALREADY_RESOLVED: {
|
|
939
|
+
readonly status: 400;
|
|
940
|
+
readonly message: "Comment thread is already resolved";
|
|
941
|
+
};
|
|
942
|
+
readonly COMMENT_THREAD_NOT_RESOLVED: {
|
|
943
|
+
readonly status: 400;
|
|
944
|
+
readonly message: "Comment thread is not resolved";
|
|
945
|
+
};
|
|
946
|
+
readonly COMMENT_MESSAGE_NOT_FOUND: {
|
|
947
|
+
readonly status: 404;
|
|
948
|
+
readonly message: "Comment message not found";
|
|
949
|
+
};
|
|
950
|
+
readonly COMMENT_MESSAGE_DELETED: {
|
|
951
|
+
readonly status: 400;
|
|
952
|
+
readonly message: "Comment message has been deleted";
|
|
953
|
+
};
|
|
954
|
+
readonly COMMENT_BODY_REQUIRED: {
|
|
955
|
+
readonly status: 400;
|
|
956
|
+
readonly message: "Body is required for comment messages";
|
|
957
|
+
};
|
|
958
|
+
readonly COMMENT_AUTHOR_MISMATCH: {
|
|
959
|
+
readonly status: 403;
|
|
960
|
+
readonly message: "Only the author can edit or delete this message";
|
|
961
|
+
};
|
|
962
|
+
readonly NOTIFICATION_NOT_FOUND: {
|
|
963
|
+
readonly status: 404;
|
|
964
|
+
readonly message: "Notification not found";
|
|
965
|
+
};
|
|
966
|
+
readonly NOTIFICATION_RECIPIENT_MISMATCH: {
|
|
967
|
+
readonly status: 403;
|
|
968
|
+
readonly message: "You can only access your own notifications";
|
|
969
|
+
};
|
|
970
|
+
};
|
|
971
|
+
type CMSErrorCode = keyof typeof CMS_ERRORS;
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Client-side CMS error thrown by `$fetch` when the server returns an error
|
|
975
|
+
* response. Unlike the server-side `CMSError` (which extends better-call's
|
|
976
|
+
* `APIError`), this is a plain `Error` subclass that works in the browser.
|
|
977
|
+
*/
|
|
978
|
+
declare class CMSClientError extends Error {
|
|
979
|
+
readonly status: number;
|
|
980
|
+
readonly statusText: string;
|
|
981
|
+
readonly code: string | undefined;
|
|
982
|
+
constructor(errorBody: {
|
|
983
|
+
message?: string;
|
|
984
|
+
code?: string;
|
|
985
|
+
status?: number;
|
|
986
|
+
statusText?: string;
|
|
987
|
+
});
|
|
988
|
+
get cmsCode(): CMSErrorCode | undefined;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
export { CMSClientError, createCMSClient, createCMSQuery, useStore };
|
|
992
|
+
export type { CMSAtomListener, CMSClientInstance, CMSClientOptions, CMSClientPlugin, CMSClientStore, CMSFetch, EventDeclaration, QueryState };
|