@byline/core 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/README.md +17 -0
- package/dist/@types/admin-types.d.ts +275 -0
- package/dist/@types/admin-types.d.ts.map +1 -0
- package/dist/@types/admin-types.js +18 -0
- package/dist/@types/admin-types.js.map +1 -0
- package/dist/@types/collection-types.d.ts +816 -0
- package/dist/@types/collection-types.d.ts.map +1 -0
- package/dist/@types/collection-types.js +217 -0
- package/dist/@types/collection-types.js.map +1 -0
- package/dist/@types/db-types.d.ts +463 -0
- package/dist/@types/db-types.d.ts.map +1 -0
- package/dist/@types/db-types.js +2 -0
- package/dist/@types/db-types.js.map +1 -0
- package/dist/@types/field-data-types.d.ts +147 -0
- package/dist/@types/field-data-types.d.ts.map +1 -0
- package/dist/@types/field-data-types.js +38 -0
- package/dist/@types/field-data-types.js.map +1 -0
- package/dist/@types/field-types.d.ts +579 -0
- package/dist/@types/field-types.d.ts.map +1 -0
- package/dist/@types/field-types.js +32 -0
- package/dist/@types/field-types.js.map +1 -0
- package/dist/@types/index.d.ts +18 -0
- package/dist/@types/index.d.ts.map +1 -0
- package/dist/@types/index.js +18 -0
- package/dist/@types/index.js.map +1 -0
- package/dist/@types/populate-types.d.ts +54 -0
- package/dist/@types/populate-types.d.ts.map +1 -0
- package/dist/@types/populate-types.js +9 -0
- package/dist/@types/populate-types.js.map +1 -0
- package/dist/@types/query-predicate.d.ts +74 -0
- package/dist/@types/query-predicate.d.ts.map +1 -0
- package/dist/@types/query-predicate.js +9 -0
- package/dist/@types/query-predicate.js.map +1 -0
- package/dist/@types/site-config.d.ts +212 -0
- package/dist/@types/site-config.d.ts.map +1 -0
- package/dist/@types/site-config.js +9 -0
- package/dist/@types/site-config.js.map +1 -0
- package/dist/@types/storage-types.d.ts +86 -0
- package/dist/@types/storage-types.d.ts.map +1 -0
- package/dist/@types/storage-types.js +9 -0
- package/dist/@types/storage-types.js.map +1 -0
- package/dist/@types/store-types.d.ts +134 -0
- package/dist/@types/store-types.d.ts.map +1 -0
- package/dist/@types/store-types.js +24 -0
- package/dist/@types/store-types.js.map +1 -0
- package/dist/@types/type-utils.d.ts +17 -0
- package/dist/@types/type-utils.d.ts.map +1 -0
- package/dist/@types/type-utils.js +9 -0
- package/dist/@types/type-utils.js.map +1 -0
- package/dist/auth/apply-before-read.d.ts +36 -0
- package/dist/auth/apply-before-read.d.ts.map +1 -0
- package/dist/auth/apply-before-read.js +68 -0
- package/dist/auth/apply-before-read.js.map +1 -0
- package/dist/auth/apply-before-read.test.node.d.ts +9 -0
- package/dist/auth/apply-before-read.test.node.d.ts.map +1 -0
- package/dist/auth/apply-before-read.test.node.js +144 -0
- package/dist/auth/apply-before-read.test.node.js.map +1 -0
- package/dist/auth/assert-actor-can-perform.d.ts +39 -0
- package/dist/auth/assert-actor-can-perform.d.ts.map +1 -0
- package/dist/auth/assert-actor-can-perform.js +64 -0
- package/dist/auth/assert-actor-can-perform.js.map +1 -0
- package/dist/auth/assert-actor-can-perform.test.node.d.ts +9 -0
- package/dist/auth/assert-actor-can-perform.test.node.d.ts.map +1 -0
- package/dist/auth/assert-actor-can-perform.test.node.js +119 -0
- package/dist/auth/assert-actor-can-perform.test.node.js.map +1 -0
- package/dist/auth/index.d.ts +11 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +11 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/register-collection-abilities.d.ts +40 -0
- package/dist/auth/register-collection-abilities.d.ts.map +1 -0
- package/dist/auth/register-collection-abilities.js +87 -0
- package/dist/auth/register-collection-abilities.js.map +1 -0
- package/dist/auth/register-collection-abilities.test.node.d.ts +9 -0
- package/dist/auth/register-collection-abilities.test.node.d.ts.map +1 -0
- package/dist/auth/register-collection-abilities.test.node.js +124 -0
- package/dist/auth/register-collection-abilities.test.node.js.map +1 -0
- package/dist/config/config.d.ts +10 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +108 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/routes.d.ts +16 -0
- package/dist/config/routes.d.ts.map +1 -0
- package/dist/config/routes.js +26 -0
- package/dist/config/routes.js.map +1 -0
- package/dist/config/validate-admin-configs.d.ts +33 -0
- package/dist/config/validate-admin-configs.d.ts.map +1 -0
- package/dist/config/validate-admin-configs.js +250 -0
- package/dist/config/validate-admin-configs.js.map +1 -0
- package/dist/config/validate-admin-configs.test.node.d.ts +9 -0
- package/dist/config/validate-admin-configs.test.node.d.ts.map +1 -0
- package/dist/config/validate-admin-configs.test.node.js +224 -0
- package/dist/config/validate-admin-configs.test.node.js.map +1 -0
- package/dist/config/validate-collections.d.ts +33 -0
- package/dist/config/validate-collections.d.ts.map +1 -0
- package/dist/config/validate-collections.js +70 -0
- package/dist/config/validate-collections.js.map +1 -0
- package/dist/config/validate-collections.test.node.d.ts +9 -0
- package/dist/config/validate-collections.test.node.d.ts.map +1 -0
- package/dist/config/validate-collections.test.node.js +149 -0
- package/dist/config/validate-collections.test.node.js.map +1 -0
- package/dist/core.d.ts +89 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +99 -0
- package/dist/core.js.map +1 -0
- package/dist/defaults/default-values.d.ts +13 -0
- package/dist/defaults/default-values.d.ts.map +1 -0
- package/dist/defaults/default-values.js +60 -0
- package/dist/defaults/default-values.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/errors.d.ts +98 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +134 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/logger.d.ts +62 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +120 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/registry.d.ts +65 -0
- package/dist/lib/registry.d.ts.map +1 -0
- package/dist/lib/registry.js +133 -0
- package/dist/lib/registry.js.map +1 -0
- package/dist/logger/index.d.ts +3 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +3 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/patches/apply-patches.d.ts +21 -0
- package/dist/patches/apply-patches.d.ts.map +1 -0
- package/dist/patches/apply-patches.js +357 -0
- package/dist/patches/apply-patches.js.map +1 -0
- package/dist/patches/index.d.ts +3 -0
- package/dist/patches/index.d.ts.map +1 -0
- package/dist/patches/index.js +4 -0
- package/dist/patches/index.js.map +1 -0
- package/dist/patches/patch-types.d.ts +82 -0
- package/dist/patches/patch-types.d.ts.map +1 -0
- package/dist/patches/patch-types.js +3 -0
- package/dist/patches/patch-types.js.map +1 -0
- package/dist/patches/patch.test.node.d.ts +2 -0
- package/dist/patches/patch.test.node.d.ts.map +1 -0
- package/dist/patches/patch.test.node.js +193 -0
- package/dist/patches/patch.test.node.js.map +1 -0
- package/dist/query/parse-where.d.ts +100 -0
- package/dist/query/parse-where.d.ts.map +1 -0
- package/dist/query/parse-where.js +352 -0
- package/dist/query/parse-where.js.map +1 -0
- package/dist/query/parse-where.test.node.d.ts +9 -0
- package/dist/query/parse-where.test.node.d.ts.map +1 -0
- package/dist/query/parse-where.test.node.js +581 -0
- package/dist/query/parse-where.test.node.js.map +1 -0
- package/dist/schemas/zod/builder.d.ts +466 -0
- package/dist/schemas/zod/builder.d.ts.map +1 -0
- package/dist/schemas/zod/builder.js +276 -0
- package/dist/schemas/zod/builder.js.map +1 -0
- package/dist/schemas/zod/cache.d.ts +14 -0
- package/dist/schemas/zod/cache.d.ts.map +1 -0
- package/dist/schemas/zod/cache.js +40 -0
- package/dist/schemas/zod/cache.js.map +1 -0
- package/dist/schemas/zod/index.d.ts +4 -0
- package/dist/schemas/zod/index.d.ts.map +1 -0
- package/dist/schemas/zod/index.js +4 -0
- package/dist/schemas/zod/index.js.map +1 -0
- package/dist/schemas/zod/types.d.ts +13 -0
- package/dist/schemas/zod/types.d.ts.map +1 -0
- package/dist/schemas/zod/types.js +2 -0
- package/dist/schemas/zod/types.js.map +1 -0
- package/dist/services/collection-bootstrap.d.ts +46 -0
- package/dist/services/collection-bootstrap.d.ts.map +1 -0
- package/dist/services/collection-bootstrap.js +108 -0
- package/dist/services/collection-bootstrap.js.map +1 -0
- package/dist/services/collection-bootstrap.test.node.d.ts +9 -0
- package/dist/services/collection-bootstrap.test.node.d.ts.map +1 -0
- package/dist/services/collection-bootstrap.test.node.js +208 -0
- package/dist/services/collection-bootstrap.test.node.js.map +1 -0
- package/dist/services/document-lifecycle.d.ts +245 -0
- package/dist/services/document-lifecycle.d.ts.map +1 -0
- package/dist/services/document-lifecycle.js +481 -0
- package/dist/services/document-lifecycle.js.map +1 -0
- package/dist/services/document-lifecycle.test.node.d.ts +9 -0
- package/dist/services/document-lifecycle.test.node.d.ts.map +1 -0
- package/dist/services/document-lifecycle.test.node.js +781 -0
- package/dist/services/document-lifecycle.test.node.js.map +1 -0
- package/dist/services/document-read.d.ts +26 -0
- package/dist/services/document-read.d.ts.map +1 -0
- package/dist/services/document-read.js +60 -0
- package/dist/services/document-read.js.map +1 -0
- package/dist/services/field-upload.d.ts +100 -0
- package/dist/services/field-upload.d.ts.map +1 -0
- package/dist/services/field-upload.js +328 -0
- package/dist/services/field-upload.js.map +1 -0
- package/dist/services/field-upload.test.node.d.ts +9 -0
- package/dist/services/field-upload.test.node.d.ts.map +1 -0
- package/dist/services/field-upload.test.node.js +337 -0
- package/dist/services/field-upload.test.node.js.map +1 -0
- package/dist/services/index.d.ts +10 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +11 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/populate.d.ts +299 -0
- package/dist/services/populate.d.ts.map +1 -0
- package/dist/services/populate.js +484 -0
- package/dist/services/populate.js.map +1 -0
- package/dist/services/populate.test.node.d.ts +9 -0
- package/dist/services/populate.test.node.d.ts.map +1 -0
- package/dist/services/populate.test.node.js +910 -0
- package/dist/services/populate.test.node.js.map +1 -0
- package/dist/services/relation-projection.d.ts +52 -0
- package/dist/services/relation-projection.d.ts.map +1 -0
- package/dist/services/relation-projection.js +81 -0
- package/dist/services/relation-projection.js.map +1 -0
- package/dist/services/richtext-populate.d.ts +87 -0
- package/dist/services/richtext-populate.d.ts.map +1 -0
- package/dist/services/richtext-populate.js +189 -0
- package/dist/services/richtext-populate.js.map +1 -0
- package/dist/services/richtext-populate.test.node.d.ts +9 -0
- package/dist/services/richtext-populate.test.node.d.ts.map +1 -0
- package/dist/services/richtext-populate.test.node.js +197 -0
- package/dist/services/richtext-populate.test.node.js.map +1 -0
- package/dist/storage/collection-fingerprint.d.ts +21 -0
- package/dist/storage/collection-fingerprint.d.ts.map +1 -0
- package/dist/storage/collection-fingerprint.js +172 -0
- package/dist/storage/collection-fingerprint.js.map +1 -0
- package/dist/storage/collection-fingerprint.test.node.d.ts +9 -0
- package/dist/storage/collection-fingerprint.test.node.d.ts.map +1 -0
- package/dist/storage/collection-fingerprint.test.node.js +256 -0
- package/dist/storage/collection-fingerprint.test.node.js.map +1 -0
- package/dist/storage/field-store-map.d.ts +59 -0
- package/dist/storage/field-store-map.d.ts.map +1 -0
- package/dist/storage/field-store-map.js +75 -0
- package/dist/storage/field-store-map.js.map +1 -0
- package/dist/storage/field-store-map.test.node.d.ts +9 -0
- package/dist/storage/field-store-map.test.node.d.ts.map +1 -0
- package/dist/storage/field-store-map.test.node.js +117 -0
- package/dist/storage/field-store-map.test.node.js.map +1 -0
- package/dist/storage/index.d.ts +10 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +10 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/utils/normalise-dates.d.ts +15 -0
- package/dist/utils/normalise-dates.d.ts.map +1 -0
- package/dist/utils/normalise-dates.js +22 -0
- package/dist/utils/normalise-dates.js.map +1 -0
- package/dist/utils/slugify.d.ts +56 -0
- package/dist/utils/slugify.d.ts.map +1 -0
- package/dist/utils/slugify.js +91 -0
- package/dist/utils/slugify.js.map +1 -0
- package/dist/utils/slugify.test.node.d.ts +9 -0
- package/dist/utils/slugify.test.node.d.ts.map +1 -0
- package/dist/utils/slugify.test.node.js +86 -0
- package/dist/utils/slugify.test.node.js.map +1 -0
- package/dist/utils/storage-utils.d.ts +36 -0
- package/dist/utils/storage-utils.d.ts.map +1 -0
- package/dist/utils/storage-utils.js +38 -0
- package/dist/utils/storage-utils.js.map +1 -0
- package/dist/utils/utils.general.d.ts +64 -0
- package/dist/utils/utils.general.d.ts.map +1 -0
- package/dist/utils/utils.general.js +219 -0
- package/dist/utils/utils.general.js.map +1 -0
- package/dist/validation/index.d.ts +9 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +9 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/shared.d.ts +36 -0
- package/dist/validation/shared.d.ts.map +1 -0
- package/dist/validation/shared.js +42 -0
- package/dist/validation/shared.js.map +1 -0
- package/dist/workflow/index.d.ts +2 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +3 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/workflow.d.ts +40 -0
- package/dist/workflow/workflow.d.ts.map +1 -0
- package/dist/workflow/workflow.js +96 -0
- package/dist/workflow/workflow.js.map +1 -0
- package/dist/workflow/workflow.test.node.d.ts +2 -0
- package/dist/workflow/workflow.test.node.d.ts.map +1 -0
- package/dist/workflow/workflow.test.node.js +198 -0
- package/dist/workflow/workflow.test.node.js.map +1 -0
- package/package.json +88 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { RequestContext } from '@byline/auth';
|
|
9
|
+
import type { ReadContext } from './db-types.js';
|
|
10
|
+
import type { FieldSetData, FieldSetDataAllLocales, StoredFileValue } from './field-data-types.js';
|
|
11
|
+
import type { Block, DefaultValue, Field, FileField, ImageField } from './field-types.js';
|
|
12
|
+
import type { QueryPredicate } from './query-predicate.js';
|
|
13
|
+
import type { IStorageProvider } from './storage-types.js';
|
|
14
|
+
import type { Prettify } from './type-utils.js';
|
|
15
|
+
/** Output format for Sharp-generated image variants. */
|
|
16
|
+
export type ImageFormat = 'jpeg' | 'png' | 'webp' | 'avif';
|
|
17
|
+
/**
|
|
18
|
+
* Resize fit strategy passed to Sharp.
|
|
19
|
+
* Mirrors Sharp's `fit` option.
|
|
20
|
+
*/
|
|
21
|
+
export type ImageFit = 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
22
|
+
/**
|
|
23
|
+
* A named image size variant to generate after upload via Sharp.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* { name: 'thumbnail', width: 200, height: 200, fit: 'cover' }
|
|
28
|
+
* { name: 'desktop', width: 1920, fit: 'inside', format: 'webp', quality: 85 }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export interface ImageSize {
|
|
32
|
+
/** A unique name for this variant (e.g. `'thumbnail'`, `'desktop'`). */
|
|
33
|
+
name: string;
|
|
34
|
+
/** Target width in pixels. Omit to scale proportionally from height. */
|
|
35
|
+
width?: number;
|
|
36
|
+
/** Target height in pixels. Omit to scale proportionally from width. */
|
|
37
|
+
height?: number;
|
|
38
|
+
/** Resize fit strategy. Defaults to `'cover'`. */
|
|
39
|
+
fit?: ImageFit;
|
|
40
|
+
/** Output format override. Defaults to the original image format. */
|
|
41
|
+
format?: ImageFormat;
|
|
42
|
+
/** Quality (1–100). Relevant for jpeg, webp, and avif output. */
|
|
43
|
+
quality?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Configuration block declared on an `image` or `file` field. Hangs the
|
|
47
|
+
* upload contract — accepted MIME types, size limit, generated variants,
|
|
48
|
+
* storage routing, and server-side hooks — directly off the field that
|
|
49
|
+
* receives the file. A collection with at least one image/file field
|
|
50
|
+
* carrying an `upload` block is upload-capable; the auto-mounted route at
|
|
51
|
+
* `POST /admin/api/<collection-path>/upload` accepts a `field` selector
|
|
52
|
+
* to pick the target field.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* export const Media: CollectionDefinition = {
|
|
57
|
+
* path: 'media',
|
|
58
|
+
* fields: [
|
|
59
|
+
* {
|
|
60
|
+
* name: 'image',
|
|
61
|
+
* label: 'Image',
|
|
62
|
+
* type: 'image',
|
|
63
|
+
* upload: {
|
|
64
|
+
* mimeTypes: ['image/*'],
|
|
65
|
+
* maxFileSize: 10 * 1024 * 1024, // 10 MB
|
|
66
|
+
* sizes: [
|
|
67
|
+
* { name: 'thumbnail', width: 300, height: 300, fit: 'cover' },
|
|
68
|
+
* { name: 'mobile', width: 768, fit: 'inside' },
|
|
69
|
+
* { name: 'desktop', width: 1920, fit: 'inside', format: 'webp', quality: 85 },
|
|
70
|
+
* ],
|
|
71
|
+
* },
|
|
72
|
+
* },
|
|
73
|
+
* // ...
|
|
74
|
+
* ],
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export interface UploadConfig {
|
|
79
|
+
/**
|
|
80
|
+
* Allowed MIME types. Supports wildcards (e.g. `'image/*'`).
|
|
81
|
+
* Omit to allow all types.
|
|
82
|
+
*/
|
|
83
|
+
mimeTypes?: string[];
|
|
84
|
+
/** Maximum file size in bytes. Omit for no limit. */
|
|
85
|
+
maxFileSize?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Named image variants to generate via Sharp after the original is
|
|
88
|
+
* stored. Only applied to MIME types that match `image/*`.
|
|
89
|
+
* Omit to skip image processing (e.g. for a video or PDF field).
|
|
90
|
+
*/
|
|
91
|
+
sizes?: ImageSize[];
|
|
92
|
+
/**
|
|
93
|
+
* Storage provider for this field.
|
|
94
|
+
*
|
|
95
|
+
* When set, this takes precedence over the site-wide `ServerConfig.storage`
|
|
96
|
+
* default. Use this to route different fields to different backends —
|
|
97
|
+
* for example, keep avatars on local disk while sending editorial
|
|
98
|
+
* images to S3, or target separate S3 buckets per field.
|
|
99
|
+
*
|
|
100
|
+
* Falls back to `ServerConfig.storage` when omitted.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* // Dedicated S3 bucket just for this field:
|
|
105
|
+
* upload: {
|
|
106
|
+
* mimeTypes: ['image/*'],
|
|
107
|
+
* storage: s3StorageProvider({ bucket: 'my-photos', region: 'eu-west-1', ... }),
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
storage?: IStorageProvider;
|
|
112
|
+
/**
|
|
113
|
+
* Server-side lifecycle hooks for this field's upload pipeline.
|
|
114
|
+
*
|
|
115
|
+
* `beforeStore` fires after MIME / size validation passes and before
|
|
116
|
+
* the storage provider is asked to write the file — the bytes have
|
|
117
|
+
* already crossed the network and live on the server (an in-memory
|
|
118
|
+
* Buffer or /tmp file, depending on the storage adapter), but
|
|
119
|
+
* permanent storage hasn't been touched yet. It can rename or reject
|
|
120
|
+
* the upload.
|
|
121
|
+
*
|
|
122
|
+
* `afterStore` fires after the original file *and* all image variants
|
|
123
|
+
* have been written to the storage provider, before the document
|
|
124
|
+
* version is created.
|
|
125
|
+
*
|
|
126
|
+
* @see UploadHooks
|
|
127
|
+
*/
|
|
128
|
+
hooks?: UploadHooks;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The three status names that every workflow must contain.
|
|
132
|
+
*
|
|
133
|
+
* Storage, versioning, and API logic depend on these statuses being present.
|
|
134
|
+
* `defineWorkflow()` enforces their existence and ordering automatically:
|
|
135
|
+
*
|
|
136
|
+
* `[draft, ...customStatuses, published, archived]`
|
|
137
|
+
*/
|
|
138
|
+
export declare const WORKFLOW_STATUS_DRAFT: "draft";
|
|
139
|
+
export declare const WORKFLOW_STATUS_PUBLISHED: "published";
|
|
140
|
+
export declare const WORKFLOW_STATUS_ARCHIVED: "archived";
|
|
141
|
+
export declare const REQUIRED_WORKFLOW_STATUSES: readonly ["draft", "published", "archived"];
|
|
142
|
+
export type RequiredWorkflowStatusName = (typeof REQUIRED_WORKFLOW_STATUSES)[number];
|
|
143
|
+
/**
|
|
144
|
+
* A single status in a sequential workflow.
|
|
145
|
+
*
|
|
146
|
+
* `name` is the value stored in the database (e.g. `'draft'`, `'needs_review'`).
|
|
147
|
+
* `label` is an optional human-readable display label for the status indicator (defaults to `name`).
|
|
148
|
+
* `verb` is an optional action label shown on the transition button (e.g. "Publish"). Defaults to `label` then `name`.
|
|
149
|
+
*/
|
|
150
|
+
export interface WorkflowStatus {
|
|
151
|
+
name: string;
|
|
152
|
+
label?: string;
|
|
153
|
+
verb?: string;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Configurable sequential workflow for a collection.
|
|
157
|
+
*
|
|
158
|
+
* The `statuses` array defines the ordered progression. The first entry is
|
|
159
|
+
* used as the default status for new documents unless `defaultStatus` is
|
|
160
|
+
* explicitly set.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* defineWorkflow({
|
|
165
|
+
* draft: { label: 'Draft', verb: 'Revert to Draft' },
|
|
166
|
+
* published: { label: 'Published', verb: 'Publish' },
|
|
167
|
+
* archived: { label: 'Archived', verb: 'Archive' },
|
|
168
|
+
* customStatuses: [
|
|
169
|
+
* { name: 'needs_review', label: 'Needs Review', verb: 'Request Review' },
|
|
170
|
+
* ],
|
|
171
|
+
* })
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export interface WorkflowConfig {
|
|
175
|
+
statuses: WorkflowStatus[];
|
|
176
|
+
/** Override the default status for new documents (defaults to the first entry). */
|
|
177
|
+
defaultStatus?: string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Optional label/verb overrides for one of the three required workflow statuses.
|
|
181
|
+
*
|
|
182
|
+
* The `name` is fixed (`'draft'`, `'published'`, or `'archived'`) and injected
|
|
183
|
+
* automatically by `defineWorkflow()`.
|
|
184
|
+
*/
|
|
185
|
+
export interface RequiredStatusConfig {
|
|
186
|
+
label?: string;
|
|
187
|
+
verb?: string;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Input accepted by `defineWorkflow()`.
|
|
191
|
+
*
|
|
192
|
+
* The three required statuses — `draft`, `published`, `archived` — are always
|
|
193
|
+
* present in the resulting workflow. Their position is enforced:
|
|
194
|
+
*
|
|
195
|
+
* `[draft, ...customStatuses, published, archived]`
|
|
196
|
+
*
|
|
197
|
+
* Each required status accepts an optional `{ label, verb }` override.
|
|
198
|
+
* Any additional statuses are placed between `draft` and `published` via
|
|
199
|
+
* `customStatuses`, in the order specified.
|
|
200
|
+
*/
|
|
201
|
+
export interface DefineWorkflowInput {
|
|
202
|
+
/** Customize the `draft` status (always first). Defaults to `{ label: 'Draft' }`. */
|
|
203
|
+
draft?: RequiredStatusConfig;
|
|
204
|
+
/** Customize the `published` status (always second-to-last). Defaults to `{ label: 'Published' }`. */
|
|
205
|
+
published?: RequiredStatusConfig;
|
|
206
|
+
/** Customize the `archived` status (always last). Defaults to `{ label: 'Archived' }`. */
|
|
207
|
+
archived?: RequiredStatusConfig;
|
|
208
|
+
/** Additional statuses placed between `draft` and `published`, in order. */
|
|
209
|
+
customStatuses?: WorkflowStatus[];
|
|
210
|
+
/**
|
|
211
|
+
* Override the default status for new documents (defaults to `'draft'`).
|
|
212
|
+
*
|
|
213
|
+
* Setting this to `'published'` is the supported "publish-on-save" pattern:
|
|
214
|
+
* the full draft → published → archived lifecycle stays available, but new
|
|
215
|
+
* versions land directly as `published` so trusted editors can skip the
|
|
216
|
+
* draft step. For collections that have no editorial lifecycle at all
|
|
217
|
+
* (categories, tags, taxonomies), use `SINGLE_STATUS_WORKFLOW` instead —
|
|
218
|
+
* that also strips the workflow controls from the admin UI.
|
|
219
|
+
*/
|
|
220
|
+
defaultStatus?: string;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The built-in default workflow used when a collection does not define its own.
|
|
224
|
+
*/
|
|
225
|
+
export declare const DEFAULT_WORKFLOW: WorkflowConfig;
|
|
226
|
+
/**
|
|
227
|
+
* Single-status workflow for collections that have no editorial lifecycle —
|
|
228
|
+
* typically lookup or reference data such as categories, tags, taxonomies,
|
|
229
|
+
* and facets where a draft → review → publish flow adds friction without
|
|
230
|
+
* value.
|
|
231
|
+
*
|
|
232
|
+
* Effects of using this workflow:
|
|
233
|
+
* - Every save lands as `'published'` immediately, so public clients
|
|
234
|
+
* reading via `readMode: 'published'` see new rows right away.
|
|
235
|
+
* - The form renderer hides workflow controls and shows only Save / Close.
|
|
236
|
+
* - The list view's status filter is hidden.
|
|
237
|
+
* - `changeDocumentStatus()` and `unpublishDocument()` reject server-side
|
|
238
|
+
* because there is no other status to transition to.
|
|
239
|
+
*
|
|
240
|
+
* For editorial collections that should keep the draft/published/archived
|
|
241
|
+
* lifecycle but skip the draft step on save, prefer
|
|
242
|
+
* `defineWorkflow({ ..., defaultStatus: 'published' })` instead.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* import { SINGLE_STATUS_WORKFLOW } from '@byline/core'
|
|
247
|
+
*
|
|
248
|
+
* export const DocsCategories: CollectionDefinition = {
|
|
249
|
+
* path: 'docs-categories',
|
|
250
|
+
* workflow: SINGLE_STATUS_WORKFLOW,
|
|
251
|
+
* // ...
|
|
252
|
+
* }
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
export declare const SINGLE_STATUS_WORKFLOW: WorkflowConfig;
|
|
256
|
+
/**
|
|
257
|
+
* Type-safe factory for creating a `WorkflowConfig`.
|
|
258
|
+
*
|
|
259
|
+
* Guarantees that the three required statuses (`draft`, `published`, `archived`)
|
|
260
|
+
* are always present and correctly ordered:
|
|
261
|
+
*
|
|
262
|
+
* `[draft, ...customStatuses, published, archived]`
|
|
263
|
+
*
|
|
264
|
+
* Custom statuses are inserted between `draft` and `published`. If a custom
|
|
265
|
+
* status uses a reserved name (`draft`, `published`, or `archived`) this
|
|
266
|
+
* function throws at initialization time.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* // Minimal — uses default labels for all three required statuses:
|
|
271
|
+
* defineWorkflow({})
|
|
272
|
+
*
|
|
273
|
+
* // With custom labels/verbs on the required statuses:
|
|
274
|
+
* defineWorkflow({
|
|
275
|
+
* draft: { label: 'Draft', verb: 'Revert to Draft' },
|
|
276
|
+
* published: { label: 'Published', verb: 'Publish' },
|
|
277
|
+
* archived: { label: 'Archived', verb: 'Archive' },
|
|
278
|
+
* })
|
|
279
|
+
*
|
|
280
|
+
* // With additional statuses between draft and published:
|
|
281
|
+
* defineWorkflow({
|
|
282
|
+
* draft: { label: 'Draft', verb: 'Revert to Draft' },
|
|
283
|
+
* published: { label: 'Published', verb: 'Publish' },
|
|
284
|
+
* archived: { label: 'Archived', verb: 'Archive' },
|
|
285
|
+
* customStatuses: [
|
|
286
|
+
* { name: 'needs_review', label: 'Needs Review', verb: 'Request Review' },
|
|
287
|
+
* ],
|
|
288
|
+
* })
|
|
289
|
+
*
|
|
290
|
+
* // Publish-on-save: keep the full lifecycle, but new versions land
|
|
291
|
+
* // directly as `published` instead of `draft`.
|
|
292
|
+
* defineWorkflow({
|
|
293
|
+
* defaultStatus: 'published',
|
|
294
|
+
* })
|
|
295
|
+
* ```
|
|
296
|
+
*
|
|
297
|
+
* For collections that have no editorial lifecycle at all (categories,
|
|
298
|
+
* tags, taxonomies), use `SINGLE_STATUS_WORKFLOW` instead.
|
|
299
|
+
*/
|
|
300
|
+
export declare function defineWorkflow(input?: DefineWorkflowInput): WorkflowConfig;
|
|
301
|
+
/**
|
|
302
|
+
* Context passed to `beforeCreate` hooks.
|
|
303
|
+
*
|
|
304
|
+
* The hook can mutate `data` before it is persisted.
|
|
305
|
+
*/
|
|
306
|
+
export interface BeforeCreateContext {
|
|
307
|
+
data: Record<string, any>;
|
|
308
|
+
collectionPath: string;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Context passed to `afterCreate` hooks.
|
|
312
|
+
*
|
|
313
|
+
* Includes the `documentId` and `documentVersionId` returned by storage
|
|
314
|
+
* so the hook can reference the persisted document.
|
|
315
|
+
*/
|
|
316
|
+
export interface AfterCreateContext {
|
|
317
|
+
data: Record<string, any>;
|
|
318
|
+
collectionPath: string;
|
|
319
|
+
documentId: string;
|
|
320
|
+
documentVersionId: string;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Context passed to `beforeUpdate` hooks — both PUT and patch flows.
|
|
324
|
+
*
|
|
325
|
+
* `data` is the next version (mutable). `originalData` is the previous
|
|
326
|
+
* version as reconstructed from storage.
|
|
327
|
+
*/
|
|
328
|
+
export interface BeforeUpdateContext {
|
|
329
|
+
data: Record<string, any>;
|
|
330
|
+
originalData: Record<string, any>;
|
|
331
|
+
collectionPath: string;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Context passed to `afterUpdate` hooks.
|
|
335
|
+
*
|
|
336
|
+
* Includes the `documentId` and `documentVersionId` of the newly created
|
|
337
|
+
* version so the hook can reference the persisted document.
|
|
338
|
+
*/
|
|
339
|
+
export interface AfterUpdateContext {
|
|
340
|
+
data: Record<string, any>;
|
|
341
|
+
originalData: Record<string, any>;
|
|
342
|
+
collectionPath: string;
|
|
343
|
+
documentId: string;
|
|
344
|
+
documentVersionId: string;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Context passed to `beforeStatusChange` / `afterStatusChange` hooks.
|
|
348
|
+
*/
|
|
349
|
+
export interface StatusChangeContext {
|
|
350
|
+
documentId: string;
|
|
351
|
+
documentVersionId: string;
|
|
352
|
+
collectionPath: string;
|
|
353
|
+
previousStatus: string;
|
|
354
|
+
nextStatus: string;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Context passed to `beforeUnpublish` hooks.
|
|
358
|
+
*/
|
|
359
|
+
export interface BeforeUnpublishContext {
|
|
360
|
+
documentId: string;
|
|
361
|
+
collectionPath: string;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Context passed to `afterUnpublish` hooks.
|
|
365
|
+
*
|
|
366
|
+
* `archivedCount` indicates how many published versions were archived.
|
|
367
|
+
*/
|
|
368
|
+
export interface AfterUnpublishContext {
|
|
369
|
+
documentId: string;
|
|
370
|
+
collectionPath: string;
|
|
371
|
+
archivedCount: number;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Context passed to `beforeDelete` / `afterDelete` hooks (future).
|
|
375
|
+
*/
|
|
376
|
+
export interface DeleteContext {
|
|
377
|
+
documentId: string;
|
|
378
|
+
collectionPath: string;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Context passed to `beforeStore` hooks (configured on
|
|
382
|
+
* `field.upload.hooks`).
|
|
383
|
+
*
|
|
384
|
+
* Fires after MIME-type and file-size validation succeed and before
|
|
385
|
+
* the storage provider is asked to write the file. By the time this
|
|
386
|
+
* hook runs the bytes have already crossed the network and live on
|
|
387
|
+
* the server — an in-memory `Buffer` in our current adapters, or a
|
|
388
|
+
* /tmp file with a future streaming adapter — but permanent storage
|
|
389
|
+
* has not yet been touched.
|
|
390
|
+
*
|
|
391
|
+
* The hook can:
|
|
392
|
+
*
|
|
393
|
+
* - Rename the file by returning a string or `{ filename }`. The
|
|
394
|
+
* override is threaded into `storage.upload(...)`, so generated
|
|
395
|
+
* image variants automatically inherit the new prefix.
|
|
396
|
+
* - Reject the upload by returning `{ error }`. Surfaces as
|
|
397
|
+
* `ERR_VALIDATION` with the supplied message; no file is written,
|
|
398
|
+
* no variants are generated, no document is created, no later
|
|
399
|
+
* hook in the chain runs.
|
|
400
|
+
* - Keep defaults by returning `void` / `undefined`.
|
|
401
|
+
*
|
|
402
|
+
* When configured as an array, hooks fold: each function receives the
|
|
403
|
+
* filename returned by the previous function (or the original sanitised
|
|
404
|
+
* filename if the previous returned `void`).
|
|
405
|
+
*/
|
|
406
|
+
export interface BeforeStoreContext {
|
|
407
|
+
/** Name of the image/file field receiving this upload. */
|
|
408
|
+
fieldName: string;
|
|
409
|
+
/** The full field definition. Carries `field.upload` for hooks that want to introspect their own config. */
|
|
410
|
+
field: ImageField | FileField;
|
|
411
|
+
/** Sanitised default filename. Hooks may override. */
|
|
412
|
+
filename: string;
|
|
413
|
+
mimeType: string;
|
|
414
|
+
fileSize: number;
|
|
415
|
+
/**
|
|
416
|
+
* Other form values posted alongside the file. Use these to derive
|
|
417
|
+
* filenames from document context (e.g. `fields.publicationId`,
|
|
418
|
+
* `fields.serialNumber`). Always strings — multipart form values
|
|
419
|
+
* arrive untyped.
|
|
420
|
+
*/
|
|
421
|
+
fields: Record<string, string>;
|
|
422
|
+
collectionPath: string;
|
|
423
|
+
/** Authenticated request context. `actor.id`, `actor.tenantId`, etc. for prefixing. */
|
|
424
|
+
requestContext: RequestContext;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Result returned by a `beforeStore` hook.
|
|
428
|
+
*
|
|
429
|
+
* - `string` → override filename (shorthand).
|
|
430
|
+
* - `{ filename }` → override filename (object form).
|
|
431
|
+
* - `{ error }` → reject the upload; surfaces as
|
|
432
|
+
* `ERR_VALIDATION`. Short-circuits the chain.
|
|
433
|
+
* - `void` / undefined → keep current defaults.
|
|
434
|
+
*/
|
|
435
|
+
export type BeforeStoreResult = string | {
|
|
436
|
+
filename?: string;
|
|
437
|
+
error?: undefined;
|
|
438
|
+
} | {
|
|
439
|
+
error: string;
|
|
440
|
+
filename?: undefined;
|
|
441
|
+
} | void;
|
|
442
|
+
/**
|
|
443
|
+
* A `beforeStore` hook function. Async-capable.
|
|
444
|
+
*/
|
|
445
|
+
export type BeforeStoreHookFn = (ctx: BeforeStoreContext) => BeforeStoreResult | Promise<BeforeStoreResult>;
|
|
446
|
+
/**
|
|
447
|
+
* Context passed to `afterStore` hooks (configured on
|
|
448
|
+
* `field.upload.hooks`).
|
|
449
|
+
*
|
|
450
|
+
* Fires after the original file and every generated image variant
|
|
451
|
+
* have been written to the storage provider, and before the document
|
|
452
|
+
* version is created. Suitable for CDN cache warmup, audit logging,
|
|
453
|
+
* or async post-processing kicks. Failures are logged but do not
|
|
454
|
+
* roll back the storage write — consistent with `afterCreate` etc.,
|
|
455
|
+
* which run outside the storage transaction.
|
|
456
|
+
*/
|
|
457
|
+
export interface AfterStoreContext {
|
|
458
|
+
/** Name of the image/file field that received this upload. */
|
|
459
|
+
fieldName: string;
|
|
460
|
+
field: ImageField | FileField;
|
|
461
|
+
/**
|
|
462
|
+
* The persisted file value, including the `variants` array with
|
|
463
|
+
* `storagePath`, `storageUrl`, `width`, `height`, and `format` for
|
|
464
|
+
* each generated derivative.
|
|
465
|
+
*/
|
|
466
|
+
storedFile: StoredFileValue;
|
|
467
|
+
fields: Record<string, string>;
|
|
468
|
+
collectionPath: string;
|
|
469
|
+
requestContext: RequestContext;
|
|
470
|
+
}
|
|
471
|
+
/** An `afterStore` hook function. Async-capable. */
|
|
472
|
+
export type AfterStoreHookFn = (ctx: AfterStoreContext) => void | Promise<void>;
|
|
473
|
+
/**
|
|
474
|
+
* Server-side hooks declared on an upload-capable field's `upload`
|
|
475
|
+
* block. Each hook accepts a single function or an ordered array;
|
|
476
|
+
* `beforeStore` chains fold filename overrides through the array,
|
|
477
|
+
* `afterStore` chains run sequentially with errors logged.
|
|
478
|
+
*/
|
|
479
|
+
export interface UploadHooks {
|
|
480
|
+
beforeStore?: BeforeStoreHookFn | BeforeStoreHookFn[];
|
|
481
|
+
afterStore?: AfterStoreHookFn | AfterStoreHookFn[];
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Context passed to `afterRead` hooks.
|
|
485
|
+
*
|
|
486
|
+
* Fires once per materialised document on every read path that runs through
|
|
487
|
+
* `@byline/client` or `populateDocuments`:
|
|
488
|
+
* - `find`, `findOne`, `findById`, `findByPath` on `CollectionHandle`
|
|
489
|
+
* (once per returned source document)
|
|
490
|
+
* - Each populated relation target across every depth level
|
|
491
|
+
*
|
|
492
|
+
* The hook receives the **raw storage shape** (`{document_version_id,
|
|
493
|
+
* document_id, path, status, created_at, updated_at, fields, …}`), not
|
|
494
|
+
* the camelCase `ClientDocument` — afterRead runs *before* the client's
|
|
495
|
+
* response shaping pass so mutations to `fields` propagate cleanly.
|
|
496
|
+
* Mutations persist in place; there is no return value.
|
|
497
|
+
*
|
|
498
|
+
* Fires **after** populate on the source document, so hooks can observe
|
|
499
|
+
* (and mutate) the fully populated tree.
|
|
500
|
+
*
|
|
501
|
+
* Recursion safety: `readContext` is the same request-scoped context used
|
|
502
|
+
* by populate. A hook that performs its own reads should thread this
|
|
503
|
+
* context back in via `client.collection(...).findById(id, { _readContext:
|
|
504
|
+
* readContext })` so the visited set and read budget are preserved —
|
|
505
|
+
* essential to foreclose the A→B→A loop (see
|
|
506
|
+
* `docs/analysis/RELATIONSHIPS-ANALYSIS.md` § "Special consideration:
|
|
507
|
+
* recursive-read safety").
|
|
508
|
+
*/
|
|
509
|
+
export interface AfterReadContext {
|
|
510
|
+
/** The raw reconstructed document. Mutate in place — changes persist. */
|
|
511
|
+
doc: Record<string, any>;
|
|
512
|
+
collectionPath: string;
|
|
513
|
+
/** Thread this into any nested reads the hook performs. */
|
|
514
|
+
readContext: ReadContext;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Context passed to `beforeRead` hooks.
|
|
518
|
+
*
|
|
519
|
+
* Fires once per `IDocumentQueries.findDocuments` call (and once per populate
|
|
520
|
+
* batch, per target collection) **before** any DB work. The hook returns a
|
|
521
|
+
* `QueryPredicate` that the query layer compiles into the same `EXISTS` /
|
|
522
|
+
* `LEFT JOIN LATERAL` SQL machinery the client's existing `where` parser
|
|
523
|
+
* emits, then ANDs onto whatever the caller passed in `where`.
|
|
524
|
+
*
|
|
525
|
+
* Returning `undefined` (or simply `void`) means "no scoping" — typically
|
|
526
|
+
* the superuser / unconditional-read branch. Use a sentinel predicate that
|
|
527
|
+
* yields no rows (e.g. `{ id: '__none__' }`) when the actor cannot read
|
|
528
|
+
* anything; do not throw, because callers expect empty list results rather
|
|
529
|
+
* than collapsed endpoints.
|
|
530
|
+
*
|
|
531
|
+
* The hook receives:
|
|
532
|
+
* - `requestContext` — the authenticated request, including `actor`. The
|
|
533
|
+
* actor is the primary input to most predicates.
|
|
534
|
+
* - `readContext` — the same per-request context threaded through
|
|
535
|
+
* populate and `afterRead`. Carries a hook-result cache so async
|
|
536
|
+
* predicates don't re-run across populate fanout.
|
|
537
|
+
* - `collectionPath` — the collection being queried (useful when the
|
|
538
|
+
* same hook function is reused across collections).
|
|
539
|
+
*
|
|
540
|
+
* See `docs/analysis/AUTHN-AUTHZ-ANALYSIS.md` (Phase 7) for the strategic
|
|
541
|
+
* rationale and `docs/analysis/ACCESS-CONTROL-RECIPES.md` for worked
|
|
542
|
+
* examples.
|
|
543
|
+
*/
|
|
544
|
+
export interface BeforeReadContext {
|
|
545
|
+
collectionPath: string;
|
|
546
|
+
requestContext: RequestContext;
|
|
547
|
+
readContext: ReadContext;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* A `beforeRead` hook function. Returns a `QueryPredicate` to scope the
|
|
551
|
+
* query, or `void`/`undefined` to apply no scoping. May be async — actors
|
|
552
|
+
* needing tenant lookups or role-metadata fetches commonly are.
|
|
553
|
+
*/
|
|
554
|
+
export type BeforeReadHookFn = (ctx: BeforeReadContext) => QueryPredicate | void | Promise<QueryPredicate | void>;
|
|
555
|
+
/**
|
|
556
|
+
* Slot type for `beforeRead`.
|
|
557
|
+
*
|
|
558
|
+
* Distinct from the generic `CollectionHookSlot` because `beforeRead`
|
|
559
|
+
* returns a value (a predicate). When multiple hook functions are
|
|
560
|
+
* configured, their predicates are combined with implicit AND in
|
|
561
|
+
* declaration order; functions that return `void` are skipped.
|
|
562
|
+
*/
|
|
563
|
+
export type BeforeReadHookSlot = BeforeReadHookFn | BeforeReadHookFn[];
|
|
564
|
+
/**
|
|
565
|
+
* A single collection-hook function signature, parameterised by context type.
|
|
566
|
+
*/
|
|
567
|
+
export type CollectionHookFn<Ctx> = (ctx: Ctx) => void | Promise<void>;
|
|
568
|
+
/**
|
|
569
|
+
* A hook slot: accepts a single function **or** an array of functions.
|
|
570
|
+
*
|
|
571
|
+
* When an array is provided the functions are executed sequentially in order.
|
|
572
|
+
*/
|
|
573
|
+
export type CollectionHookSlot<Ctx> = CollectionHookFn<Ctx> | CollectionHookFn<Ctx>[];
|
|
574
|
+
/** Normalise a collection-hook slot (single function or array) into a flat array. */
|
|
575
|
+
export declare function normalizeCollectionHook<Ctx>(hook: CollectionHookSlot<Ctx> | undefined): CollectionHookFn<Ctx>[];
|
|
576
|
+
/**
|
|
577
|
+
* Lifecycle hooks for a collection.
|
|
578
|
+
*
|
|
579
|
+
* Each hook receives a typed context object. `before*` hooks can mutate the
|
|
580
|
+
* data before it is persisted; `after*` hooks receive the final data after
|
|
581
|
+
* persistence together with identifiers of what was created/updated.
|
|
582
|
+
*
|
|
583
|
+
* Hooks run **outside** the storage transaction — they cannot participate in
|
|
584
|
+
* the atomic write. They are suitable for logging, cache invalidation,
|
|
585
|
+
* webhooks, and similar side-effects.
|
|
586
|
+
*
|
|
587
|
+
* Each hook accepts a single function or an **array** of functions that are
|
|
588
|
+
* executed sequentially in order. Hooks are optional — if omitted, the
|
|
589
|
+
* framework skips the step.
|
|
590
|
+
*/
|
|
591
|
+
export interface CollectionHooks {
|
|
592
|
+
/** Runs before a new document is created. Can mutate `data`. */
|
|
593
|
+
beforeCreate?: CollectionHookSlot<BeforeCreateContext>;
|
|
594
|
+
/** Runs after a new document is created. */
|
|
595
|
+
afterCreate?: CollectionHookSlot<AfterCreateContext>;
|
|
596
|
+
/** Runs before an existing document is updated (PUT or patch). Can mutate `data`. */
|
|
597
|
+
beforeUpdate?: CollectionHookSlot<BeforeUpdateContext>;
|
|
598
|
+
/** Runs after an existing document is updated. */
|
|
599
|
+
afterUpdate?: CollectionHookSlot<AfterUpdateContext>;
|
|
600
|
+
/** Runs before a document's workflow status is changed. */
|
|
601
|
+
beforeStatusChange?: CollectionHookSlot<StatusChangeContext>;
|
|
602
|
+
/** Runs after a document's workflow status has been changed. */
|
|
603
|
+
afterStatusChange?: CollectionHookSlot<StatusChangeContext>;
|
|
604
|
+
/** Runs before a published document is unpublished (archived). */
|
|
605
|
+
beforeUnpublish?: CollectionHookSlot<BeforeUnpublishContext>;
|
|
606
|
+
/** Runs after a published document has been unpublished. */
|
|
607
|
+
afterUnpublish?: CollectionHookSlot<AfterUnpublishContext>;
|
|
608
|
+
/** Runs before a document is deleted. */
|
|
609
|
+
beforeDelete?: CollectionHookSlot<DeleteContext>;
|
|
610
|
+
/** Runs after a document is deleted. */
|
|
611
|
+
afterDelete?: CollectionHookSlot<DeleteContext>;
|
|
612
|
+
/**
|
|
613
|
+
* Runs once per `findDocuments` call (and once per populate batch, per
|
|
614
|
+
* target collection), **before** any DB work. Returns a `QueryPredicate`
|
|
615
|
+
* that the query layer ANDs onto the caller's `where` to enforce
|
|
616
|
+
* read-side row scoping (multi-tenant, owner-only-drafts, soft-delete
|
|
617
|
+
* hide, etc). Returning `void` applies no scoping. Multiple functions
|
|
618
|
+
* combine with implicit AND. See
|
|
619
|
+
* `docs/analysis/ACCESS-CONTROL-RECIPES.md`.
|
|
620
|
+
*/
|
|
621
|
+
beforeRead?: BeforeReadHookSlot;
|
|
622
|
+
/**
|
|
623
|
+
* Runs once per materialised document on every read path that flows
|
|
624
|
+
* through `@byline/client` or `populateDocuments`. Can mutate
|
|
625
|
+
* `ctx.doc.fields` in place — mutations propagate back through the
|
|
626
|
+
* response. Fires after populate on the source document, so hooks see
|
|
627
|
+
* the fully populated tree. Hooks that perform their own reads should
|
|
628
|
+
* thread `ctx.readContext` through to preserve the visited set and
|
|
629
|
+
* read budget (A→B→A safety).
|
|
630
|
+
*/
|
|
631
|
+
afterRead?: CollectionHookSlot<AfterReadContext>;
|
|
632
|
+
}
|
|
633
|
+
export interface CollectionDefinition {
|
|
634
|
+
labels: {
|
|
635
|
+
singular: string;
|
|
636
|
+
plural: string;
|
|
637
|
+
};
|
|
638
|
+
path: string;
|
|
639
|
+
fields: Field[];
|
|
640
|
+
/** Sequential workflow configuration. Falls back to DEFAULT_WORKFLOW if omitted. */
|
|
641
|
+
workflow?: WorkflowConfig;
|
|
642
|
+
/** Lifecycle hooks for server-side document operations. */
|
|
643
|
+
hooks?: CollectionHooks;
|
|
644
|
+
/**
|
|
645
|
+
* Configures which text fields are searched when the admin list view's
|
|
646
|
+
* search box is used. Only `store_text` fields are supported for now.
|
|
647
|
+
* Falls back to `{ fields: ['title'] }` when omitted.
|
|
648
|
+
*/
|
|
649
|
+
search?: {
|
|
650
|
+
fields: string[];
|
|
651
|
+
};
|
|
652
|
+
/**
|
|
653
|
+
* The field that represents this document's identity — used anywhere a
|
|
654
|
+
* single-line label for the document is needed: form headings, relation
|
|
655
|
+
* widget summaries, populate's default projection, future `afterRead`
|
|
656
|
+
* hooks, logs, etc.
|
|
657
|
+
*
|
|
658
|
+
* Lives on the schema (not admin config) so server-side consumers like
|
|
659
|
+
* `populateDocuments` and the client API can read it without taking a
|
|
660
|
+
* dependency on UI concerns. Analogous to Django's `Model.__str__`.
|
|
661
|
+
*/
|
|
662
|
+
useAsTitle?: string;
|
|
663
|
+
/**
|
|
664
|
+
* Names the field whose value initialises this collection's
|
|
665
|
+
* `documentVersions.path` column. The value is slugified (in the
|
|
666
|
+
* default content locale) using the installation slugifier and stored
|
|
667
|
+
* as system metadata — `path` itself is a reserved name and cannot be
|
|
668
|
+
* declared as a field.
|
|
669
|
+
*
|
|
670
|
+
* `path` is sticky after creation: subsequent updates do not
|
|
671
|
+
* re-derive. Users edit it via the system path widget; collections
|
|
672
|
+
* without `useAsPath` receive a UUID `path` instead.
|
|
673
|
+
*/
|
|
674
|
+
useAsPath?: string;
|
|
675
|
+
/***
|
|
676
|
+
* When `true`, the rich text editor's link plugin surfaces relation targets
|
|
677
|
+
* from this collection as linkable options. Requires the collection to have
|
|
678
|
+
* a `useAsTitle` field, which is used to label the options in the editor.
|
|
679
|
+
*/
|
|
680
|
+
linksInEditor?: boolean;
|
|
681
|
+
/**
|
|
682
|
+
* When `true`, the admin landing page displays a per-status document count
|
|
683
|
+
* inside the collection card. Requires a database round-trip per collection
|
|
684
|
+
* on every landing-page load, so opt in deliberately.
|
|
685
|
+
*/
|
|
686
|
+
showStats?: boolean;
|
|
687
|
+
/**
|
|
688
|
+
* Optional explicit version pin. When omitted, the startup bootstrap
|
|
689
|
+
* auto-increments the collection's stored version any time the schema
|
|
690
|
+
* fingerprint changes. When set, the value is used verbatim as long as it
|
|
691
|
+
* is >= the currently-stored version; pinning backwards throws at startup.
|
|
692
|
+
*
|
|
693
|
+
* The stamped version is written onto every `documentVersions` row so that
|
|
694
|
+
* a document can later be resolved against the schema shape it was
|
|
695
|
+
* authored under.
|
|
696
|
+
*/
|
|
697
|
+
version?: number;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Type-safe factory for creating a CollectionDefinition.
|
|
701
|
+
* Returns the definition as-is but provides type checking.
|
|
702
|
+
*/
|
|
703
|
+
export declare function defineCollection<const C extends CollectionDefinition>(definition: C & CollectionDefinition): C;
|
|
704
|
+
export type CollectionFieldData<C extends CollectionDefinition> = FieldSetData<C['fields']>;
|
|
705
|
+
export type CollectionFieldDataAllLocales<C extends CollectionDefinition> = FieldSetDataAllLocales<C['fields']>;
|
|
706
|
+
/**
|
|
707
|
+
* Type-safe factory for creating a Block. Returns the definition as-is,
|
|
708
|
+
* but locks in literal types for `blockType`, field names, and select
|
|
709
|
+
* option values — so `BlockFieldData<typeof MyBlock>` and the
|
|
710
|
+
* `_type: B['blockType']` discriminant resolve precisely. Replaces the
|
|
711
|
+
* `as const satisfies Block` pattern.
|
|
712
|
+
*/
|
|
713
|
+
export declare function defineBlock<const B extends Block>(definition: B & Block): B;
|
|
714
|
+
/**
|
|
715
|
+
* Field-only data shape inferred from a block schema. The counterpart
|
|
716
|
+
* to `CollectionFieldData<C>` — use this when you want just the
|
|
717
|
+
* editable fields (e.g. for forms or block-internal helpers).
|
|
718
|
+
*/
|
|
719
|
+
export type BlockFieldData<B extends Block> = FieldSetData<B['fields']>;
|
|
720
|
+
export type BlockFieldDataAllLocales<B extends Block> = FieldSetDataAllLocales<B['fields']>;
|
|
721
|
+
/**
|
|
722
|
+
* Full block instance shape as it appears inside a document tree:
|
|
723
|
+
* the field data plus the synthetic `_id` / `_type` discriminants
|
|
724
|
+
* written by the storage layer. Use this as the prop type for block
|
|
725
|
+
* renderers — `_type` lets a `switch (block._type)` exhaustively narrow
|
|
726
|
+
* across a `BlocksUnion<typeof MyBlocks>`.
|
|
727
|
+
*/
|
|
728
|
+
export type BlockData<B extends Block> = Prettify<{
|
|
729
|
+
_id: string;
|
|
730
|
+
_type: B['blockType'];
|
|
731
|
+
} & BlockFieldData<B>>;
|
|
732
|
+
/**
|
|
733
|
+
* Discriminated union of `BlockData<B>` over a tuple of block
|
|
734
|
+
* definitions. Drives exhaustive switching in `RenderBlocks`-style
|
|
735
|
+
* renderers.
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* ```ts
|
|
739
|
+
* const Blocks = [PhotoBlock, RichTextBlock] as const
|
|
740
|
+
* type AnyBlock = BlocksUnion<typeof Blocks>
|
|
741
|
+
* // → BlockData<typeof PhotoBlock> | BlockData<typeof RichTextBlock>
|
|
742
|
+
* ```
|
|
743
|
+
*/
|
|
744
|
+
export type BlocksUnion<Bs extends readonly Block[]> = Bs[number] extends infer B ? B extends Block ? BlockData<B> : never : never;
|
|
745
|
+
export type CollectionData<C extends CollectionDefinition> = Prettify<{
|
|
746
|
+
document_id: string;
|
|
747
|
+
document_version_id: string;
|
|
748
|
+
status: string;
|
|
749
|
+
created_at: Date;
|
|
750
|
+
updated_at: Date;
|
|
751
|
+
} & CollectionFieldData<C>>;
|
|
752
|
+
export type CollectionDataAllLocales<C extends CollectionDefinition> = Prettify<{
|
|
753
|
+
document_id: string;
|
|
754
|
+
document_version_id: string;
|
|
755
|
+
status: string;
|
|
756
|
+
created_at: Date;
|
|
757
|
+
updated_at: Date;
|
|
758
|
+
} & CollectionFieldDataAllLocales<C>>;
|
|
759
|
+
/**
|
|
760
|
+
* A field definition with all function-valued properties stripped.
|
|
761
|
+
*
|
|
762
|
+
* Safe for JSON serialization — use for API responses, SSR loader return
|
|
763
|
+
* values, RSC props, mobile clients, and CLI introspection. The following
|
|
764
|
+
* are omitted:
|
|
765
|
+
* - `validate` — client UI concern, cannot cross a network boundary
|
|
766
|
+
* - `hooks` — field-level hooks are always functions
|
|
767
|
+
* - `defaultValue` — only literal (non-function) defaults are preserved
|
|
768
|
+
*
|
|
769
|
+
* Nested `fields` (composite / array / blocks) are recursively serialized.
|
|
770
|
+
*/
|
|
771
|
+
export type SerializableField = Omit<Field, 'validate' | 'hooks' | 'defaultValue' | 'fields' | 'blocks'> & {
|
|
772
|
+
/** Only literal defaults are serializable; function defaults are dropped. */
|
|
773
|
+
defaultValue?: Exclude<DefaultValue, (...args: any[]) => any>;
|
|
774
|
+
/** Recursively serializable child fields (group / array). */
|
|
775
|
+
fields?: SerializableField[];
|
|
776
|
+
/** Recursively serializable blocks (blocks field). */
|
|
777
|
+
blocks?: SerializableBlock[];
|
|
778
|
+
};
|
|
779
|
+
/**
|
|
780
|
+
* A block definition with all function-valued properties stripped.
|
|
781
|
+
*/
|
|
782
|
+
export type SerializableBlock = Omit<Block, 'validate' | 'hooks' | 'fields'> & {
|
|
783
|
+
fields: SerializableField[];
|
|
784
|
+
};
|
|
785
|
+
/**
|
|
786
|
+
* A collection definition with all function-valued properties stripped.
|
|
787
|
+
*
|
|
788
|
+
* Safe for JSON serialization — use for API schema endpoints, SSR loaders,
|
|
789
|
+
* RSC components, mobile clients, and any context where the full definition
|
|
790
|
+
* (with live functions) cannot be transmitted.
|
|
791
|
+
*
|
|
792
|
+
* - Collection-level `hooks` are entirely omitted (all entries are functions).
|
|
793
|
+
* - Field `validate`, `hooks`, and function `defaultValue` are stripped.
|
|
794
|
+
*
|
|
795
|
+
* On the receiving end, resolve the full `CollectionDefinition` from the
|
|
796
|
+
* local config store via `getCollectionDefinition(path)` to regain access
|
|
797
|
+
* to validators, hooks, and computed defaults.
|
|
798
|
+
*/
|
|
799
|
+
export type SerializableCollectionDefinition = Omit<CollectionDefinition, 'hooks' | 'fields'> & {
|
|
800
|
+
fields: SerializableField[];
|
|
801
|
+
};
|
|
802
|
+
/**
|
|
803
|
+
* Strips all function-valued properties from a `CollectionDefinition`,
|
|
804
|
+
* producing a version safe for JSON serialization.
|
|
805
|
+
*
|
|
806
|
+
* @example
|
|
807
|
+
* ```ts
|
|
808
|
+
* // In an API route:
|
|
809
|
+
* return Response.json(toSerializableCollection(collectionDef))
|
|
810
|
+
*
|
|
811
|
+
* // In an SSR loader:
|
|
812
|
+
* return { schema: toSerializableCollection(collectionDef), document: data }
|
|
813
|
+
* ```
|
|
814
|
+
*/
|
|
815
|
+
export declare function toSerializableCollection(def: CollectionDefinition): SerializableCollectionDefinition;
|
|
816
|
+
//# sourceMappingURL=collection-types.d.ts.map
|