@createcms/core 0.1.1 → 0.2.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.
@@ -229,18 +229,70 @@ type BlockDefinition<TProps extends Record<string, BlockProperty> = Record<strin
229
229
  label: string;
230
230
  description?: string;
231
231
  previewImageUrl?: string;
232
+ /**
233
+ * Editor hint: the block-picker category this block is shown under (e.g.
234
+ * `'Forms'`, `'Layout'`). Purely presentational — the editor groups blocks by
235
+ * this label; the package never acts on it. Free-form by design; for
236
+ * consistent, autocompleted group names across blocks, reference a shared
237
+ * `as const` object (e.g. `group: BLOCK_GROUPS.forms`).
238
+ */
239
+ group?: string;
232
240
  /** Events this (functional) block can emit — see {@link EventDeclaration}. */
233
241
  events?: TEvents;
234
242
  } & ({
235
243
  allowChildren?: false;
236
244
  } | {
237
245
  allowChildren: true;
238
- allowedChildBlocks?: string[];
239
246
  });
240
247
  type AnyBlockDefinition = BlockDefinition<Record<string, BlockProperty>, Record<string, EventDeclaration>>;
241
248
  type RootDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>> = {
242
249
  properties: TProps;
243
250
  };
251
+ /**
252
+ * One PARENT's placement rule inside a collection's {@link CollectionStructure}
253
+ * — declares which child block types that parent (or the literal `'root'`) may
254
+ * contain. There are three mutually-exclusive modes, enforced by the type:
255
+ *
256
+ * - **open** — `{}` or `{ accepts: '*' }`: holds any block. (Same as having no
257
+ * entry at all; `'*'` is just an explicit, readable form.)
258
+ * - **whitelist** — `{ accepts: ['a', 'b'] }`: holds ONLY `a`/`b`. Fail-closed —
259
+ * a block added to the collection later is rejected until listed. `excludes`
260
+ * is forbidden here (a concrete `accepts` already says exactly what's allowed).
261
+ * - **blacklist** — `{ excludes: ['z'] }` (or `{ accepts: '*', excludes: ['z'] }`):
262
+ * holds anything EXCEPT `z`. Fail-open — a block added later is accepted.
263
+ *
264
+ * Whether a parent accepts children AT ALL is the separate, coarser
265
+ * `allowChildren` gate on the block (the root always accepts children); these
266
+ * rules only refine WHICH children an accepting parent may hold.
267
+ */
268
+ type BlockStructureEntry<TBlockName extends string> = {
269
+ /** `'*'` = open base (optional, for readability). */
270
+ accepts?: '*';
271
+ /** Holds anything except these. */
272
+ excludes?: readonly TBlockName[];
273
+ } | {
274
+ /** Holds ONLY these block types. */
275
+ accepts: readonly TBlockName[];
276
+ /**
277
+ * Forbidden alongside a concrete `accepts` list — the list already names
278
+ * exactly what is allowed, so `excludes` would be ignored.
279
+ */
280
+ excludes?: "Remove 'excludes': a concrete 'accepts' list already defines exactly which blocks are allowed. Use accepts: '*' with excludes for an all-except list.";
281
+ };
282
+ /**
283
+ * Placement rules for a collection, keyed by PARENT block name (or the literal
284
+ * `'root'` for the top level). Open by default: a parent with no entry holds any
285
+ * block. The keys and the `accepts` / `excludes` block names autocomplete against
286
+ * the collection's block names and are checked at compile time by
287
+ * {@link defineCollection} (the field type alone enforces this — no extra step).
288
+ *
289
+ * This is the single source of truth that the visual editor (drop-zone gating)
290
+ * and the server guard (createBlock / moveBlock / duplicateBlock) both read,
291
+ * alongside each block's `allowChildren` flag, so they can never diverge.
292
+ */
293
+ type CollectionStructure<TBlocks extends Record<string, AnyBlockDefinition>> = {
294
+ [K in keyof TBlocks | 'root']?: BlockStructureEntry<keyof TBlocks & string>;
295
+ };
244
296
  type SlugConfig = {
245
297
  enabled: false;
246
298
  } | {
@@ -264,6 +316,15 @@ type CollectionDefinition<TProps extends Record<string, BlockProperty> = Record<
264
316
  * regardless of this flag). Any collection can still be a reference target.
265
317
  */
266
318
  reusableBlock?: boolean;
