@beechcms/core 0.6.0-preview.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/automations/automations-grammar-words.d.ts.map +1 -0
- package/dist/automations/automations.repository.interface.d.ts.map +1 -0
- package/dist/automations/automations.runner.interface.d.ts.map +1 -0
- package/dist/automations/automations.types.d.ts.map +1 -0
- package/dist/common/clock.d.ts.map +1 -0
- package/dist/common/email.provider.d.ts +32 -0
- package/dist/common/email.provider.d.ts.map +1 -0
- package/dist/common/email.provider.js +4 -0
- package/dist/{hooks.d.ts → common/hooks.d.ts} +2 -2
- package/dist/common/hooks.d.ts.map +1 -0
- package/dist/common/id-generator.d.ts.map +1 -0
- package/dist/common/storage.d.ts.map +1 -0
- package/dist/{content-scan.repository.d.ts → content/content-scan.repository.d.ts} +1 -1
- package/dist/content/content-scan.repository.d.ts.map +1 -0
- package/dist/{content.repository.d.ts → content/content.repository.d.ts} +13 -4
- package/dist/content/content.repository.d.ts.map +1 -0
- package/dist/content/demo-data.repository.d.ts.map +1 -0
- package/dist/content/idempotency.repository.d.ts.map +1 -0
- package/dist/content/richtext/richtext-render.d.ts +12 -0
- package/dist/content/richtext/richtext-render.d.ts.map +1 -0
- package/dist/{richtext-render.js → content/richtext/richtext-render.js} +7 -14
- package/dist/content/richtext/richtext.d.ts.map +1 -0
- package/dist/{seed.repository.d.ts → content/seed.repository.d.ts} +1 -1
- package/dist/content/seed.repository.d.ts.map +1 -0
- package/dist/content/slug-utils.d.ts.map +1 -0
- package/dist/{dashboard-layout.d.ts → dashboard-layout/dashboard-layout.d.ts} +1 -1
- package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -0
- package/dist/dashboard-layout/dashboard-layout.repository.d.ts.map +1 -0
- package/dist/dashboard-layout/dashboard-permissions.d.ts.map +1 -0
- package/dist/dashboard-layout/dashboard-scopes.d.ts.map +1 -0
- package/dist/dashboard-layout/kanban/kanban-position.repository.d.ts +19 -0
- package/dist/dashboard-layout/kanban/kanban-position.repository.d.ts.map +1 -0
- package/dist/dashboard-layout/kanban/kanban.d.ts +75 -0
- package/dist/dashboard-layout/kanban/kanban.d.ts.map +1 -0
- package/dist/dashboard-layout/kanban/kanban.js +82 -0
- package/dist/dashboard-layout/layout-permissions.d.ts.map +1 -0
- package/dist/{seed-layout.d.ts → dashboard-layout/seed-layout.d.ts} +77 -3
- package/dist/dashboard-layout/seed-layout.d.ts.map +1 -0
- package/dist/{seed-layout.js → dashboard-layout/seed-layout.js} +101 -8
- package/dist/{seed-layout.repository.d.ts → dashboard-layout/seed-layout.repository.d.ts} +5 -1
- package/dist/dashboard-layout/seed-layout.repository.d.ts.map +1 -0
- package/dist/dashboard-layout/view-authorization.d.ts +18 -0
- package/dist/dashboard-layout/view-authorization.d.ts.map +1 -0
- package/dist/dashboard-layout/view-authorization.js +28 -0
- package/dist/engine/ddl.d.ts +173 -0
- package/dist/engine/ddl.d.ts.map +1 -0
- package/dist/engine/ddl.js +454 -0
- package/dist/engine/define-seed.d.ts.map +1 -0
- package/dist/engine/engine.d.ts +4 -0
- package/dist/engine/engine.d.ts.map +1 -0
- package/dist/engine/engine.js +4 -0
- package/dist/engine/policies.d.ts.map +1 -0
- package/dist/engine/query.d.ts +23 -0
- package/dist/engine/query.d.ts.map +1 -0
- package/dist/engine/query.js +208 -0
- package/dist/engine/relations.d.ts.map +1 -0
- package/dist/{schema-mutator.d.ts → engine/schema-mutator.d.ts} +4 -0
- package/dist/engine/schema-mutator.d.ts.map +1 -0
- package/dist/engine/seed-ddl-destructive.d.ts.map +1 -0
- package/dist/engine/seed-ddl.d.ts.map +1 -0
- package/dist/engine/seed-registry.d.ts.map +1 -0
- package/dist/{seed-registry.js → engine/seed-registry.js} +1 -1
- package/dist/engine/seed-types-generator.d.ts +10 -0
- package/dist/engine/seed-types-generator.d.ts.map +1 -0
- package/dist/engine/seed-types-generator.js +86 -0
- package/dist/engine/seed-validation.d.ts.map +1 -0
- package/dist/{seed-validation.js → engine/seed-validation.js} +14 -1
- package/dist/engine/seeds.d.ts.map +1 -0
- package/dist/engine/serialize.d.ts +20 -0
- package/dist/engine/serialize.d.ts.map +1 -0
- package/dist/engine/serialize.js +160 -0
- package/dist/{types.d.ts → engine/types.d.ts} +102 -63
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/validation/cache.d.ts +12 -0
- package/dist/engine/validation/cache.d.ts.map +1 -0
- package/dist/engine/validation/cache.js +115 -0
- package/dist/engine/validation/file-branch.d.ts +48 -0
- package/dist/engine/validation/file-branch.d.ts.map +1 -0
- package/dist/engine/validation/file-branch.js +127 -0
- package/dist/engine/validation/index.d.ts +115 -0
- package/dist/engine/validation/index.d.ts.map +1 -0
- package/dist/engine/validation/index.js +333 -0
- package/dist/engine/validation/primitives.d.ts +33 -0
- package/dist/engine/validation/primitives.d.ts.map +1 -0
- package/dist/engine/validation/primitives.js +43 -0
- package/dist/engine/validation/richtext-sanitizer.d.ts +30 -0
- package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -0
- package/dist/engine/validation/richtext-sanitizer.js +193 -0
- package/dist/engine/validation/schema-builders.d.ts +12 -0
- package/dist/engine/validation/schema-builders.d.ts.map +1 -0
- package/dist/engine/validation/schema-builders.js +322 -0
- package/dist/index.d.ts +48 -44
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -44
- package/dist/media/file-types.d.ts +13 -0
- package/dist/media/file-types.d.ts.map +1 -0
- package/dist/media/media.repository.d.ts.map +1 -0
- package/dist/queue/queue.interface.d.ts +39 -0
- package/dist/queue/queue.interface.d.ts.map +1 -0
- package/dist/queue/queue.stub.d.ts +6 -0
- package/dist/queue/queue.stub.d.ts.map +1 -0
- package/dist/queue/queue.stub.js +8 -0
- package/dist/queue/scheduler.interface.d.ts.map +1 -0
- package/dist/queue/scheduler.interface.js +3 -0
- package/dist/queue/scheduler.stub.d.ts.map +1 -0
- package/dist/queue.interface.d.ts +4 -4
- package/dist/queue.interface.d.ts.map +1 -1
- package/dist/queue.stub.d.ts.map +1 -1
- package/dist/rate-limit/rate-limiter.d.ts +5 -0
- package/dist/rate-limit/rate-limiter.d.ts.map +1 -1
- package/dist/search/search.repository.d.ts +1 -1
- package/dist/search/search.repository.d.ts.map +1 -1
- package/dist/seed-types-generator.d.ts +1 -1
- package/dist/seed-types-generator.d.ts.map +1 -1
- package/dist/settings/site-settings.repository.d.ts.map +1 -0
- package/dist/settings/site-settings.repository.js +3 -0
- package/dist/webhooks/webhook-crypto.d.ts +8 -0
- package/dist/webhooks/webhook-crypto.d.ts.map +1 -0
- package/dist/webhooks/webhook-crypto.js +37 -0
- package/dist/webhooks/webhook-validation.d.ts.map +1 -0
- package/dist/widget/widget.repository.d.ts +1 -1
- package/dist/widget/widget.repository.d.ts.map +1 -1
- package/package.json +4 -3
- package/dist/automations-grammar-words.d.ts.map +0 -1
- package/dist/automations.repository.interface.d.ts.map +0 -1
- package/dist/automations.runner.interface.d.ts.map +0 -1
- package/dist/automations.runner.stub.d.ts +0 -5
- package/dist/automations.runner.stub.d.ts.map +0 -1
- package/dist/automations.runner.stub.js +0 -7
- package/dist/automations.types.d.ts.map +0 -1
- package/dist/clock.d.ts.map +0 -1
- package/dist/content-scan.repository.d.ts.map +0 -1
- package/dist/content.repository.d.ts.map +0 -1
- package/dist/dashboard-layout.d.ts.map +0 -1
- package/dist/dashboard-layout.repository.d.ts.map +0 -1
- package/dist/dashboard-permissions.d.ts.map +0 -1
- package/dist/dashboard-scopes.d.ts.map +0 -1
- package/dist/define-seed.d.ts.map +0 -1
- package/dist/demo-data.repository.d.ts.map +0 -1
- package/dist/engine.d.ts +0 -144
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -723
- package/dist/file-types.d.ts +0 -13
- package/dist/file-types.d.ts.map +0 -1
- package/dist/hooks.d.ts.map +0 -1
- package/dist/id-generator.d.ts.map +0 -1
- package/dist/idempotency.repository.d.ts.map +0 -1
- package/dist/layout-permissions.d.ts.map +0 -1
- package/dist/media.repository.d.ts.map +0 -1
- package/dist/policies.d.ts.map +0 -1
- package/dist/relations.d.ts.map +0 -1
- package/dist/richtext-render.d.ts +0 -11
- package/dist/richtext-render.d.ts.map +0 -1
- package/dist/richtext.d.ts.map +0 -1
- package/dist/scheduler.interface.d.ts.map +0 -1
- package/dist/scheduler.stub.d.ts.map +0 -1
- package/dist/schema-mutator.d.ts.map +0 -1
- package/dist/seed-ddl-destructive.d.ts.map +0 -1
- package/dist/seed-ddl.d.ts.map +0 -1
- package/dist/seed-layout.d.ts.map +0 -1
- package/dist/seed-layout.repository.d.ts.map +0 -1
- package/dist/seed-registry.d.ts.map +0 -1
- package/dist/seed-validation.d.ts.map +0 -1
- package/dist/seed.repository.d.ts.map +0 -1
- package/dist/seeds.d.ts.map +0 -1
- package/dist/site-settings.repository.d.ts.map +0 -1
- package/dist/slug-utils.d.ts.map +0 -1
- package/dist/storage.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/validation.d.ts +0 -50
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -659
- package/dist/webhook-validation.d.ts.map +0 -1
- /package/dist/{automations-grammar-words.d.ts → automations/automations-grammar-words.d.ts} +0 -0
- /package/dist/{automations-grammar-words.js → automations/automations-grammar-words.js} +0 -0
- /package/dist/{automations.repository.interface.d.ts → automations/automations.repository.interface.d.ts} +0 -0
- /package/dist/{automations.repository.interface.js → automations/automations.repository.interface.js} +0 -0
- /package/dist/{automations.runner.interface.d.ts → automations/automations.runner.interface.d.ts} +0 -0
- /package/dist/{automations.runner.interface.js → automations/automations.runner.interface.js} +0 -0
- /package/dist/{automations.types.d.ts → automations/automations.types.d.ts} +0 -0
- /package/dist/{automations.types.js → automations/automations.types.js} +0 -0
- /package/dist/{clock.d.ts → common/clock.d.ts} +0 -0
- /package/dist/{clock.js → common/clock.js} +0 -0
- /package/dist/{hooks.js → common/hooks.js} +0 -0
- /package/dist/{id-generator.d.ts → common/id-generator.d.ts} +0 -0
- /package/dist/{id-generator.js → common/id-generator.js} +0 -0
- /package/dist/{storage.d.ts → common/storage.d.ts} +0 -0
- /package/dist/{storage.js → common/storage.js} +0 -0
- /package/dist/{content-scan.repository.js → content/content-scan.repository.js} +0 -0
- /package/dist/{content.repository.js → content/content.repository.js} +0 -0
- /package/dist/{demo-data.repository.d.ts → content/demo-data.repository.d.ts} +0 -0
- /package/dist/{demo-data.repository.js → content/demo-data.repository.js} +0 -0
- /package/dist/{idempotency.repository.d.ts → content/idempotency.repository.d.ts} +0 -0
- /package/dist/{idempotency.repository.js → content/idempotency.repository.js} +0 -0
- /package/dist/{richtext.d.ts → content/richtext/richtext.d.ts} +0 -0
- /package/dist/{richtext.js → content/richtext/richtext.js} +0 -0
- /package/dist/{seed.repository.js → content/seed.repository.js} +0 -0
- /package/dist/{slug-utils.d.ts → content/slug-utils.d.ts} +0 -0
- /package/dist/{slug-utils.js → content/slug-utils.js} +0 -0
- /package/dist/{dashboard-layout.js → dashboard-layout/dashboard-layout.js} +0 -0
- /package/dist/{dashboard-layout.repository.d.ts → dashboard-layout/dashboard-layout.repository.d.ts} +0 -0
- /package/dist/{dashboard-layout.repository.js → dashboard-layout/dashboard-layout.repository.js} +0 -0
- /package/dist/{dashboard-permissions.d.ts → dashboard-layout/dashboard-permissions.d.ts} +0 -0
- /package/dist/{dashboard-permissions.js → dashboard-layout/dashboard-permissions.js} +0 -0
- /package/dist/{dashboard-scopes.d.ts → dashboard-layout/dashboard-scopes.d.ts} +0 -0
- /package/dist/{dashboard-scopes.js → dashboard-layout/dashboard-scopes.js} +0 -0
- /package/dist/{media.repository.js → dashboard-layout/kanban/kanban-position.repository.js} +0 -0
- /package/dist/{layout-permissions.d.ts → dashboard-layout/layout-permissions.d.ts} +0 -0
- /package/dist/{layout-permissions.js → dashboard-layout/layout-permissions.js} +0 -0
- /package/dist/{seed-layout.repository.js → dashboard-layout/seed-layout.repository.js} +0 -0
- /package/dist/{define-seed.d.ts → engine/define-seed.d.ts} +0 -0
- /package/dist/{define-seed.js → engine/define-seed.js} +0 -0
- /package/dist/{policies.d.ts → engine/policies.d.ts} +0 -0
- /package/dist/{policies.js → engine/policies.js} +0 -0
- /package/dist/{relations.d.ts → engine/relations.d.ts} +0 -0
- /package/dist/{relations.js → engine/relations.js} +0 -0
- /package/dist/{schema-mutator.js → engine/schema-mutator.js} +0 -0
- /package/dist/{seed-ddl-destructive.d.ts → engine/seed-ddl-destructive.d.ts} +0 -0
- /package/dist/{seed-ddl-destructive.js → engine/seed-ddl-destructive.js} +0 -0
- /package/dist/{seed-ddl.d.ts → engine/seed-ddl.d.ts} +0 -0
- /package/dist/{seed-ddl.js → engine/seed-ddl.js} +0 -0
- /package/dist/{seed-registry.d.ts → engine/seed-registry.d.ts} +0 -0
- /package/dist/{seed-validation.d.ts → engine/seed-validation.d.ts} +0 -0
- /package/dist/{seeds.d.ts → engine/seeds.d.ts} +0 -0
- /package/dist/{seeds.js → engine/seeds.js} +0 -0
- /package/dist/{types.js → engine/types.js} +0 -0
- /package/dist/{file-types.js → media/file-types.js} +0 -0
- /package/dist/{media.repository.d.ts → media/media.repository.d.ts} +0 -0
- /package/dist/{scheduler.interface.js → media/media.repository.js} +0 -0
- /package/dist/{site-settings.repository.js → queue/queue.interface.js} +0 -0
- /package/dist/{scheduler.interface.d.ts → queue/scheduler.interface.d.ts} +0 -0
- /package/dist/{scheduler.stub.d.ts → queue/scheduler.stub.d.ts} +0 -0
- /package/dist/{scheduler.stub.js → queue/scheduler.stub.js} +0 -0
- /package/dist/{site-settings.repository.d.ts → settings/site-settings.repository.d.ts} +0 -0
- /package/dist/{webhook-validation.d.ts → webhooks/webhook-validation.d.ts} +0 -0
- /package/dist/{webhook-validation.js → webhooks/webhook-validation.js} +0 -0
|
@@ -1,47 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @module Types
|
|
3
|
+
* Core schema types for the Botanical Engine: `Seed` (content type definition),
|
|
4
|
+
* `Branch` (field definition), and the query types consumed by `buildSelectQuery`.
|
|
5
|
+
* Pure data shapes — no runtime logic lives here.
|
|
6
|
+
*/
|
|
7
|
+
import type { FileAccept } from '../media/file-types.js';
|
|
8
|
+
import type { DashboardView } from '../dashboard-layout/view-authorization.js';
|
|
9
|
+
/** All supported field value types for a Branch. */
|
|
2
10
|
export type BranchType = 'text' | 'number' | 'boolean' | 'json' | 'date' | 'richtext' | 'file' | 'tags' | 'relation' | 'repeater';
|
|
3
|
-
/**
|
|
11
|
+
/** Specialized configuration for a branch of type 'number'. */
|
|
4
12
|
export interface NumberFieldOptions {
|
|
5
|
-
/**
|
|
13
|
+
/** Visual display style. Default: 'decimal'. */
|
|
6
14
|
format?: 'decimal' | 'currency' | 'percentage' | 'compact';
|
|
7
|
-
/**
|
|
15
|
+
/** ISO 4217 currency code (required when format === 'currency'). E.g. 'EUR', 'USD'. */
|
|
8
16
|
currency?: string;
|
|
9
|
-
/**
|
|
17
|
+
/** Custom text prepended to the formatted value. */
|
|
10
18
|
prefix?: string;
|
|
11
|
-
/**
|
|
19
|
+
/** Custom unit appended to the formatted value (e.g. 'kg', 'm²'). */
|
|
12
20
|
suffix?: string;
|
|
13
|
-
/**
|
|
21
|
+
/** Explicit number of decimal digits to display and enforce. */
|
|
14
22
|
decimals?: number;
|
|
15
|
-
/**
|
|
23
|
+
/** Enable/disable thousands separator grouping. Default: true. */
|
|
16
24
|
grouping?: boolean;
|
|
17
|
-
/**
|
|
25
|
+
/** Alternative input mechanism for the entry editor. Default: 'input'. */
|
|
18
26
|
control?: 'input' | 'slider' | 'rating' | 'stepper';
|
|
19
|
-
/**
|
|
27
|
+
/** Minimum allowed value. */
|
|
20
28
|
min?: number;
|
|
21
|
-
/**
|
|
29
|
+
/** Maximum allowed value. */
|
|
22
30
|
max?: number;
|
|
23
|
-
/**
|
|
31
|
+
/** Value increment (e.g. 1 for strict integers, 0.5 for half-steps). */
|
|
24
32
|
step?: number;
|
|
25
33
|
}
|
|
26
|
-
/**
|
|
34
|
+
/** Specialized configuration for a branch of type 'file'. */
|
|
27
35
|
export interface FileFieldOptions {
|
|
28
36
|
/**
|
|
29
|
-
*
|
|
30
|
-
* - 'image':
|
|
37
|
+
* Semantic type of file accepted.
|
|
38
|
+
* - 'image': renderable images with preview
|
|
31
39
|
* - 'document': PDF/Office/text
|
|
32
|
-
* - 'any':
|
|
40
|
+
* - 'any': any file (default — UI shows a generic icon, no image render attempt)
|
|
33
41
|
* Default: 'any'.
|
|
34
42
|
*/
|
|
35
43
|
accept?: FileAccept;
|
|
36
44
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
45
|
+
* Maximum size of a single file, in bytes.
|
|
46
|
+
* NOTE: the backend /upload endpoint enforces the global MAX_FILE_SIZE_BYTES (5MB) —
|
|
47
|
+
* this field is informational for the UI only; it is not enforced on upload.
|
|
40
48
|
* Default: 5_242_880.
|
|
41
49
|
*/
|
|
42
50
|
maxSize?: number;
|
|
43
51
|
}
|
|
44
|
-
/** Branch:
|
|
52
|
+
/** Branch: definition of a single field. `alias` is the SQL column name. */
|
|
45
53
|
export interface Branch {
|
|
46
54
|
/**
|
|
47
55
|
* Stable logical id of this branch, e.g. 'br_01', 'br_title'.
|
|
@@ -53,55 +61,55 @@ export interface Branch {
|
|
|
53
61
|
* The Botanical Engine still emits alias as the SQL column name; id is a logical handle.
|
|
54
62
|
*/
|
|
55
63
|
id: string;
|
|
56
|
-
/**
|
|
64
|
+
/** Human-readable alias, used in the API payload and as the SQL column name in the dedicated table. */
|
|
57
65
|
alias: string;
|
|
58
|
-
/**
|
|
66
|
+
/** UI display label. */
|
|
59
67
|
label: string;
|
|
60
|
-
/**
|
|
68
|
+
/** Optional help text shown as a tooltip next to the label in the form. UI-only, ignored by the engine. */
|
|
61
69
|
hint?: string;
|
|
62
|
-
/**
|
|
70
|
+
/** Value type. */
|
|
63
71
|
type: BranchType;
|
|
64
72
|
/**
|
|
65
|
-
*
|
|
66
|
-
* `asset-list`
|
|
73
|
+
* Optional semantic variant of the field for UI/validation purposes.
|
|
74
|
+
* `asset-list` on a multiple `file` branch enables gallery management.
|
|
67
75
|
*/
|
|
68
76
|
format?: 'plain' | 'markdown' | 'html' | 'date' | 'datetime' | 'asset-list';
|
|
69
77
|
/**
|
|
70
|
-
*
|
|
71
|
-
* - false/undefined:
|
|
72
|
-
* - true:
|
|
78
|
+
* Optional cardinality for media fields:
|
|
79
|
+
* - false/undefined: single asset (string URL)
|
|
80
|
+
* - true: asset list (string[] URL)
|
|
73
81
|
*/
|
|
74
82
|
multiple?: boolean;
|
|
75
83
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
84
|
+
* Predefined vocabulary for tag/select/multiselect fields.
|
|
85
|
+
* Static list defined in the Seed (not persisted to the DB).
|
|
78
86
|
*/
|
|
79
87
|
options?: string[];
|
|
80
|
-
/**
|
|
88
|
+
/** Required on create — generates NOT NULL in generateCreateTable. */
|
|
81
89
|
requiredOnCreate?: boolean;
|
|
82
|
-
/**
|
|
90
|
+
/** Required on update. */
|
|
83
91
|
requiredOnUpdate?: boolean;
|
|
84
92
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
93
|
+
* Access and handling policy for the field.
|
|
94
|
+
* All values are optional — `resolvePolicies(branch)` supplies the defaults.
|
|
87
95
|
*/
|
|
88
96
|
policies?: {
|
|
89
|
-
/**
|
|
97
|
+
/** How the value is stored. Default: 'plain'. */
|
|
90
98
|
privacy?: 'plain' | 'hash' | 'encrypt';
|
|
91
|
-
/**
|
|
99
|
+
/** How the value is returned in API responses. Default: 'full'. */
|
|
92
100
|
visibility?: 'full' | 'masked' | 'hidden';
|
|
93
|
-
/**
|
|
101
|
+
/** Whether the field is included in full-text search queries. Default: true. */
|
|
94
102
|
search?: boolean;
|
|
95
|
-
/**
|
|
103
|
+
/** Whether the field is available as a filter column in the dashboard. Default: true. */
|
|
96
104
|
filter?: boolean;
|
|
97
|
-
/**
|
|
105
|
+
/** Whether the field is available as a sort column in the dashboard. Default: true. */
|
|
98
106
|
sort?: boolean;
|
|
99
|
-
/**
|
|
107
|
+
/** Whether the field is included in Public API responses. Default: true. */
|
|
100
108
|
public?: boolean;
|
|
101
109
|
};
|
|
102
|
-
/**
|
|
110
|
+
/** Advanced options for number fields. Ignored if type !== 'number'. */
|
|
103
111
|
numberOptions?: NumberFieldOptions;
|
|
104
|
-
/**
|
|
112
|
+
/** Advanced options for file fields. Ignored if type !== 'file'. */
|
|
105
113
|
fileOptions?: FileFieldOptions;
|
|
106
114
|
/**
|
|
107
115
|
* Slug of the referenced Seed (without the `content_` prefix).
|
|
@@ -149,13 +157,13 @@ export interface Branch {
|
|
|
149
157
|
}
|
|
150
158
|
/** Dashboard-specific config embedded in a Seed. All fields optional — defaults applied by the dashboard. */
|
|
151
159
|
export interface DashboardSeedConfig {
|
|
152
|
-
/** Lucide icon name (string, resolved to component client-side). Default: 'Folder' */
|
|
160
|
+
/** Lucide icon name (string, resolved to component client-side). Default: 'Folder'. */
|
|
153
161
|
icon?: string;
|
|
154
162
|
/** Sidebar group label. Ungrouped seeds share a single 'Contents' section. */
|
|
155
163
|
group?: string;
|
|
156
|
-
/** Sort order within the group. Lower = higher. Default: 99 */
|
|
164
|
+
/** Sort order within the group. Lower = higher. Default: 99. */
|
|
157
165
|
order?: number;
|
|
158
|
-
/** Hide from sidebar navigation. Default: false */
|
|
166
|
+
/** Hide from sidebar navigation. Default: false. */
|
|
159
167
|
hidden?: boolean;
|
|
160
168
|
/** Tooltip description shown in the sidebar. */
|
|
161
169
|
description?: string;
|
|
@@ -166,33 +174,39 @@ export interface DashboardSeedConfig {
|
|
|
166
174
|
export?: boolean;
|
|
167
175
|
bulkDelete?: boolean;
|
|
168
176
|
};
|
|
177
|
+
/**
|
|
178
|
+
* Views authorized for this seed in the content manager. When omitted,
|
|
179
|
+
* the dashboard falls back to DEFAULT_AUTHORIZED_VIEWS. 'table' is always
|
|
180
|
+
* guaranteed at read time by resolveAuthorizedViews (universal fallback).
|
|
181
|
+
*/
|
|
182
|
+
views?: DashboardView[];
|
|
169
183
|
}
|
|
170
|
-
/** Seed:
|
|
184
|
+
/** Seed: schema definition of a content type. */
|
|
171
185
|
export interface Seed {
|
|
172
|
-
/**
|
|
186
|
+
/** Identifying slug — also the table name: `content_{slug}`. */
|
|
173
187
|
slug: string;
|
|
174
|
-
/**
|
|
188
|
+
/** Singular UI label. */
|
|
175
189
|
label: string;
|
|
176
|
-
/**
|
|
190
|
+
/** Plural UI label. Falls back to `label` when absent. */
|
|
177
191
|
labelPlural?: string;
|
|
178
|
-
/**
|
|
192
|
+
/** Enable reads from the Public API (`GET /api/v1/public/:seed`). Default: false. */
|
|
179
193
|
allowPublicRead?: boolean;
|
|
180
|
-
/**
|
|
194
|
+
/** Enable creation from the Public API (`POST /api/v1/public/:seed/add`). Default: false. */
|
|
181
195
|
allowPublicPost?: boolean;
|
|
182
|
-
/**
|
|
196
|
+
/** Enable edits from the Public API (`PUT /api/v1/public/:seed/edit/:id`). Default: false. */
|
|
183
197
|
allowPublicEdit?: boolean;
|
|
184
198
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* Default: false
|
|
199
|
+
* Enables the "pending draft" feature for this seed.
|
|
200
|
+
* When true, generates the `content_{slug}_drafts` table and enables the `/draft` endpoints.
|
|
201
|
+
* Default: false.
|
|
188
202
|
*/
|
|
189
203
|
allowDrafts?: boolean;
|
|
190
204
|
/**
|
|
191
|
-
* Alias
|
|
192
|
-
*
|
|
205
|
+
* Alias of the branch used as the entry's human-readable name (e.g. "title", "name", "author").
|
|
206
|
+
* Required — UIs use it for display without heuristics.
|
|
193
207
|
*/
|
|
194
208
|
displayNameAlias: string;
|
|
195
|
-
/**
|
|
209
|
+
/** List of fields (Branch). */
|
|
196
210
|
branches: Branch[];
|
|
197
211
|
/** Optional dashboard-specific UI config. Ignored by the Botanical Engine. */
|
|
198
212
|
dashboard?: DashboardSeedConfig;
|
|
@@ -201,37 +215,62 @@ export interface Seed {
|
|
|
201
215
|
* Type matches FormLayout from seed-layout.ts — kept as unknown here to avoid circular imports. */
|
|
202
216
|
layout?: unknown;
|
|
203
217
|
}
|
|
218
|
+
/** Comparison/matching operator applied by a filter condition. */
|
|
204
219
|
export type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty' | 'in' | 'not_in' | 'has_tag' | 'has_any_tag' | 'has_all_tags';
|
|
220
|
+
/** Declared value type of a filtered column, used to normalize/coerce filter values before binding. */
|
|
205
221
|
export type FilterType = 'text' | 'number' | 'date' | 'boolean' | 'tags' | 'select' | 'system' | 'json';
|
|
222
|
+
/** A single operator + value pair applied to a FilterGroup's column. */
|
|
206
223
|
export interface FilterCondition {
|
|
224
|
+
/** Operator to apply. */
|
|
207
225
|
op: FilterOperator;
|
|
226
|
+
/** Comparison value(s). Arrays are only meaningful for `in`/`not_in`/`has_any_tag`/`has_all_tags`. */
|
|
208
227
|
value: string | number | boolean | null | string[] | number[];
|
|
209
228
|
}
|
|
210
229
|
export interface FilterGroup {
|
|
211
|
-
/**
|
|
230
|
+
/** Column name: system column (id/slug/status/created_at/updated_at) or branch alias. */
|
|
212
231
|
column: string;
|
|
232
|
+
/** Declared type of the column, used to normalize condition values. */
|
|
213
233
|
type: FilterType;
|
|
234
|
+
/** Conditions applied to this column, ANDed together. */
|
|
214
235
|
conditions: FilterCondition[];
|
|
215
236
|
}
|
|
216
237
|
export interface SelectOptions {
|
|
238
|
+
/** Filter groups, ANDed together. */
|
|
217
239
|
filters?: FilterGroup[];
|
|
240
|
+
/** Sort column and direction. Ignored when `kanbanOrder` is set. */
|
|
218
241
|
orderBy?: {
|
|
219
242
|
column: string;
|
|
220
243
|
dir: 'ASC' | 'DESC';
|
|
221
244
|
};
|
|
245
|
+
/** LIMIT/OFFSET pagination. */
|
|
222
246
|
pagination?: {
|
|
223
247
|
limit: number;
|
|
224
248
|
offset: number;
|
|
225
249
|
};
|
|
226
|
-
/**
|
|
250
|
+
/** Filters by status. null = no status filter. */
|
|
227
251
|
status?: string | null;
|
|
228
|
-
/** Full-text search —
|
|
252
|
+
/** Full-text search — uses FTS5 if the seed has indexable richtext/text branches. */
|
|
229
253
|
search?: string;
|
|
230
|
-
/**
|
|
254
|
+
/** Column projection. Empty = SELECT *. */
|
|
231
255
|
fields?: string[];
|
|
256
|
+
/** When set, LEFT JOIN kanban_positions and order by fractional index (KB-S04c/S05).
|
|
257
|
+
* Mutually exclusive with `orderBy`; if both present, `kanbanOrder` wins. */
|
|
258
|
+
kanbanOrder?: {
|
|
259
|
+
seedSlug: string;
|
|
260
|
+
axisBranchId: string;
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* When true, generates a COUNT(*) query instead of fetching rows.
|
|
264
|
+
* This omits projections, sorting/ordering clauses, and pagination limits/offsets,
|
|
265
|
+
* while keeping the join/where clauses intact for accurate counts.
|
|
266
|
+
*/
|
|
267
|
+
isCount?: boolean;
|
|
232
268
|
}
|
|
269
|
+
/** SQL string paired with its ordered parameter bindings, ready for a D1 `.bind(...)` call. */
|
|
233
270
|
export interface ParameterizedQuery {
|
|
271
|
+
/** Parameterized SQL statement, using `?` placeholders. */
|
|
234
272
|
sql: string;
|
|
273
|
+
/** Values bound to the `?` placeholders, in order. */
|
|
235
274
|
bindings: (string | number | boolean | null)[];
|
|
236
275
|
}
|
|
237
276
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Seed } from '../types.js';
|
|
3
|
+
import type { ResolvedOptions } from './index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Compiles or retrieves from cache the full Zod validation schema for a seed structure.
|
|
6
|
+
*
|
|
7
|
+
* @param seed - The seed definition.
|
|
8
|
+
* @param options - The resolved validation options.
|
|
9
|
+
* @returns The compiled strict Zod object schema.
|
|
10
|
+
*/
|
|
11
|
+
export declare function compileSeedSchema(seed: Seed, options: ResolvedOptions): z.ZodObject<Record<string, z.ZodTypeAny>>;
|
|
12
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/cache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAsB,IAAI,EAAwC,MAAM,aAAa,CAAA;AACjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAoHjD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CA2BjH"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { schemaForBranch } from './schema-builders.js';
|
|
5
|
+
/** Max entries kept in {@link seedSchemaCache} before evicting the least recently used. */
|
|
6
|
+
const SEED_SCHEMA_CACHE_MAX_SIZE = 150;
|
|
7
|
+
/** Cache storing compiled Zod schemas for seeds to optimize validation runs. */
|
|
8
|
+
const seedSchemaCache = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Retrieves a cache entry and marks it as most recently used.
|
|
11
|
+
*
|
|
12
|
+
* @param key - The cache key.
|
|
13
|
+
* @returns The cached schema, or undefined if absent.
|
|
14
|
+
*/
|
|
15
|
+
function getCachedSchema(key) {
|
|
16
|
+
const cached = seedSchemaCache.get(key);
|
|
17
|
+
if (cached) {
|
|
18
|
+
seedSchemaCache.delete(key);
|
|
19
|
+
seedSchemaCache.set(key, cached);
|
|
20
|
+
}
|
|
21
|
+
return cached;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Stores a cache entry, evicting the least recently used one if over capacity.
|
|
25
|
+
*
|
|
26
|
+
* @param key - The cache key.
|
|
27
|
+
* @param value - The compiled schema to cache.
|
|
28
|
+
*/
|
|
29
|
+
function setCachedSchema(key, value) {
|
|
30
|
+
if (seedSchemaCache.size >= SEED_SCHEMA_CACHE_MAX_SIZE) {
|
|
31
|
+
const oldestKey = seedSchemaCache.keys().next().value;
|
|
32
|
+
if (oldestKey !== undefined)
|
|
33
|
+
seedSchemaCache.delete(oldestKey);
|
|
34
|
+
}
|
|
35
|
+
seedSchemaCache.set(key, value);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Recursively generates a typesafe fingerprint object for a branch and its sub-branches.
|
|
39
|
+
*
|
|
40
|
+
* @param branch - The branch definition.
|
|
41
|
+
* @returns The structured branch fingerprint object.
|
|
42
|
+
*/
|
|
43
|
+
function buildBranchFingerprint(branch) {
|
|
44
|
+
return {
|
|
45
|
+
a: branch.alias,
|
|
46
|
+
t: branch.type,
|
|
47
|
+
f: branch.format ?? null,
|
|
48
|
+
m: branch.multiple === true,
|
|
49
|
+
rc: branch.requiredOnCreate === true,
|
|
50
|
+
ru: branch.requiredOnUpdate === true,
|
|
51
|
+
n: branch.numberOptions ?? null,
|
|
52
|
+
fi: branch.fileOptions ?? null,
|
|
53
|
+
mi: branch.minItems ?? null,
|
|
54
|
+
ma: branch.maxItems ?? null,
|
|
55
|
+
sub: branch.fields?.map(buildBranchFingerprint) ?? null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Generates a unique JSON fingerprint string representing a seed's structure for caching.
|
|
60
|
+
*
|
|
61
|
+
* @param seed - The seed definition.
|
|
62
|
+
* @returns The fingerprint string.
|
|
63
|
+
*/
|
|
64
|
+
function buildSeedFingerprint(seed) {
|
|
65
|
+
const parts = seed.branches.map(buildBranchFingerprint);
|
|
66
|
+
return JSON.stringify({ s: seed.slug, b: parts });
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Builds a cache key for a seed validation schema combining its fingerprint, operation, and options.
|
|
70
|
+
*
|
|
71
|
+
* @param seed - The seed definition.
|
|
72
|
+
* @param options - The resolved validation options.
|
|
73
|
+
* @returns The cache key string.
|
|
74
|
+
*/
|
|
75
|
+
function buildCacheKey(seed, options) {
|
|
76
|
+
return [
|
|
77
|
+
buildSeedFingerprint(seed),
|
|
78
|
+
options.operation,
|
|
79
|
+
options.allowNull ? '1' : '0',
|
|
80
|
+
options.enforceRequiredFields ? '1' : '0',
|
|
81
|
+
String(options.maxTextLength),
|
|
82
|
+
].join('|');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Compiles or retrieves from cache the full Zod validation schema for a seed structure.
|
|
86
|
+
*
|
|
87
|
+
* @param seed - The seed definition.
|
|
88
|
+
* @param options - The resolved validation options.
|
|
89
|
+
* @returns The compiled strict Zod object schema.
|
|
90
|
+
*/
|
|
91
|
+
export function compileSeedSchema(seed, options) {
|
|
92
|
+
// Seeds with relation branches cannot be safely cached because the schema
|
|
93
|
+
// captures the idGenerator by closure, and different generators (e.g.
|
|
94
|
+
// SystemIdGenerator vs SequentialIdGenerator) have different isValid() semantics.
|
|
95
|
+
const hasRelation = seed.branches.some((b) => b.type === 'relation');
|
|
96
|
+
if (!hasRelation) {
|
|
97
|
+
const key = buildCacheKey(seed, options);
|
|
98
|
+
const cached = getCachedSchema(key);
|
|
99
|
+
if (cached)
|
|
100
|
+
return cached;
|
|
101
|
+
}
|
|
102
|
+
const requiredFlag = options.operation === 'create' ? 'requiredOnCreate' : 'requiredOnUpdate';
|
|
103
|
+
const shape = {};
|
|
104
|
+
for (const branch of seed.branches) {
|
|
105
|
+
const branchSchema = schemaForBranch(branch, options);
|
|
106
|
+
const isRequired = branch[requiredFlag] && options.enforceRequiredFields;
|
|
107
|
+
shape[branch.alias] = isRequired ? branchSchema : branchSchema.optional();
|
|
108
|
+
}
|
|
109
|
+
const compiled = z.object(shape).strict();
|
|
110
|
+
if (!hasRelation) {
|
|
111
|
+
const key = buildCacheKey(seed, options);
|
|
112
|
+
setCachedSchema(key, compiled);
|
|
113
|
+
}
|
|
114
|
+
return compiled;
|
|
115
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Branch } from '../types.js';
|
|
2
|
+
import type { FileAccept } from '../../media/file-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parses a candidate value into a valid HTTP or HTTPS URL string, returning null if invalid.
|
|
5
|
+
*
|
|
6
|
+
* @param input - The value to validate.
|
|
7
|
+
* @returns The cleaned URL string, or null if invalid.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseHttpUrl(input: unknown): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Resolves the file validation options for a branch, applying default values.
|
|
12
|
+
*
|
|
13
|
+
* @param branch - The file branch containing option overrides.
|
|
14
|
+
* @returns An object with resolved file acceptance rules and maximum size limit.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveFileOptions(branch: Branch): {
|
|
17
|
+
accept: FileAccept;
|
|
18
|
+
maxSize: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Checks if a branch acts as an asset list (file type with multiple upload allowed or asset-list format).
|
|
22
|
+
*
|
|
23
|
+
* @param branch - The branch to check.
|
|
24
|
+
* @returns True if it is an asset list, false otherwise.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isAssetListBranch(branch: Branch): boolean;
|
|
27
|
+
/** A file URL paired with its byte size, when known from payload metadata. */
|
|
28
|
+
export interface FileCandidate {
|
|
29
|
+
url: string;
|
|
30
|
+
size: number | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extracts a URL and, when available, a byte size from a candidate value
|
|
34
|
+
* (a string URL, or an object with `url`/`size` fields, e.g. uploader output).
|
|
35
|
+
*
|
|
36
|
+
* @param candidate - The candidate value.
|
|
37
|
+
* @returns The extracted file candidate, or null if no valid URL was found.
|
|
38
|
+
*/
|
|
39
|
+
export declare function extractFileCandidate(candidate: unknown): FileCandidate | null;
|
|
40
|
+
/**
|
|
41
|
+
* Collects and deduplicates valid file candidates (url + optional size) from a raw
|
|
42
|
+
* input representing an asset list.
|
|
43
|
+
*
|
|
44
|
+
* @param raw - The raw input data (can be array, JSON string, or single item).
|
|
45
|
+
* @returns The list of unique file candidates, or null if any item is invalid.
|
|
46
|
+
*/
|
|
47
|
+
export declare function collectAssetListItems(raw: unknown): FileCandidate[] | null;
|
|
48
|
+
//# sourceMappingURL=file-branch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-branch.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/file-branch.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAoB3D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAW1D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAM1F;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AA6BD,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAI7E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,EAAE,GAAG,IAAI,CAe1E"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { cleanString, isPlainObject } from './primitives.js';
|
|
4
|
+
/** Default maximum file size in bytes for file branches. */
|
|
5
|
+
const DEFAULT_FILE_MAX_SIZE = 5 * 1024 * 1024;
|
|
6
|
+
/**
|
|
7
|
+
* Safely parses a JSON string, returning the parsed value, or the original string if parsing fails.
|
|
8
|
+
*
|
|
9
|
+
* @param raw - The string to attempt parsing.
|
|
10
|
+
* @returns The parsed object/array, or the original string.
|
|
11
|
+
*/
|
|
12
|
+
function tryParseJson(raw) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return raw;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parses a candidate value into a valid HTTP or HTTPS URL string, returning null if invalid.
|
|
22
|
+
*
|
|
23
|
+
* @param input - The value to validate.
|
|
24
|
+
* @returns The cleaned URL string, or null if invalid.
|
|
25
|
+
*/
|
|
26
|
+
export function parseHttpUrl(input) {
|
|
27
|
+
if (typeof input !== 'string')
|
|
28
|
+
return null;
|
|
29
|
+
const cleaned = cleanString(input);
|
|
30
|
+
if (cleaned.length === 0)
|
|
31
|
+
return null;
|
|
32
|
+
let parsed;
|
|
33
|
+
try {
|
|
34
|
+
parsed = new URL(cleaned);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return (parsed.protocol === 'http:' || parsed.protocol === 'https:') ? cleaned : null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolves the file validation options for a branch, applying default values.
|
|
43
|
+
*
|
|
44
|
+
* @param branch - The file branch containing option overrides.
|
|
45
|
+
* @returns An object with resolved file acceptance rules and maximum size limit.
|
|
46
|
+
*/
|
|
47
|
+
export function resolveFileOptions(branch) {
|
|
48
|
+
const opts = branch.fileOptions;
|
|
49
|
+
return {
|
|
50
|
+
accept: opts?.accept ?? 'any',
|
|
51
|
+
maxSize: opts?.maxSize ?? DEFAULT_FILE_MAX_SIZE,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Checks if a branch acts as an asset list (file type with multiple upload allowed or asset-list format).
|
|
56
|
+
*
|
|
57
|
+
* @param branch - The branch to check.
|
|
58
|
+
* @returns True if it is an asset list, false otherwise.
|
|
59
|
+
*/
|
|
60
|
+
export function isAssetListBranch(branch) {
|
|
61
|
+
return branch.type === 'file' && (branch.multiple === true || branch.format === 'asset-list');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Extracts a URL string from a candidate value (which can be a string URL or an object containing a `url` field).
|
|
65
|
+
*
|
|
66
|
+
* @param candidate - The candidate value.
|
|
67
|
+
* @returns The extracted URL string, or null if not found.
|
|
68
|
+
*/
|
|
69
|
+
function extractUrlFromCandidate(candidate) {
|
|
70
|
+
const direct = parseHttpUrl(candidate);
|
|
71
|
+
if (direct)
|
|
72
|
+
return direct;
|
|
73
|
+
if (isPlainObject(candidate)) {
|
|
74
|
+
return parseHttpUrl(candidate.url);
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extracts a byte size from a candidate value's `size` field, if present and valid.
|
|
80
|
+
*
|
|
81
|
+
* @param candidate - The candidate value.
|
|
82
|
+
* @returns The size in bytes, or null if not available.
|
|
83
|
+
*/
|
|
84
|
+
function extractSizeFromCandidate(candidate) {
|
|
85
|
+
if (!isPlainObject(candidate))
|
|
86
|
+
return null;
|
|
87
|
+
const raw = candidate.size;
|
|
88
|
+
return typeof raw === 'number' && Number.isFinite(raw) && raw >= 0 ? raw : null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Extracts a URL and, when available, a byte size from a candidate value
|
|
92
|
+
* (a string URL, or an object with `url`/`size` fields, e.g. uploader output).
|
|
93
|
+
*
|
|
94
|
+
* @param candidate - The candidate value.
|
|
95
|
+
* @returns The extracted file candidate, or null if no valid URL was found.
|
|
96
|
+
*/
|
|
97
|
+
export function extractFileCandidate(candidate) {
|
|
98
|
+
const url = extractUrlFromCandidate(candidate);
|
|
99
|
+
if (!url)
|
|
100
|
+
return null;
|
|
101
|
+
return { url, size: extractSizeFromCandidate(candidate) };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Collects and deduplicates valid file candidates (url + optional size) from a raw
|
|
105
|
+
* input representing an asset list.
|
|
106
|
+
*
|
|
107
|
+
* @param raw - The raw input data (can be array, JSON string, or single item).
|
|
108
|
+
* @returns The list of unique file candidates, or null if any item is invalid.
|
|
109
|
+
*/
|
|
110
|
+
export function collectAssetListItems(raw) {
|
|
111
|
+
const source = typeof raw === 'string' ? tryParseJson(raw) : raw;
|
|
112
|
+
const items = Array.isArray(source) ? source : [source];
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
const out = [];
|
|
115
|
+
for (const item of items) {
|
|
116
|
+
if (item === null || item === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
const candidate = extractFileCandidate(item);
|
|
119
|
+
if (!candidate)
|
|
120
|
+
return null;
|
|
121
|
+
if (!seen.has(candidate.url)) {
|
|
122
|
+
seen.add(candidate.url);
|
|
123
|
+
out.push(candidate);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|