@beechcms/core 0.9.5 → 0.9.7
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 +21 -0
- package/dist/auth/login-request.d.ts +16 -0
- package/dist/auth/login-request.d.ts.map +1 -0
- package/dist/auth/login-request.js +22 -0
- package/dist/automations-grammar-words.d.ts +2 -0
- package/dist/automations-grammar-words.d.ts.map +1 -0
- package/dist/automations-grammar-words.js +8 -0
- package/dist/automations.repository.interface.d.ts +19 -0
- package/dist/automations.repository.interface.d.ts.map +1 -0
- package/dist/automations.repository.interface.js +3 -0
- package/dist/automations.runner.interface.d.ts +10 -0
- package/dist/automations.runner.interface.d.ts.map +1 -0
- package/dist/automations.runner.interface.js +3 -0
- package/dist/automations.runner.stub.d.ts +5 -0
- package/dist/automations.runner.stub.d.ts.map +1 -0
- package/dist/automations.runner.stub.js +7 -0
- package/dist/automations.types.d.ts +75 -0
- package/dist/automations.types.d.ts.map +1 -0
- package/dist/automations.types.js +3 -0
- package/dist/clock.d.ts +25 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +11 -0
- package/dist/content/content.repository.d.ts +22 -22
- package/dist/content/content.repository.d.ts.map +1 -1
- package/dist/content/retention.d.ts +13 -0
- package/dist/content/retention.d.ts.map +1 -0
- package/dist/content/retention.js +20 -0
- package/dist/content/slug-utils.d.ts +7 -0
- package/dist/content/slug-utils.d.ts.map +1 -1
- package/dist/content/slug-utils.js +12 -0
- package/dist/content-scan.repository.d.ts +9 -0
- package/dist/content-scan.repository.d.ts.map +1 -0
- package/dist/content-scan.repository.js +3 -0
- package/dist/content.repository.d.ts +247 -0
- package/dist/content.repository.d.ts.map +1 -0
- package/dist/content.repository.js +59 -0
- package/dist/dashboard-layout/dashboard-layout.d.ts +2 -2
- package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -1
- package/dist/dashboard-layout/dashboard-layout.js +3 -3
- package/dist/dashboard-layout/relation-avatar.d.ts +12 -0
- package/dist/dashboard-layout/relation-avatar.d.ts.map +1 -0
- package/dist/dashboard-layout/relation-avatar.js +18 -0
- package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
- package/dist/dashboard-layout/seed-layout.js +17 -1
- package/dist/dashboard-layout.d.ts +154 -0
- package/dist/dashboard-layout.d.ts.map +1 -0
- package/dist/dashboard-layout.js +186 -0
- package/dist/dashboard-layout.repository.d.ts +18 -0
- package/dist/dashboard-layout.repository.d.ts.map +1 -0
- package/dist/dashboard-layout.repository.js +3 -0
- package/dist/dashboard-permissions.d.ts +3 -0
- package/dist/dashboard-permissions.d.ts.map +1 -0
- package/dist/dashboard-permissions.js +11 -0
- package/dist/dashboard-scopes.d.ts +12 -0
- package/dist/dashboard-scopes.d.ts.map +1 -0
- package/dist/dashboard-scopes.js +30 -0
- package/dist/define-seed.d.ts +3 -0
- package/dist/define-seed.d.ts.map +1 -0
- package/dist/define-seed.js +5 -0
- package/dist/demo-data.repository.d.ts +5 -0
- package/dist/demo-data.repository.d.ts.map +1 -0
- package/dist/demo-data.repository.js +3 -0
- package/dist/email.provider.d.ts +32 -0
- package/dist/email.provider.d.ts.map +1 -0
- package/dist/email.provider.js +4 -0
- package/dist/engine/ddl/ddl.d.ts +4 -6
- package/dist/engine/ddl/ddl.d.ts.map +1 -1
- package/dist/engine/ddl/ddl.js +13 -27
- package/dist/engine/ddl/seed-ddl.d.ts.map +1 -1
- package/dist/engine/ddl/seed-ddl.js +10 -2
- package/dist/engine/ddl.d.ts +0 -23
- package/dist/engine/ddl.d.ts.map +1 -1
- package/dist/engine/ddl.js +3 -59
- package/dist/engine/localization.d.ts +127 -0
- package/dist/engine/localization.d.ts.map +1 -0
- package/dist/engine/localization.js +212 -0
- package/dist/engine/privacy.service.d.ts.map +1 -1
- package/dist/engine/query/active-clause.d.ts +2 -3
- package/dist/engine/query/active-clause.d.ts.map +1 -1
- package/dist/engine/query/active-clause.js +3 -4
- package/dist/engine/query/query.js +8 -8
- package/dist/engine/query/search-support.d.ts +32 -0
- package/dist/engine/query/search-support.d.ts.map +1 -0
- package/dist/engine/query/search-support.js +34 -0
- package/dist/engine/query.d.ts.map +1 -1
- package/dist/engine/query.js +0 -12
- package/dist/engine/schema-fingerprint.d.ts +2 -0
- package/dist/engine/schema-fingerprint.d.ts.map +1 -1
- package/dist/engine/schema-fingerprint.js +2 -0
- package/dist/engine/seed-ddl.d.ts +1 -4
- package/dist/engine/seed-ddl.d.ts.map +1 -1
- package/dist/engine/seed-ddl.js +2 -17
- package/dist/engine/seed-registry.d.ts.map +1 -1
- package/dist/engine/seed-types-generator.d.ts +2 -17
- package/dist/engine/seed-types-generator.d.ts.map +1 -1
- package/dist/engine/seed-types-generator.js +3 -31
- package/dist/engine/seed-validation.d.ts.map +1 -1
- package/dist/engine/seed-validation.js +0 -10
- package/dist/engine/seeds/seed-definition-diff.d.ts +1 -1
- package/dist/engine/seeds/seed-definition-diff.d.ts.map +1 -1
- package/dist/engine/seeds/seed-definition-diff.js +2 -2
- package/dist/engine/seeds/seed-validation.d.ts.map +1 -1
- package/dist/engine/seeds/seed-validation.js +3 -12
- package/dist/engine/types.d.ts +7 -7
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/engine.d.ts +144 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +741 -0
- package/dist/file-types.d.ts +13 -0
- package/dist/file-types.d.ts.map +1 -0
- package/dist/file-types.js +85 -0
- package/dist/hooks.d.ts +29 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +3 -0
- package/dist/id-generator.d.ts +26 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +12 -0
- package/dist/idempotency.repository.d.ts +13 -0
- package/dist/idempotency.repository.d.ts.map +1 -0
- package/dist/idempotency.repository.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/kanban-position.repository.d.ts +19 -0
- package/dist/kanban-position.repository.d.ts.map +1 -0
- package/dist/kanban-position.repository.js +3 -0
- package/dist/kanban.d.ts +75 -0
- package/dist/kanban.d.ts.map +1 -0
- package/dist/kanban.js +82 -0
- package/dist/layout-permissions.d.ts +3 -0
- package/dist/layout-permissions.d.ts.map +1 -0
- package/dist/layout-permissions.js +11 -0
- package/dist/media.repository.d.ts +61 -0
- package/dist/media.repository.d.ts.map +1 -0
- package/dist/media.repository.js +3 -0
- package/dist/notifications/notification.repository.d.ts +6 -3
- package/dist/notifications/notification.repository.d.ts.map +1 -1
- package/dist/policies.d.ts +10 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +33 -0
- package/dist/queue.interface.d.ts +39 -0
- package/dist/queue.interface.d.ts.map +1 -0
- package/dist/queue.interface.js +3 -0
- package/dist/queue.stub.d.ts +6 -0
- package/dist/queue.stub.d.ts.map +1 -0
- package/dist/queue.stub.js +8 -0
- package/dist/relations.d.ts +28 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +25 -0
- package/dist/richtext-render.d.ts +11 -0
- package/dist/richtext-render.d.ts.map +1 -0
- package/dist/richtext-render.js +87 -0
- package/dist/richtext.d.ts +11 -0
- package/dist/richtext.d.ts.map +1 -0
- package/dist/richtext.js +13 -0
- package/dist/scheduler.interface.d.ts +4 -0
- package/dist/scheduler.interface.d.ts.map +1 -0
- package/dist/scheduler.interface.js +3 -0
- package/dist/scheduler.stub.d.ts +5 -0
- package/dist/scheduler.stub.d.ts.map +1 -0
- package/dist/scheduler.stub.js +7 -0
- package/dist/schema-mutator.d.ts +26 -0
- package/dist/schema-mutator.d.ts.map +1 -0
- package/dist/schema-mutator.js +3 -0
- package/dist/security/time-trap.d.ts +3 -2
- package/dist/security/time-trap.d.ts.map +1 -1
- package/dist/security/time-trap.js +5 -4
- package/dist/seed-ddl-destructive.d.ts +40 -0
- package/dist/seed-ddl-destructive.d.ts.map +1 -0
- package/dist/seed-ddl-destructive.js +149 -0
- package/dist/seed-ddl.d.ts +40 -0
- package/dist/seed-ddl.d.ts.map +1 -0
- package/dist/seed-ddl.js +100 -0
- package/dist/seed-layout.d.ts +145 -0
- package/dist/seed-layout.d.ts.map +1 -0
- package/dist/seed-layout.js +192 -0
- package/dist/seed-layout.repository.d.ts +22 -0
- package/dist/seed-layout.repository.d.ts.map +1 -0
- package/dist/seed-layout.repository.js +3 -0
- package/dist/seed-registry.d.ts +71 -0
- package/dist/seed-registry.d.ts.map +1 -0
- package/dist/seed-registry.js +143 -0
- package/dist/seed-types-generator.d.ts +1 -1
- package/dist/seed-types-generator.d.ts.map +1 -1
- package/dist/seed-validation.d.ts +23 -0
- package/dist/seed-validation.d.ts.map +1 -0
- package/dist/seed-validation.js +224 -0
- package/dist/seed.repository.d.ts +37 -0
- package/dist/seed.repository.d.ts.map +1 -0
- package/dist/seed.repository.js +3 -0
- package/dist/seeds.d.ts +20 -0
- package/dist/seeds.d.ts.map +1 -0
- package/dist/seeds.js +21 -0
- package/dist/settings/site-settings.repository.d.ts +2 -0
- package/dist/settings/site-settings.repository.d.ts.map +1 -1
- package/dist/site-settings.repository.d.ts +16 -0
- package/dist/site-settings.repository.d.ts.map +1 -0
- package/dist/site-settings.repository.js +3 -0
- package/dist/slug-utils.d.ts +18 -0
- package/dist/slug-utils.d.ts.map +1 -0
- package/dist/slug-utils.js +32 -0
- package/dist/storage.d.ts +46 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +3 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/validation.d.ts +50 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +660 -0
- package/dist/webhook-validation.d.ts +3 -0
- package/dist/webhook-validation.d.ts.map +1 -0
- package/dist/webhook-validation.js +6 -0
- package/package.json +1 -1
- package/dist/content/content-lock.repository.d.ts +0 -63
- package/dist/content/content-lock.repository.d.ts.map +0 -1
- package/dist/content/content-lock.repository.js +0 -15
package/dist/seed-ddl.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { generateCreateTable, generateDraftTable, generateIndexes, generateFtsTable, generateFtsTriggers, generateAddColumn, generateJunctionTable, generateJunctionIndexes, generateJunctionDraftTable, indexableSearchBranches, } from './engine.js';
|
|
4
|
+
/**
|
|
5
|
+
* Full create-from-scratch statement set for a seed. Mirrors the CLI's buildStatements.
|
|
6
|
+
* Order: parent table → indexes → draft table → FTS table → FTS triggers →
|
|
7
|
+
* per multi-relation: junction table → junction indexes → junction draft table.
|
|
8
|
+
* Callers that create several seeds must order seeds with sortSeedsByDependencies
|
|
9
|
+
* first so relation FK targets exist.
|
|
10
|
+
*/
|
|
11
|
+
export function planCreateSeed(seed) {
|
|
12
|
+
const stmts = [generateCreateTable(seed), ...generateIndexes(seed)];
|
|
13
|
+
const draft = generateDraftTable(seed);
|
|
14
|
+
if (draft)
|
|
15
|
+
stmts.push(draft);
|
|
16
|
+
const fts = generateFtsTable(seed);
|
|
17
|
+
if (fts)
|
|
18
|
+
stmts.push(fts, ...generateFtsTriggers(seed));
|
|
19
|
+
for (const branch of seed.branches) {
|
|
20
|
+
if (branch.type !== 'relation' || branch.multiple !== true)
|
|
21
|
+
continue;
|
|
22
|
+
stmts.push(generateJunctionTable(seed, branch), ...generateJunctionIndexes(seed, branch));
|
|
23
|
+
const dj = generateJunctionDraftTable(seed, branch);
|
|
24
|
+
if (dj)
|
|
25
|
+
stmts.push(dj);
|
|
26
|
+
}
|
|
27
|
+
return stmts;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Additive extension: given the columns that already exist on content_{slug}
|
|
31
|
+
* (from PRAGMA table_info, passed in by the caller), return ONLY the statements
|
|
32
|
+
* needed to add new branches — ADD COLUMN + indexes, plus junction tables for
|
|
33
|
+
* new multi-relation branches. Never drops or renames.
|
|
34
|
+
*
|
|
35
|
+
* FTS: SQLite cannot ALTER an fts5 table's columns. If a new text/richtext
|
|
36
|
+
* searchable branch was added, ftsRebuildNeeded=true signals the caller
|
|
37
|
+
* (sprint 03) to handle it — no DROP is emitted.
|
|
38
|
+
*/
|
|
39
|
+
export function planExtendSeed(seed, existingColumns) {
|
|
40
|
+
const statements = [];
|
|
41
|
+
let ftsRebuildNeeded = false;
|
|
42
|
+
for (const branch of seed.branches) {
|
|
43
|
+
if (branch.type === 'relation' && branch.multiple === true) {
|
|
44
|
+
// junction table is CREATE IF NOT EXISTS — safe to re-emit
|
|
45
|
+
statements.push(generateJunctionTable(seed, branch), ...generateJunctionIndexes(seed, branch));
|
|
46
|
+
const dj = generateJunctionDraftTable(seed, branch);
|
|
47
|
+
if (dj)
|
|
48
|
+
statements.push(dj);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (existingColumns.has(branch.alias))
|
|
52
|
+
continue;
|
|
53
|
+
statements.push(generateAddColumn(seed, branch));
|
|
54
|
+
if ((branch.type === 'text' || branch.type === 'richtext') && branch.policies?.search !== false) {
|
|
55
|
+
ftsRebuildNeeded = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// CREATE INDEX IF NOT EXISTS — idempotent, safe to re-run
|
|
59
|
+
statements.push(...generateIndexes(seed));
|
|
60
|
+
return { statements, ftsRebuildNeeded };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Destructive FTS rebuild (sprint 06). SQLite cannot ALTER an fts5 table's
|
|
64
|
+
* columns, so when a searchable branch is added, renamed, retyped, or dropped
|
|
65
|
+
* the only correct fix is to drop and recreate the `fts_{slug}` virtual table
|
|
66
|
+
* (and its insert/update/delete triggers), then backfill from `content_{slug}`.
|
|
67
|
+
*
|
|
68
|
+
* Returns an empty array when the seed has no searchable branches — in that
|
|
69
|
+
* case the caller should instead drop any leftover FTS table via
|
|
70
|
+
* generateDropTable's FTS statements. The returned statements are destructive
|
|
71
|
+
* and must run through ISchemaMutator.execDestructive, never execDdl.
|
|
72
|
+
*
|
|
73
|
+
* Order: drop triggers → drop fts table → recreate table → recreate triggers →
|
|
74
|
+
* backfill existing rows.
|
|
75
|
+
*/
|
|
76
|
+
export function planFtsRebuild(seed) {
|
|
77
|
+
const branches = indexableSearchBranches(seed);
|
|
78
|
+
if (branches.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
const slug = seed.slug;
|
|
81
|
+
const ftsTable = `fts_${slug}`;
|
|
82
|
+
const contentTable = `content_${slug}`;
|
|
83
|
+
const cols = branches.map(b => b.alias);
|
|
84
|
+
const ftsTableDdl = generateFtsTable(seed);
|
|
85
|
+
const triggers = generateFtsTriggers(seed);
|
|
86
|
+
// generateFtsTable/Triggers return non-null here (branches.length > 0 checked).
|
|
87
|
+
if (!ftsTableDdl)
|
|
88
|
+
return [];
|
|
89
|
+
const ftsColList = ['entry_id', ...cols].join(', ');
|
|
90
|
+
const selectList = ['id', ...cols].join(', ');
|
|
91
|
+
return [
|
|
92
|
+
`DROP TRIGGER IF EXISTS fts_${slug}_insert;`,
|
|
93
|
+
`DROP TRIGGER IF EXISTS fts_${slug}_update;`,
|
|
94
|
+
`DROP TRIGGER IF EXISTS fts_${slug}_delete;`,
|
|
95
|
+
`DROP TABLE IF EXISTS ${ftsTable};`,
|
|
96
|
+
ftsTableDdl,
|
|
97
|
+
...triggers,
|
|
98
|
+
`INSERT INTO ${ftsTable}(${ftsColList}) SELECT ${selectList} FROM ${contentTable};`,
|
|
99
|
+
];
|
|
100
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Seed, Branch } from './types.js';
|
|
3
|
+
export declare const kanbanViewConfigSchema: z.ZodObject<{
|
|
4
|
+
axisBranchId: z.ZodNullable<z.ZodString>;
|
|
5
|
+
sort: z.ZodNullable<z.ZodObject<{
|
|
6
|
+
branchId: z.ZodString;
|
|
7
|
+
dir: z.ZodEnum<{
|
|
8
|
+
ASC: "ASC";
|
|
9
|
+
DESC: "DESC";
|
|
10
|
+
}>;
|
|
11
|
+
}, z.core.$strip>>;
|
|
12
|
+
hiddenColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
13
|
+
collapsedColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export type KanbanViewConfig = z.infer<typeof kanbanViewConfigSchema>;
|
|
16
|
+
export declare const seedViewConfigSchema: z.ZodObject<{
|
|
17
|
+
kanban: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
axisBranchId: z.ZodNullable<z.ZodString>;
|
|
19
|
+
sort: z.ZodNullable<z.ZodObject<{
|
|
20
|
+
branchId: z.ZodString;
|
|
21
|
+
dir: z.ZodEnum<{
|
|
22
|
+
ASC: "ASC";
|
|
23
|
+
DESC: "DESC";
|
|
24
|
+
}>;
|
|
25
|
+
}, z.core.$strip>>;
|
|
26
|
+
hiddenColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
27
|
+
collapsedColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
28
|
+
}, z.core.$strip>>;
|
|
29
|
+
}, z.core.$loose>;
|
|
30
|
+
export type SeedViewConfig = z.infer<typeof seedViewConfigSchema>;
|
|
31
|
+
/** Reference to a Seed branch by its stable id (e.g. 'br_03').
|
|
32
|
+
* Aliases can be renamed — ids cannot. When the id does not match any
|
|
33
|
+
* branch on the Seed at read time, the field is silently stripped (auto-cleanup). */
|
|
34
|
+
export interface LayoutField {
|
|
35
|
+
branchId: string;
|
|
36
|
+
}
|
|
37
|
+
export interface LayoutColumn {
|
|
38
|
+
id: string;
|
|
39
|
+
fields: LayoutField[];
|
|
40
|
+
}
|
|
41
|
+
export interface LayoutSection {
|
|
42
|
+
id: string;
|
|
43
|
+
label?: string;
|
|
44
|
+
hideLabel?: boolean;
|
|
45
|
+
hideBorder?: boolean;
|
|
46
|
+
collapsible?: boolean;
|
|
47
|
+
/** Max 4 columns. Enforced by validator. */
|
|
48
|
+
columns: LayoutColumn[];
|
|
49
|
+
}
|
|
50
|
+
export interface LayoutTab {
|
|
51
|
+
id: string;
|
|
52
|
+
label: string;
|
|
53
|
+
sections: LayoutSection[];
|
|
54
|
+
}
|
|
55
|
+
export interface FormLayout {
|
|
56
|
+
/** Format version — bump when introducing breaking changes. */
|
|
57
|
+
version: 1;
|
|
58
|
+
tabs: LayoutTab[];
|
|
59
|
+
}
|
|
60
|
+
/** Branch types that must occupy a section alone, full width. */
|
|
61
|
+
export declare const FULL_WIDTH_BRANCH_TYPES: Set<import("./types.js").BranchType>;
|
|
62
|
+
/** Returns true when a branch is a gallery (file + multiple/asset-list). */
|
|
63
|
+
export declare function isGalleryBranch(branch: Branch): boolean;
|
|
64
|
+
/** Branch types currently unsupported in the Layout Builder. */
|
|
65
|
+
export declare const UNSUPPORTED_BRANCH_TYPES: Set<import("./types.js").BranchType>;
|
|
66
|
+
/** Aliases that are NEVER included in the editor form (handled by other UI). */
|
|
67
|
+
export declare const SYSTEM_ALIASES: Set<string>;
|
|
68
|
+
/** True when the branch is eligible to appear in the layout at all. */
|
|
69
|
+
export declare function isLayoutableBranch(branch: Branch): boolean;
|
|
70
|
+
/** True when the branch must occupy a dedicated full-width section. */
|
|
71
|
+
export declare function isFullWidthBranch(branch: Branch): boolean;
|
|
72
|
+
/** Branches considered SEO fields. Matches the existing rule in entry-editor.tsx. */
|
|
73
|
+
export declare function isSeoBranch(branch: Branch): boolean;
|
|
74
|
+
export declare const layoutFieldSchema: z.ZodObject<{
|
|
75
|
+
branchId: z.ZodString;
|
|
76
|
+
}, z.core.$strip>;
|
|
77
|
+
export declare const layoutColumnSchema: z.ZodObject<{
|
|
78
|
+
id: z.ZodString;
|
|
79
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
80
|
+
branchId: z.ZodString;
|
|
81
|
+
}, z.core.$strip>>;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
export declare const layoutSectionSchema: z.ZodObject<{
|
|
84
|
+
id: z.ZodString;
|
|
85
|
+
label: z.ZodOptional<z.ZodString>;
|
|
86
|
+
hideLabel: z.ZodOptional<z.ZodBoolean>;
|
|
87
|
+
hideBorder: z.ZodOptional<z.ZodBoolean>;
|
|
88
|
+
collapsible: z.ZodOptional<z.ZodBoolean>;
|
|
89
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
90
|
+
id: z.ZodString;
|
|
91
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
92
|
+
branchId: z.ZodString;
|
|
93
|
+
}, z.core.$strip>>;
|
|
94
|
+
}, z.core.$strip>>;
|
|
95
|
+
}, z.core.$strip>;
|
|
96
|
+
export declare const layoutTabSchema: z.ZodObject<{
|
|
97
|
+
id: z.ZodString;
|
|
98
|
+
label: z.ZodString;
|
|
99
|
+
sections: z.ZodArray<z.ZodObject<{
|
|
100
|
+
id: z.ZodString;
|
|
101
|
+
label: z.ZodOptional<z.ZodString>;
|
|
102
|
+
hideLabel: z.ZodOptional<z.ZodBoolean>;
|
|
103
|
+
hideBorder: z.ZodOptional<z.ZodBoolean>;
|
|
104
|
+
collapsible: z.ZodOptional<z.ZodBoolean>;
|
|
105
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
106
|
+
id: z.ZodString;
|
|
107
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
108
|
+
branchId: z.ZodString;
|
|
109
|
+
}, z.core.$strip>>;
|
|
110
|
+
}, z.core.$strip>>;
|
|
111
|
+
}, z.core.$strip>>;
|
|
112
|
+
}, z.core.$strip>;
|
|
113
|
+
export declare const formLayoutSchema: z.ZodObject<{
|
|
114
|
+
version: z.ZodLiteral<1>;
|
|
115
|
+
tabs: z.ZodArray<z.ZodObject<{
|
|
116
|
+
id: z.ZodString;
|
|
117
|
+
label: z.ZodString;
|
|
118
|
+
sections: z.ZodArray<z.ZodObject<{
|
|
119
|
+
id: z.ZodString;
|
|
120
|
+
label: z.ZodOptional<z.ZodString>;
|
|
121
|
+
hideLabel: z.ZodOptional<z.ZodBoolean>;
|
|
122
|
+
hideBorder: z.ZodOptional<z.ZodBoolean>;
|
|
123
|
+
collapsible: z.ZodOptional<z.ZodBoolean>;
|
|
124
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
125
|
+
id: z.ZodString;
|
|
126
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
127
|
+
branchId: z.ZodString;
|
|
128
|
+
}, z.core.$strip>>;
|
|
129
|
+
}, z.core.$strip>>;
|
|
130
|
+
}, z.core.$strip>>;
|
|
131
|
+
}, z.core.$strip>>;
|
|
132
|
+
}, z.core.$strip>;
|
|
133
|
+
export declare function generateDefaultLayout(seed: Seed, opts?: {
|
|
134
|
+
newId: () => string;
|
|
135
|
+
}): FormLayout;
|
|
136
|
+
export type ValidateLayoutResult = {
|
|
137
|
+
ok: true;
|
|
138
|
+
cleaned: FormLayout;
|
|
139
|
+
} | {
|
|
140
|
+
ok: false;
|
|
141
|
+
errors: string[];
|
|
142
|
+
cleaned: FormLayout;
|
|
143
|
+
};
|
|
144
|
+
export declare function validateLayoutAgainstSeed(layout: FormLayout, seed: Seed): ValidateLayoutResult;
|
|
145
|
+
//# sourceMappingURL=seed-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed-layout.d.ts","sourceRoot":"","sources":["../src/seed-layout.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAO9C,eAAO,MAAM,sBAAsB;;;;;;;;;;;iBAKjC,CAAA;AACF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAErE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;iBAEjB,CAAA;AAChB,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAMjE;;sFAEsF;AACtF,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,WAAW,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,4CAA4C;IAC5C,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,aAAa,EAAE,CAAA;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,+DAA+D;IAC/D,OAAO,EAAE,CAAC,CAAA;IACV,IAAI,EAAE,SAAS,EAAE,CAAA;CAClB;AAMD,iEAAiE;AACjE,eAAO,MAAM,uBAAuB,sCAAwC,CAAA;AAE5E,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED,gEAAgE;AAChE,eAAO,MAAM,wBAAwB,sCAAoC,CAAA;AAEzE,gFAAgF;AAChF,eAAO,MAAM,cAAc,aAEzB,CAAA;AAEF,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAK1D;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD;AAMD,eAAO,MAAM,iBAAiB;;iBAAgE,CAAA;AAC9F,eAAO,MAAM,kBAAkB;;;;;iBAG7B,CAAA;AACF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;iBAO9B,CAAA;AACF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;iBAI1B,CAAA;AACF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;iBAG3B,CAAA;AAiDF,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,MAAM,CAAA;CAAE,GAC7B,UAAU,CAmBZ;AAMD,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,GACjC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAA;AAExD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,IAAI,GACT,oBAAoB,CAiEtB"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { findBranchById } from './seed-registry.js';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// View config — per-seed, per-view dashboard preferences (KB-S02)
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export const kanbanViewConfigSchema = z.object({
|
|
9
|
+
axisBranchId: z.string().nullable(),
|
|
10
|
+
sort: z.object({ branchId: z.string(), dir: z.enum(['ASC', 'DESC']) }).nullable(),
|
|
11
|
+
hiddenColumnValues: z.array(z.string()).optional(),
|
|
12
|
+
collapsedColumnValues: z.array(z.string()).optional(),
|
|
13
|
+
});
|
|
14
|
+
export const seedViewConfigSchema = z.object({
|
|
15
|
+
kanban: kanbanViewConfigSchema.optional(),
|
|
16
|
+
}).passthrough();
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Branch-type rules used by both the default generator and the validator.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/** Branch types that must occupy a section alone, full width. */
|
|
21
|
+
export const FULL_WIDTH_BRANCH_TYPES = new Set(['richtext']);
|
|
22
|
+
/** Returns true when a branch is a gallery (file + multiple/asset-list). */
|
|
23
|
+
export function isGalleryBranch(branch) {
|
|
24
|
+
return branch.type === 'file'
|
|
25
|
+
&& (branch.multiple === true || branch.format === 'asset-list');
|
|
26
|
+
}
|
|
27
|
+
/** Branch types currently unsupported in the Layout Builder. */
|
|
28
|
+
export const UNSUPPORTED_BRANCH_TYPES = new Set(['json']);
|
|
29
|
+
/** Aliases that are NEVER included in the editor form (handled by other UI). */
|
|
30
|
+
export const SYSTEM_ALIASES = new Set([
|
|
31
|
+
'id', 'slug', 'status', 'created_at', 'updated_at',
|
|
32
|
+
]);
|
|
33
|
+
/** True when the branch is eligible to appear in the layout at all. */
|
|
34
|
+
export function isLayoutableBranch(branch) {
|
|
35
|
+
if (SYSTEM_ALIASES.has(branch.alias))
|
|
36
|
+
return false;
|
|
37
|
+
if (branch.policies?.visibility === 'hidden')
|
|
38
|
+
return false;
|
|
39
|
+
if (UNSUPPORTED_BRANCH_TYPES.has(branch.type))
|
|
40
|
+
return false;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
/** True when the branch must occupy a dedicated full-width section. */
|
|
44
|
+
export function isFullWidthBranch(branch) {
|
|
45
|
+
return FULL_WIDTH_BRANCH_TYPES.has(branch.type) || isGalleryBranch(branch);
|
|
46
|
+
}
|
|
47
|
+
/** Branches considered SEO fields. Matches the existing rule in entry-editor.tsx. */
|
|
48
|
+
export function isSeoBranch(branch) {
|
|
49
|
+
return branch.alias.startsWith('meta');
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Zod schema for shape validation
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
export const layoutFieldSchema = z.object({ branchId: z.string().regex(/^br_[A-Za-z0-9]+$/) });
|
|
55
|
+
export const layoutColumnSchema = z.object({
|
|
56
|
+
id: z.string().min(1),
|
|
57
|
+
fields: z.array(layoutFieldSchema),
|
|
58
|
+
});
|
|
59
|
+
export const layoutSectionSchema = z.object({
|
|
60
|
+
id: z.string().min(1),
|
|
61
|
+
label: z.string().optional(),
|
|
62
|
+
hideLabel: z.boolean().optional(),
|
|
63
|
+
hideBorder: z.boolean().optional(),
|
|
64
|
+
collapsible: z.boolean().optional(),
|
|
65
|
+
columns: z.array(layoutColumnSchema).min(1).max(4),
|
|
66
|
+
});
|
|
67
|
+
export const layoutTabSchema = z.object({
|
|
68
|
+
id: z.string().min(1),
|
|
69
|
+
label: z.string().min(1).max(60),
|
|
70
|
+
sections: z.array(layoutSectionSchema).min(1),
|
|
71
|
+
});
|
|
72
|
+
export const formLayoutSchema = z.object({
|
|
73
|
+
version: z.literal(1),
|
|
74
|
+
tabs: z.array(layoutTabSchema).min(1).max(8),
|
|
75
|
+
});
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Default layout generator
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
const DEFAULT_COLUMNS_PER_SECTION = 3;
|
|
80
|
+
const defaultIdFactory = () => crypto.randomUUID();
|
|
81
|
+
function buildSectionsForBranches(branches, newId) {
|
|
82
|
+
const sections = [];
|
|
83
|
+
let currentColumns = [];
|
|
84
|
+
function flushCompact() {
|
|
85
|
+
if (currentColumns.length === 0)
|
|
86
|
+
return;
|
|
87
|
+
sections.push({ id: newId(), columns: currentColumns });
|
|
88
|
+
currentColumns = [];
|
|
89
|
+
}
|
|
90
|
+
for (const branch of branches) {
|
|
91
|
+
if (isFullWidthBranch(branch)) {
|
|
92
|
+
flushCompact();
|
|
93
|
+
sections.push({
|
|
94
|
+
id: newId(),
|
|
95
|
+
label: branch.label,
|
|
96
|
+
hideLabel: true,
|
|
97
|
+
columns: [{ id: newId(), fields: [{ branchId: branch.id }] }],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
currentColumns.push({ id: newId(), fields: [{ branchId: branch.id }] });
|
|
102
|
+
if (currentColumns.length >= DEFAULT_COLUMNS_PER_SECTION) {
|
|
103
|
+
flushCompact();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
flushCompact();
|
|
108
|
+
if (sections.length === 0) {
|
|
109
|
+
sections.push({ id: newId(), columns: [{ id: newId(), fields: [] }] });
|
|
110
|
+
}
|
|
111
|
+
return sections;
|
|
112
|
+
}
|
|
113
|
+
export function generateDefaultLayout(seed, opts) {
|
|
114
|
+
const newId = opts?.newId ?? defaultIdFactory;
|
|
115
|
+
const layoutable = seed.branches.filter(isLayoutableBranch);
|
|
116
|
+
const seo = layoutable.filter(isSeoBranch);
|
|
117
|
+
const main = layoutable.filter((b) => !isSeoBranch(b));
|
|
118
|
+
const dataTab = {
|
|
119
|
+
id: newId(),
|
|
120
|
+
label: 'Data',
|
|
121
|
+
sections: buildSectionsForBranches(main, newId),
|
|
122
|
+
};
|
|
123
|
+
const seoTab = {
|
|
124
|
+
id: newId(),
|
|
125
|
+
label: 'SEO',
|
|
126
|
+
sections: buildSectionsForBranches(seo, newId),
|
|
127
|
+
};
|
|
128
|
+
return { version: 1, tabs: [dataTab, seoTab] };
|
|
129
|
+
}
|
|
130
|
+
export function validateLayoutAgainstSeed(layout, seed) {
|
|
131
|
+
// Gracefully handle undefined/null layout, or missing tabs (e.g. from {} placeholder)
|
|
132
|
+
if (!layout || !Array.isArray(layout.tabs)) {
|
|
133
|
+
const defaultLayout = generateDefaultLayout(seed);
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
errors: ['Invalid layout structure: missing tabs'],
|
|
137
|
+
cleaned: defaultLayout,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const errors = [];
|
|
141
|
+
const seenBranchIds = new Set();
|
|
142
|
+
const cleanedTabs = (layout.tabs ?? []).map((tab) => ({
|
|
143
|
+
...tab,
|
|
144
|
+
sections: (tab?.sections ?? []).map((section) => ({
|
|
145
|
+
...section,
|
|
146
|
+
columns: (section?.columns ?? []).map((col) => ({
|
|
147
|
+
...col,
|
|
148
|
+
fields: (col?.fields ?? []).filter((f) => {
|
|
149
|
+
if (!f || typeof f !== 'object' || !('branchId' in f))
|
|
150
|
+
return false;
|
|
151
|
+
const branch = findBranchById(seed, f.branchId);
|
|
152
|
+
if (!branch)
|
|
153
|
+
return false;
|
|
154
|
+
if (UNSUPPORTED_BRANCH_TYPES.has(branch.type) || SYSTEM_ALIASES.has(branch.alias))
|
|
155
|
+
return false;
|
|
156
|
+
return true;
|
|
157
|
+
}),
|
|
158
|
+
})),
|
|
159
|
+
})),
|
|
160
|
+
}));
|
|
161
|
+
// Duplicate and full-width constraint checks on cleaned layout
|
|
162
|
+
for (const tab of cleanedTabs) {
|
|
163
|
+
for (const section of tab.sections ?? []) {
|
|
164
|
+
const fieldsInSection = [];
|
|
165
|
+
for (const col of section.columns ?? []) {
|
|
166
|
+
for (const f of col.fields ?? []) {
|
|
167
|
+
const branch = findBranchById(seed, f.branchId);
|
|
168
|
+
if (!branch)
|
|
169
|
+
continue;
|
|
170
|
+
if (seenBranchIds.has(f.branchId)) {
|
|
171
|
+
errors.push(`Branch '${branch.alias}' (id=${f.branchId}) appears more than once in the layout.`);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
seenBranchIds.add(f.branchId);
|
|
175
|
+
fieldsInSection.push(branch);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Full-width branches must occupy a dedicated section
|
|
180
|
+
const fullWidthInSection = fieldsInSection.filter(isFullWidthBranch);
|
|
181
|
+
if (fullWidthInSection.length > 0 && fieldsInSection.length > fullWidthInSection.length) {
|
|
182
|
+
for (const fw of fullWidthInSection) {
|
|
183
|
+
errors.push(`Branch '${fw.alias}' (type=${fw.type}) must occupy a dedicated section and cannot share it with other fields.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const cleaned = { ...layout, tabs: cleanedTabs };
|
|
189
|
+
if (errors.length > 0)
|
|
190
|
+
return { ok: false, errors, cleaned };
|
|
191
|
+
return { ok: true, cleaned };
|
|
192
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FormLayout, SeedViewConfig } from './seed-layout.js';
|
|
2
|
+
export interface SeedLayoutRecord {
|
|
3
|
+
slug: string;
|
|
4
|
+
layout: FormLayout;
|
|
5
|
+
updatedAt: number;
|
|
6
|
+
updatedBy: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ISeedLayoutRepository {
|
|
9
|
+
/** Return the stored layout for a seed, or null if none was ever saved. */
|
|
10
|
+
get(slug: string): Promise<SeedLayoutRecord | null>;
|
|
11
|
+
/** Return all stored layouts, keyed by slug — used by GET /api/schema to enrich. */
|
|
12
|
+
getAllAsMap(): Promise<Map<string, FormLayout>>;
|
|
13
|
+
/** Upsert. `updatedBy` is the writer's user id. */
|
|
14
|
+
upsert(slug: string, layout: FormLayout, updatedBy: string): Promise<void>;
|
|
15
|
+
/** Remove the stored row — used by the "Reset" action. */
|
|
16
|
+
remove(slug: string): Promise<void>;
|
|
17
|
+
/** Return the per-view config blob for a seed, or null if none was stored. */
|
|
18
|
+
getViewConfig(slug: string): Promise<SeedViewConfig | null>;
|
|
19
|
+
/** Upsert the per-view config for a seed. */
|
|
20
|
+
setViewConfig(slug: string, config: SeedViewConfig, updatedBy: string): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=seed-layout.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed-layout.repository.d.ts","sourceRoot":"","sources":["../src/seed-layout.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAElE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IACnD,oFAAoF;IACpF,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;IAC/C,mDAAmD;IACnD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1E,0DAA0D;IAC1D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,8EAA8E;IAC9E,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAA;IAC3D,6CAA6C;IAC7C,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACtF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Seed, Branch } from './types.js';
|
|
2
|
+
export interface ISeedRegistry {
|
|
3
|
+
/**
|
|
4
|
+
* Returns all seeds as a flat array, preserving insertion order.
|
|
5
|
+
* Decouples callers from the internal storage shape so the registry
|
|
6
|
+
* implementation can change without touching every route handler.
|
|
7
|
+
*/
|
|
8
|
+
all(): Seed[];
|
|
9
|
+
/**
|
|
10
|
+
* Returns the seed with the given slug, or null if not found.
|
|
11
|
+
* Provides a single lookup point that can be overridden in tests
|
|
12
|
+
* without rebuilding the full registry object.
|
|
13
|
+
*/
|
|
14
|
+
get(slug: string): Seed | null;
|
|
15
|
+
/**
|
|
16
|
+
* Returns seeds that are visible in the dashboard sidebar.
|
|
17
|
+
* A seed is visible when dashboard.hidden is not explicitly true.
|
|
18
|
+
* Eliminates the seeds.filter(s => !s.dashboard?.hidden) pattern
|
|
19
|
+
* that would otherwise be duplicated across route handlers.
|
|
20
|
+
*/
|
|
21
|
+
visibleInDashboard(): Seed[];
|
|
22
|
+
/**
|
|
23
|
+
* Returns seeds that have allowPublicRead enabled.
|
|
24
|
+
* Eliminates the seeds.filter(s => s.allowPublicRead) pattern.
|
|
25
|
+
*/
|
|
26
|
+
publicReadable(): Seed[];
|
|
27
|
+
/**
|
|
28
|
+
* Returns seeds that have the draft workflow enabled.
|
|
29
|
+
* Eliminates the seeds.filter(s => s.allowDrafts) pattern.
|
|
30
|
+
*/
|
|
31
|
+
draftEnabled(): Seed[];
|
|
32
|
+
}
|
|
33
|
+
export declare class SeedRegistry implements ISeedRegistry {
|
|
34
|
+
private readonly seedMap;
|
|
35
|
+
private readonly orderedSeeds;
|
|
36
|
+
constructor(seeds: Seed[]);
|
|
37
|
+
all(): Seed[];
|
|
38
|
+
get(slug: string): Seed | null;
|
|
39
|
+
visibleInDashboard(): Seed[];
|
|
40
|
+
publicReadable(): Seed[];
|
|
41
|
+
draftEnabled(): Seed[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Named subclass of SeedRegistry for use in test suites.
|
|
45
|
+
* Gives tests a semantic name without requiring any dependency on factory.ts.
|
|
46
|
+
*/
|
|
47
|
+
export declare class InMemorySeedRegistry extends SeedRegistry {
|
|
48
|
+
}
|
|
49
|
+
/** Finds a branch by its stable id within a Seed. Returns null if not found. */
|
|
50
|
+
export declare function findBranchById(seed: Seed, id: string): Branch | null;
|
|
51
|
+
/**
|
|
52
|
+
* Returns the next free branch id for a seed in the form `br_NN` (zero-padded to 2,
|
|
53
|
+
* growing as needed). Scans existing ids, never reuses a number already present —
|
|
54
|
+
* even if a branch was removed, its id is not recycled (ids must be globally stable
|
|
55
|
+
* for the life of the seed so layouts/automations/FTS triggers never collide).
|
|
56
|
+
*/
|
|
57
|
+
export declare function nextBranchId(seed: Pick<Seed, 'branches'>): string;
|
|
58
|
+
/**
|
|
59
|
+
* Returns the input seeds reordered so that every seed appears AFTER all the
|
|
60
|
+
* seeds it depends on (its `relation` branch targets). Pure function — does
|
|
61
|
+
* not read from any global registry.
|
|
62
|
+
*
|
|
63
|
+
* Uses Kahn's algorithm (BFS on in-degree) for cycle detection and topological
|
|
64
|
+
* ordering. Sprint 3 (CLI / migration runner) consumes this to create tables in
|
|
65
|
+
* the correct order without disabling SQLite FK constraints.
|
|
66
|
+
*
|
|
67
|
+
* @throws {Error} When a relation targets an unknown slug not in `seeds`.
|
|
68
|
+
* @throws {Error} When a cyclic dependency is detected, listing involved slugs.
|
|
69
|
+
*/
|
|
70
|
+
export declare function sortSeedsByDependencies(seeds: ReadonlyArray<Seed>): Seed[];
|
|
71
|
+
//# sourceMappingURL=seed-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed-registry.d.ts","sourceRoot":"","sources":["../src/seed-registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAG9C,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,GAAG,IAAI,IAAI,EAAE,CAAA;IAEb;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAE9B;;;;;OAKG;IACH,kBAAkB,IAAI,IAAI,EAAE,CAAA;IAE5B;;;OAGG;IACH,cAAc,IAAI,IAAI,EAAE,CAAA;IAExB;;;OAGG;IACH,YAAY,IAAI,IAAI,EAAE,CAAA;CACvB;AAID,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;gBAEzB,KAAK,EAAE,IAAI,EAAE;IA4BzB,GAAG,IAAI,IAAI,EAAE;IAIb,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI9B,kBAAkB,IAAI,IAAI,EAAE;IAI5B,cAAc,IAAI,IAAI,EAAE;IAIxB,YAAY,IAAI,IAAI,EAAE;CAGvB;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;CAAG;AAEzD,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,MAAM,CAQjE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAsE1E"}
|