319
+ /**
320
+ * Placement rules keyed by PARENT block name (or `'root'`) — which children
321
+ * each container may hold, via `accepts` (whitelist) / `excludes` (blacklist)
322
+ * (see {@link CollectionStructure}). Read by the editor and the server guard
323
+ * together with each block's `allowChildren` flag. Open by default; block
324
+ * names are checked at compile time by the field type itself, so a typo is a
325
+ * compile error at the `defineCollection` call site.
326
+ */
327
+ structure?: CollectionStructure<TBlocks>;
267
328
  };
268
329
  type AnyCollectionDefinition = CollectionDefinition<Record<string, BlockProperty>, Record<string, AnyBlockDefinition>>;
269
330
  type CollectionWithName = Omit<AnyCollectionDefinition, 'blocks'> & {
@@ -667,6 +728,10 @@ declare const CMS_ERRORS: {
667
728
  readonly status: 404;
668
729
  readonly message: "Parent block not found";
669
730
  };
731
+ readonly BLOCK_NOT_ALLOWED_IN_PARENT: {
732
+ readonly status: 400;
733
+ readonly message: "This block type is not allowed inside the target parent";
734
+ };
670
735
  readonly ROOT_NOT_FOUND: {
671
736
  readonly status: 404;
672
737
  readonly message: "Root block not found in snapshot";
@@ -229,18 +229,70 @@ type BlockDefinition<TProps extends Record<string, BlockProperty> = Record<strin
229
229
  label: string;
230
230
  description?: string;
231
231
  previewImageUrl?: string;
232
+ /**
233
+ * Editor hint: the block-picker category this block is shown under (e.g.
234
+ * `'Forms'`, `'Layout'`). Purely presentational — the editor groups blocks by
235
+ * this label; the package never acts on it. Free-form by design; for
236
+ * consistent, autocompleted group names across blocks, reference a shared
237
+ * `as const` object (e.g. `group: BLOCK_GROUPS.forms`).
238
+ */
239
+ group?: string;
232
240
  /** Events this (functional) block can emit — see {@link EventDeclaration}. */
233
241
  events?: TEvents;
234
242
  } & ({
235
243
  allowChildren?: false;
236
244
  } | {
237
245
  allowChildren: true;
238
- allowedChildBlocks?: string[];
239
246
  });
240
247
  type AnyBlockDefinition = BlockDefinition<Record<string, BlockProperty>, Record<string, EventDeclaration>>;
241
248
  type RootDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>> = {
242
249
  properties: TProps;
243
250
  };
251
+ /**
252
+ * One PARENT's placement rule inside a collection's {@link CollectionStructure}
253
+ * — declares which child block types that parent (or the literal `'root'`) may
254
+ * contain. There are three mutually-exclusive modes, enforced by the type:
255
+ *
256
+ * - **open** — `{}` or `{ accepts: '*' }`: holds any block. (Same as having no
257
+ * entry at all; `'*'` is just an explicit, readable form.)
258
+ * - **whitelist** — `{ accepts: ['a', 'b'] }`: holds ONLY `a`/`b`. Fail-closed —
259
+ * a block added to the collection later is rejected until listed. `excludes`
260
+ * is forbidden here (a concrete `accepts` already says exactly what's allowed).
261
+ * - **blacklist** — `{ excludes: ['z'] }` (or `{ accepts: '*', excludes: ['z'] }`):
262
+ * holds anything EXCEPT `z`. Fail-open — a block added later is accepted.
263
+ *
264
+ * Whether a parent accepts children AT ALL is the separate, coarser
265
+ * `allowChildren` gate on the block (the root always accepts children); these
266
+ * rules only refine WHICH children an accepting parent may hold.
267
+ */
268
+ type BlockStructureEntry<TBlockName extends string> = {
269
+ /** `'*'` = open base (optional, for readability). */
270
+ accepts?: '*';
271
+ /** Holds anything except these. */
272
+ excludes?: readonly TBlockName[];
273
+ } | {
274
+ /** Holds ONLY these block types. */
275
+ accepts: readonly TBlockName[];
276
+ /**
277
+ * Forbidden alongside a concrete `accepts` list — the list already names
278
+ * exactly what is allowed, so `excludes` would be ignored.
279
+ */
280
+ excludes?: "Remove 'excludes': a concrete 'accepts' list already defines exactly which blocks are allowed. Use accepts: '*' with excludes for an all-except list.";
281
+ };
282
+ /**
283
+ * Placement rules for a collection, keyed by PARENT block name (or the literal
284
+ * `'root'` for the top level). Open by default: a parent with no entry holds any
285
+ * block. The keys and the `accepts` / `excludes` block names autocomplete against
286
+ * the collection's block names and are checked at compile time by
287
+ * {@link defineCollection} (the field type alone enforces this — no extra step).
288
+ *
289
+ * This is the single source of truth that the visual editor (drop-zone gating)
290
+ * and the server guard (createBlock / moveBlock / duplicateBlock) both read,
291
+ * alongside each block's `allowChildren` flag, so they can never diverge.
292
+ */
293
+ type CollectionStructure<TBlocks extends Record<string, AnyBlockDefinition>> = {
294
+ [K in keyof TBlocks | 'root']?: BlockStructureEntry<keyof TBlocks & string>;
295
+ };
244
296
  type SlugConfig = {
245
297
  enabled: false;
246
298
  } | {
@@ -264,6 +316,15 @@ type CollectionDefinition<TProps extends Record<string, BlockProperty> = Record<
264
316
  * regardless of this flag). Any collection can still be a reference target.
265
317
  */
266
318
  reusableBlock?: boolean;
319
+ /**
320
+ * Placement rules keyed by PARENT block name (or `'root'`) — which children
321
+ * each container may hold, via `accepts` (whitelist) / `excludes` (blacklist)
322
+ * (see {@link CollectionStructure}). Read by the editor and the server guard
323
+ * together with each block's `allowChildren` flag. Open by default; block
324
+ * names are checked at compile time by the field type itself, so a typo is a
325
+ * compile error at the `defineCollection` call site.
326
+ */
327
+ structure?: CollectionStructure<TBlocks>;
267
328
  };
268
329
  type AnyCollectionDefinition = CollectionDefinition<Record<string, BlockProperty>, Record<string, AnyBlockDefinition>>;
269
330
  type CollectionWithName = Omit<AnyCollectionDefinition, 'blocks'> & {
@@ -667,6 +728,10 @@ declare const CMS_ERRORS: {
667
728
  readonly status: 404;
668
729
  readonly message: "Parent block not found";
669
730
  };
731
+ readonly BLOCK_NOT_ALLOWED_IN_PARENT: {
732
+ readonly status: 400;
733
+ readonly message: "This block type is not allowed inside the target parent";
734
+ };
670
735
  readonly ROOT_NOT_FOUND: {
671
736
  readonly status: 404;
672
737
  readonly message: "Root block not found in snapshot";
@@ -148,6 +148,10 @@ const CMS_ERRORS = {
148
148
  status: 404,
149
149
  message: 'Parent block not found'
150
150
  },
151
+ BLOCK_NOT_ALLOWED_IN_PARENT: {
152
+ status: 400,
153
+ message: 'This block type is not allowed inside the target parent'
154
+ },
151
155
  ROOT_NOT_FOUND: {
152
156
  status: 404,
153
157
  message: 'Root block not found in snapshot'
@@ -147,18 +147,70 @@ type BlockDefinition<TProps extends Record<string, BlockProperty> = Record<strin
147
147
  label: string;
148
148
  description?: string;
149
149
  previewImageUrl?: string;
150
+ /**
151
+ * Editor hint: the block-picker category this block is shown under (e.g.
152
+ * `'Forms'`, `'Layout'`). Purely presentational — the editor groups blocks by
153
+ * this label; the package never acts on it. Free-form by design; for
154
+ * consistent, autocompleted group names across blocks, reference a shared
155
+ * `as const` object (e.g. `group: BLOCK_GROUPS.forms`).
156
+ */
157
+ group?: string;
150
158
  /** Events this (functional) block can emit — see {@link EventDeclaration}. */
151
159
  events?: TEvents;
152
160
  } & ({
153
161
  allowChildren?: false;
154
162
  } | {
155
163
  allowChildren: true;
156
- allowedChildBlocks?: string[];
157
164
  });
158
165
  type AnyBlockDefinition = BlockDefinition<Record<string, BlockProperty>, Record<string, EventDeclaration>>;
159
166
  type RootDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>> = {
160
167
  properties: TProps;
161
168
  };
169
+ /**
170
+ * One PARENT's placement rule inside a collection's {@link CollectionStructure}
171
+ * — declares which child block types that parent (or the literal `'root'`) may
172
+ * contain. There are three mutually-exclusive modes, enforced by the type:
173
+ *
174
+ * - **open** — `{}` or `{ accepts: '*' }`: holds any block. (Same as having no
175
+ * entry at all; `'*'` is just an explicit, readable form.)
176
+ * - **whitelist** — `{ accepts: ['a', 'b'] }`: holds ONLY `a`/`b`. Fail-closed —
177
+ * a block added to the collection later is rejected until listed. `excludes`
178
+ * is forbidden here (a concrete `accepts` already says exactly what's allowed).
179
+ * - **blacklist** — `{ excludes: ['z'] }` (or `{ accepts: '*', excludes: ['z'] }`):
180
+ * holds anything EXCEPT `z`. Fail-open — a block added later is accepted.
181
+ *
182
+ * Whether a parent accepts children AT ALL is the separate, coarser
183
+ * `allowChildren` gate on the block (the root always accepts children); these
184
+ * rules only refine WHICH children an accepting parent may hold.
185
+ */
186
+ type BlockStructureEntry<TBlockName extends string> = {
187
+ /** `'*'` = open base (optional, for readability). */
188
+ accepts?: '*';
189
+ /** Holds anything except these. */
190
+ excludes?: readonly TBlockName[];
191
+ } | {
192
+ /** Holds ONLY these block types. */
193
+ accepts: readonly TBlockName[];
194
+ /**
195
+ * Forbidden alongside a concrete `accepts` list — the list already names
196
+ * exactly what is allowed, so `excludes` would be ignored.
197
+ */
198
+ excludes?: "Remove 'excludes': a concrete 'accepts' list already defines exactly which blocks are allowed. Use accepts: '*' with excludes for an all-except list.";
199
+ };
200
+ /**
201
+ * Placement rules for a collection, keyed by PARENT block name (or the literal
202
+ * `'root'` for the top level). Open by default: a parent with no entry holds any
203
+ * block. The keys and the `accepts` / `excludes` block names autocomplete against
204
+ * the collection's block names and are checked at compile time by
205
+ * {@link defineCollection} (the field type alone enforces this — no extra step).
206
+ *
207
+ * This is the single source of truth that the visual editor (drop-zone gating)
208
+ * and the server guard (createBlock / moveBlock / duplicateBlock) both read,
209
+ * alongside each block's `allowChildren` flag, so they can never diverge.
210
+ */
211
+ type CollectionStructure<TBlocks extends Record<string, AnyBlockDefinition>> = {
212
+ [K in keyof TBlocks | 'root']?: BlockStructureEntry<keyof TBlocks & string>;
213
+ };
162
214
  type SlugConfig = {
163
215
  enabled: false;
164
216
  } | {
@@ -182,6 +234,15 @@ type CollectionDefinition<TProps extends Record<string, BlockProperty> = Record<
182
234
  * regardless of this flag). Any collection can still be a reference target.
183
235
  */
184
236
  reusableBlock?: boolean;
237
+ /**
238
+ * Placement rules keyed by PARENT block name (or `'root'`) — which children
239
+ * each container may hold, via `accepts` (whitelist) / `excludes` (blacklist)
240
+ * (see {@link CollectionStructure}). Read by the editor and the server guard
241
+ * together with each block's `allowChildren` flag. Open by default; block
242
+ * names are checked at compile time by the field type itself, so a typo is a
243
+ * compile error at the `defineCollection` call site.
244
+ */
245
+ structure?: CollectionStructure<TBlocks>;
185
246
  };
186
247
  type AnyCollectionDefinition = CollectionDefinition<Record<string, BlockProperty>, Record<string, AnyBlockDefinition>>;
187
248
 
@@ -147,18 +147,70 @@ type BlockDefinition<TProps extends Record<string, BlockProperty> = Record<strin
147
147
  label: string;
148
148
  description?: string;
149
149
  previewImageUrl?: string;
150
+ /**
151
+ * Editor hint: the block-picker category this block is shown under (e.g.
152
+ * `'Forms'`, `'Layout'`). Purely presentational — the editor groups blocks by
153
+ * this label; the package never acts on it. Free-form by design; for
154
+ * consistent, autocompleted group names across blocks, reference a shared
155
+ * `as const` object (e.g. `group: BLOCK_GROUPS.forms`).
156
+ */
157
+ group?: string;
150
158
  /** Events this (functional) block can emit — see {@link EventDeclaration}. */
151
159
  events?: TEvents;
152
160
  } & ({
153
161
  allowChildren?: false;
154
162
  } | {
155
163
  allowChildren: true;
156
- allowedChildBlocks?: string[];
157
164
  });
158
165
  type AnyBlockDefinition = BlockDefinition<Record<string, BlockProperty>, Record<string, EventDeclaration>>;
159
166
  type RootDefinition<TProps extends Record<string, BlockProperty> = Record<string, BlockProperty>> = {
160
167
  properties: TProps;
161
168
  };
169
+ /**
170
+ * One PARENT's placement rule inside a collection's {@link CollectionStructure}
171
+ * — declares which child block types that parent (or the literal `'root'`) may
172
+ * contain. There are three mutually-exclusive modes, enforced by the type:
173
+ *
174
+ * - **open** — `{}` or `{ accepts: '*' }`: holds any block. (Same as having no
175
+ * entry at all; `'*'` is just an explicit, readable form.)
176
+ * - **whitelist** — `{ accepts: ['a', 'b'] }`: holds ONLY `a`/`b`. Fail-closed —
177
+ * a block added to the collection later is rejected until listed. `excludes`
178
+ * is forbidden here (a concrete `accepts` already says exactly what's allowed).
179
+ * - **blacklist** — `{ excludes: ['z'] }` (or `{ accepts: '*', excludes: ['z'] }`):
180
+ * holds anything EXCEPT `z`. Fail-open — a block added later is accepted.
181
+ *
182
+ * Whether a parent accepts children AT ALL is the separate, coarser
183
+ * `allowChildren` gate on the block (the root always accepts children); these
184
+ * rules only refine WHICH children an accepting parent may hold.
185
+ */
186
+ type BlockStructureEntry<TBlockName extends string> = {
187
+ /** `'*'` = open base (optional, for readability). */
188
+ accepts?: '*';
189
+ /** Holds anything except these. */
190
+ excludes?: readonly TBlockName[];
191
+ } | {
192
+ /** Holds ONLY these block types. */
193
+ accepts: readonly TBlockName[];
194
+ /**
195
+ * Forbidden alongside a concrete `accepts` list — the list already names
196
+ * exactly what is allowed, so `excludes` would be ignored.
197
+ */
198
+ excludes?: "Remove 'excludes': a concrete 'accepts' list already defines exactly which blocks are allowed. Use accepts: '*' with excludes for an all-except list.";
199
+ };
200
+ /**
201
+ * Placement rules for a collection, keyed by PARENT block name (or the literal
202
+ * `'root'` for the top level). Open by default: a parent with no entry holds any
203
+ * block. The keys and the `accepts` / `excludes` block names autocomplete against
204
+ * the collection's block names and are checked at compile time by
205
+ * {@link defineCollection} (the field type alone enforces this — no extra step).
206
+ *
207
+ * This is the single source of truth that the visual editor (drop-zone gating)
208
+ * and the server guard (createBlock / moveBlock / duplicateBlock) both read,
209
+ * alongside each block's `allowChildren` flag, so they can never diverge.
210
+ */
211
+ type CollectionStructure<TBlocks extends Record<string, AnyBlockDefinition>> = {
212
+ [K in keyof TBlocks | 'root']?: BlockStructureEntry<keyof TBlocks & string>;
213
+ };
162
214
  type SlugConfig = {
163
215
  enabled: false;
164
216
  } | {
@@ -182,6 +234,15 @@ type CollectionDefinition<TProps extends Record<string, BlockProperty> = Record<
182
234
  * regardless of this flag). Any collection can still be a reference target.
183
235
  */
184
236
  reusableBlock?: boolean;
237
+ /**
238
+ * Placement rules keyed by PARENT block name (or `'root'`) — which children
239
+ * each container may hold, via `accepts` (whitelist) / `excludes` (blacklist)
240
+ * (see {@link CollectionStructure}). Read by the editor and the server guard
241
+ * together with each block's `allowChildren` flag. Open by default; block
242
+ * names are checked at compile time by the field type itself, so a typo is a
243
+ * compile error at the `defineCollection` call site.
244
+ */
245
+ structure?: CollectionStructure<TBlocks>;
185
246
  };
186
247
  type AnyCollectionDefinition = CollectionDefinition<Record<string, BlockProperty>, Record<string, AnyBlockDefinition>>;
187
248
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@createcms/core",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "[Work in progress · pre-1.0 · not production-ready] Composable, block-based headless CMS powered by better-call and Drizzle ORM (Postgres). Database-native versioning with Git-like branches, copy-on-write drafts, visual diffs, merges, reusable blocks, nested pages, and a fully type-safe API.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/weepaho3/createCMS#readme",