@beechcms/core 0.8.1 → 0.9.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.
Files changed (163) hide show
  1. package/dist/antivirus/noop-antivirus.provider.d.ts.map +1 -1
  2. package/dist/antivirus/virustotal-antivirus.provider.d.ts.map +1 -1
  3. package/dist/auth/account-email.d.ts +3 -0
  4. package/dist/auth/account-email.d.ts.map +1 -0
  5. package/dist/auth/account-email.js +9 -0
  6. package/dist/auth/password-reset-token.repository.d.ts +13 -2
  7. package/dist/auth/password-reset-token.repository.d.ts.map +1 -1
  8. package/dist/auth/user.repository.d.ts +69 -4
  9. package/dist/auth/user.repository.d.ts.map +1 -1
  10. package/dist/common/canonical-json.d.ts +2 -1
  11. package/dist/common/canonical-json.d.ts.map +1 -1
  12. package/dist/common/canonical-json.js +5 -3
  13. package/dist/content/content-scan.repository.d.ts +1 -1
  14. package/dist/content/content.repository.d.ts +156 -5
  15. package/dist/content/content.repository.d.ts.map +1 -1
  16. package/dist/content/content.repository.js +62 -0
  17. package/dist/content/seed-media-purge.repository.d.ts +34 -0
  18. package/dist/content/seed-media-purge.repository.d.ts.map +1 -0
  19. package/dist/content/seed-media-purge.repository.js +3 -0
  20. package/dist/content/seed.repository.d.ts +14 -0
  21. package/dist/content/seed.repository.d.ts.map +1 -1
  22. package/dist/dashboard-layout/content-view.d.ts +757 -0
  23. package/dist/dashboard-layout/content-view.d.ts.map +1 -0
  24. package/dist/dashboard-layout/content-view.js +285 -0
  25. package/dist/dashboard-layout/content-view.repository.d.ts +39 -0
  26. package/dist/dashboard-layout/content-view.repository.d.ts.map +1 -0
  27. package/dist/dashboard-layout/content-view.repository.js +3 -0
  28. package/dist/dashboard-layout/dashboard-layout.d.ts +5 -0
  29. package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -1
  30. package/dist/dashboard-layout/dashboard-layout.js +17 -9
  31. package/dist/dashboard-layout/kanban/kanban.d.ts +32 -18
  32. package/dist/dashboard-layout/kanban/kanban.d.ts.map +1 -1
  33. package/dist/dashboard-layout/kanban/kanban.js +45 -21
  34. package/dist/dashboard-layout/seed-layout.d.ts +22 -31
  35. package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
  36. package/dist/dashboard-layout/seed-layout.js +93 -18
  37. package/dist/dashboard-layout/seed-layout.repository.d.ts +1 -5
  38. package/dist/dashboard-layout/seed-layout.repository.d.ts.map +1 -1
  39. package/dist/engine/ddl/ddl.d.ts +31 -0
  40. package/dist/engine/ddl/ddl.d.ts.map +1 -1
  41. package/dist/engine/ddl/ddl.js +92 -13
  42. package/dist/engine/ddl/schema-mutator.d.ts +3 -0
  43. package/dist/engine/ddl/schema-mutator.d.ts.map +1 -1
  44. package/dist/engine/ddl/seed-purge-guards.d.ts +7 -0
  45. package/dist/engine/ddl/seed-purge-guards.d.ts.map +1 -0
  46. package/dist/engine/ddl/seed-purge-guards.js +19 -0
  47. package/dist/engine/engine.d.ts +4 -3
  48. package/dist/engine/engine.d.ts.map +1 -1
  49. package/dist/engine/engine.js +4 -3
  50. package/dist/engine/introspection/relation-audit.d.ts +46 -0
  51. package/dist/engine/introspection/relation-audit.d.ts.map +1 -0
  52. package/dist/engine/introspection/relation-audit.js +103 -0
  53. package/dist/engine/introspection/schema-fingerprint.d.ts +2 -1
  54. package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -1
  55. package/dist/engine/introspection/schema-fingerprint.js +8 -3
  56. package/dist/engine/privacy/policies.d.ts +1 -1
  57. package/dist/engine/privacy/policies.d.ts.map +1 -1
  58. package/dist/engine/privacy/policies.js +12 -6
  59. package/dist/engine/privacy/privacy.service.d.ts +13 -0
  60. package/dist/engine/privacy/privacy.service.d.ts.map +1 -1
  61. package/dist/engine/privacy/privacy.service.js +24 -0
  62. package/dist/engine/query/active-clause.d.ts +19 -0
  63. package/dist/engine/query/active-clause.d.ts.map +1 -0
  64. package/dist/engine/query/active-clause.js +27 -0
  65. package/dist/engine/query/query.d.ts +14 -1
  66. package/dist/engine/query/query.d.ts.map +1 -1
  67. package/dist/engine/query/query.js +105 -13
  68. package/dist/engine/seeds/candidate-ids.d.ts +14 -0
  69. package/dist/engine/seeds/candidate-ids.d.ts.map +1 -0
  70. package/dist/engine/seeds/candidate-ids.js +40 -0
  71. package/dist/engine/seeds/seed-definition-diff.d.ts +38 -0
  72. package/dist/engine/seeds/seed-definition-diff.d.ts.map +1 -0
  73. package/dist/engine/seeds/seed-definition-diff.js +51 -0
  74. package/dist/engine/seeds/seed-registry.d.ts +17 -0
  75. package/dist/engine/seeds/seed-registry.d.ts.map +1 -1
  76. package/dist/engine/seeds/seed-registry.js +27 -0
  77. package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -1
  78. package/dist/engine/seeds/seed-types-generator.js +4 -5
  79. package/dist/engine/seeds/seed-validation.d.ts +5 -0
  80. package/dist/engine/seeds/seed-validation.d.ts.map +1 -1
  81. package/dist/engine/seeds/seed-validation.js +39 -19
  82. package/dist/engine/seeds/tag-migration.d.ts +73 -0
  83. package/dist/engine/seeds/tag-migration.d.ts.map +1 -0
  84. package/dist/engine/seeds/tag-migration.js +212 -0
  85. package/dist/engine/seeds/tag-options.d.ts +15 -0
  86. package/dist/engine/seeds/tag-options.d.ts.map +1 -0
  87. package/dist/engine/seeds/tag-options.js +111 -0
  88. package/dist/engine/seeds/tag-value.d.ts +31 -0
  89. package/dist/engine/seeds/tag-value.d.ts.map +1 -0
  90. package/dist/engine/seeds/tag-value.js +51 -0
  91. package/dist/engine/types.d.ts +59 -7
  92. package/dist/engine/types.d.ts.map +1 -1
  93. package/dist/engine/validation/cache.d.ts.map +1 -1
  94. package/dist/engine/validation/cache.js +20 -4
  95. package/dist/engine/validation/index.d.ts +12 -1
  96. package/dist/engine/validation/index.d.ts.map +1 -1
  97. package/dist/engine/validation/index.js +29 -33
  98. package/dist/engine/validation/primitives.d.ts +12 -0
  99. package/dist/engine/validation/primitives.d.ts.map +1 -1
  100. package/dist/engine/validation/primitives.js +22 -0
  101. package/dist/engine/validation/richtext-sanitizer.d.ts +9 -1
  102. package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
  103. package/dist/engine/validation/richtext-sanitizer.js +36 -7
  104. package/dist/engine/validation/schema-builders.d.ts +16 -0
  105. package/dist/engine/validation/schema-builders.d.ts.map +1 -1
  106. package/dist/engine/validation/schema-builders.js +125 -18
  107. package/dist/index.d.ts +34 -11
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +34 -11
  110. package/dist/media/file-types.d.ts +12 -0
  111. package/dist/media/file-types.d.ts.map +1 -1
  112. package/dist/media/file-types.js +24 -4
  113. package/dist/media/magic-bytes.d.ts.map +1 -1
  114. package/dist/media/magic-bytes.js +19 -9
  115. package/dist/media/media-transform.d.ts +8 -4
  116. package/dist/media/media-transform.d.ts.map +1 -1
  117. package/dist/media/media-transform.js +10 -6
  118. package/dist/media/media.repository.d.ts +5 -0
  119. package/dist/media/media.repository.d.ts.map +1 -1
  120. package/dist/notifications/notification-service.d.ts +1 -1
  121. package/dist/notifications/notification.repository.d.ts +11 -2
  122. package/dist/notifications/notification.repository.d.ts.map +1 -1
  123. package/dist/oauth/authorization-code.repository.d.ts +10 -5
  124. package/dist/oauth/authorization-code.repository.d.ts.map +1 -1
  125. package/dist/oauth/pkce.d.ts +1 -1
  126. package/dist/oauth/role-guard.d.ts +30 -11
  127. package/dist/oauth/role-guard.d.ts.map +1 -1
  128. package/dist/oauth/role-guard.js +27 -5
  129. package/dist/oauth/scopes.d.ts +1 -1
  130. package/dist/oauth/scopes.d.ts.map +1 -1
  131. package/dist/oauth/token.repository.d.ts +2 -0
  132. package/dist/oauth/token.repository.d.ts.map +1 -1
  133. package/dist/queue/queue.interface.d.ts +9 -1
  134. package/dist/queue/queue.interface.d.ts.map +1 -1
  135. package/dist/queue/queue.stub.d.ts +1 -1
  136. package/dist/queue/queue.stub.d.ts.map +1 -1
  137. package/dist/queue/queue.stub.js +2 -3
  138. package/dist/queue/scheduler.stub.d.ts.map +1 -1
  139. package/dist/rate-limit/token-bucket-rate-limiter.d.ts +7 -0
  140. package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -1
  141. package/dist/rate-limit/token-bucket-rate-limiter.js +66 -0
  142. package/dist/rbac/invitation.repository.d.ts +28 -10
  143. package/dist/rbac/invitation.repository.d.ts.map +1 -1
  144. package/dist/rbac/types.d.ts +40 -28
  145. package/dist/rbac/types.d.ts.map +1 -1
  146. package/dist/rbac/types.js +2 -1
  147. package/dist/schema/manifest-seeds.js +3 -3
  148. package/dist/search/search.repository.d.ts +5 -4
  149. package/dist/search/search.repository.d.ts.map +1 -1
  150. package/dist/search/vector-extractor.d.ts.map +1 -1
  151. package/dist/search/vector-extractor.js +23 -2
  152. package/dist/search/vector.repository.d.ts +2 -1
  153. package/dist/search/vector.repository.d.ts.map +1 -1
  154. package/dist/security/time-trap-token.repository.d.ts +5 -3
  155. package/dist/security/time-trap-token.repository.d.ts.map +1 -1
  156. package/dist/settings/site-settings.repository.d.ts +8 -0
  157. package/dist/settings/site-settings.repository.d.ts.map +1 -1
  158. package/dist/transfer/flat-seed.d.ts.map +1 -1
  159. package/dist/transfer/flat-seed.js +6 -1
  160. package/dist/transfer/row-mapping.d.ts.map +1 -1
  161. package/dist/transfer/row-mapping.js +9 -2
  162. package/package.json +8 -3
  163. package/dist/.tsbuildinfo +0 -1
@@ -33,36 +33,6 @@ export declare const kanbanCardConfigSchema: z.ZodObject<{
33
33
  }, z.core.$strip>>>;
34
34
  }, z.core.$strip>;
35
35
  export type KanbanCardConfig = z.infer<typeof kanbanCardConfigSchema>;
36
- export declare const seedViewConfigSchema: z.ZodObject<{
37
- kanban: z.ZodOptional<z.ZodObject<{
38
- axisBranchId: z.ZodNullable<z.ZodString>;
39
- sort: z.ZodNullable<z.ZodObject<{
40
- branchId: z.ZodString;
41
- dir: z.ZodEnum<{
42
- ASC: "ASC";
43
- DESC: "DESC";
44
- }>;
45
- }, z.core.$strip>>;
46
- hiddenColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
47
- collapsedColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
48
- }, z.core.$strip>>;
49
- card: z.ZodOptional<z.ZodObject<{
50
- version: z.ZodLiteral<1>;
51
- media: z.ZodOptional<z.ZodNullable<z.ZodObject<{
52
- branchId: z.ZodString;
53
- }, z.core.$strip>>>;
54
- header: z.ZodOptional<z.ZodNullable<z.ZodObject<{
55
- branchId: z.ZodString;
56
- }, z.core.$strip>>>;
57
- subtitle: z.ZodOptional<z.ZodNullable<z.ZodObject<{
58
- branchId: z.ZodString;
59
- }, z.core.$strip>>>;
60
- metadata: z.ZodDefault<z.ZodArray<z.ZodObject<{
61
- branchId: z.ZodString;
62
- }, z.core.$strip>>>;
63
- }, z.core.$strip>>;
64
- }, z.core.$loose>;
65
- export type SeedViewConfig = z.infer<typeof seedViewConfigSchema>;
66
36
  /** Reference to a Seed branch by its stable id (e.g. 'br_03').
67
37
  * Aliases can be renamed — ids cannot. When the id does not match any
68
38
  * branch on the Seed at read time, the field is silently stripped (auto-cleanup). */
@@ -108,6 +78,12 @@ export declare function isFullWidthBranch(branch: Branch): boolean;
108
78
  export declare function isSeoBranch(branch: Branch): boolean;
109
79
  export declare const METADATA_SLOT_CAP = 6;
110
80
  export declare function isCardEligibleBranch(branch: Branch): boolean;
81
+ /** Gallery cover: a single-file branch that accepts images (a multi-file/asset-list branch has no single cover). */
82
+ export declare function isImageMediaBranch(branch: Branch): boolean;
83
+ export interface ValidateCardConfigOptions {
84
+ /** Gallery cards only render an image cover: a non-image `media` slot is dropped. */
85
+ mediaImageOnly?: boolean;
86
+ }
111
87
  export type ValidateCardConfigResult = {
112
88
  ok: true;
113
89
  cleaned: KanbanCardConfig;
@@ -116,7 +92,9 @@ export type ValidateCardConfigResult = {
116
92
  errors: string[];
117
93
  cleaned: KanbanCardConfig;
118
94
  };
119
- export declare function validateCardConfigAgainstSeed(config: KanbanCardConfig, seed: Seed): ValidateCardConfigResult;
95
+ export declare function validateCardConfigAgainstSeed(config: KanbanCardConfig, seed: Seed, options?: ValidateCardConfigOptions): ValidateCardConfigResult;
96
+ /** Max serialized size of a form layout. Keeps the `seed_layouts` row far below D1's per-row limit. */
97
+ export declare const MAX_LAYOUT_BYTES: number;
120
98
  export declare const layoutFieldSchema: z.ZodObject<{
121
99
  branchId: z.ZodString;
122
100
  }, z.core.$strip>;
@@ -176,6 +154,12 @@ export declare const formLayoutSchema: z.ZodObject<{
176
154
  }, z.core.$strip>>;
177
155
  }, z.core.$strip>>;
178
156
  }, z.core.$strip>;
157
+ /**
158
+ * Builds the default editor layout for a Seed with no custom `layout`: full-width branches
159
+ * (richtext, json, galleries) each get their own dedicated section, the remaining branches are
160
+ * packed three per section in seed order, and a single main non-gallery image file branch
161
+ * (`fileOptions.accept === 'image'`) leads the Data tab alone in its own full-width section.
162
+ */
179
163
  export declare function generateDefaultLayout(seed: Seed, opts?: {
180
164
  newId: () => string;
181
165
  }): FormLayout;
@@ -188,4 +172,11 @@ export type ValidateLayoutResult = {
188
172
  cleaned: FormLayout;
189
173
  };
190
174
  export declare function validateLayoutAgainstSeed(layout: FormLayout, seed: Seed): ValidateLayoutResult;
175
+ /**
176
+ * Makes a stored layout safe to serve after the Seed changed. A layout that breaks the
177
+ * full-width rules falls back to the generated default; otherwise layoutable branches the
178
+ * layout never placed (added to the Seed later) are appended as trailing sections of the
179
+ * first tab, so the editor can always render and fill them.
180
+ */
181
+ export declare function reconcileLayoutWithSeed(layout: FormLayout, seed: Seed): FormLayout;
191
182
  //# sourceMappingURL=seed-layout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"seed-layout.d.ts","sourceRoot":"","sources":["../../src/dashboard-layout/seed-layout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seed-layout.d.ts","sourceRoot":"","sources":["../../src/dashboard-layout/seed-layout.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
3
  import { z } from 'zod';
4
- import { findBranchById } from '../engine/seed-registry.js';
4
+ import { findBranchById } from '../engine/seeds/seed-registry.js';
5
5
  // ---------------------------------------------------------------------------
6
- // View config — per-seed, per-view dashboard preferences (KB-S02)
6
+ // Kanban view config — embedded in ContentViewConfig.kanban (seed_views)
7
7
  // ---------------------------------------------------------------------------
8
8
  export const kanbanViewConfigSchema = z.object({
9
9
  axisBranchId: z.string().nullable(),
@@ -12,7 +12,7 @@ export const kanbanViewConfigSchema = z.object({
12
12
  collapsedColumnValues: z.array(z.string()).optional(),
13
13
  });
14
14
  // ---------------------------------------------------------------------------
15
- // Kanban card layout (view_config.card)
15
+ // Kanban card layout — embedded in ContentViewConfig.card (seed_views)
16
16
  // ---------------------------------------------------------------------------
17
17
  export const cardSlotFieldSchema = z.object({
18
18
  branchId: z.string().regex(/^br_[A-Za-z0-9]+$/),
@@ -28,10 +28,6 @@ export const kanbanCardConfigSchema = z.object({
28
28
  /** 2-column grid. Hard cap enforced by validator (see METADATA_SLOT_CAP). */
29
29
  metadata: z.array(cardSlotFieldSchema).max(6).default([]),
30
30
  });
31
- export const seedViewConfigSchema = z.object({
32
- kanban: kanbanViewConfigSchema.optional(),
33
- card: kanbanCardConfigSchema.optional(),
34
- }).passthrough();
35
31
  // ---------------------------------------------------------------------------
36
32
  // Branch-type rules used by both the default generator and the validator.
37
33
  // ---------------------------------------------------------------------------
@@ -42,6 +38,12 @@ export function isGalleryBranch(branch) {
42
38
  return branch.type === 'file'
43
39
  && (branch.multiple === true || branch.format === 'asset-list');
44
40
  }
41
+ /** True for a single-file image branch: the candidate cover of the default editor layout. */
42
+ function isCoverImageBranch(branch) {
43
+ return branch.type === 'file'
44
+ && !isGalleryBranch(branch)
45
+ && branch.fileOptions?.accept === 'image';
46
+ }
45
47
  /** Branch types currently unsupported in the Layout Builder. */
46
48
  export const UNSUPPORTED_BRANCH_TYPES = new Set([]);
47
49
  /** Aliases that are NEVER included in the editor form (handled by other UI). */
@@ -80,7 +82,14 @@ export function isCardEligibleBranch(branch) {
80
82
  return false;
81
83
  return true;
82
84
  }
83
- export function validateCardConfigAgainstSeed(config, seed) {
85
+ /** Gallery cover: a single-file branch that accepts images (a multi-file/asset-list branch has no single cover). */
86
+ export function isImageMediaBranch(branch) {
87
+ return isCardEligibleBranch(branch)
88
+ && branch.type === 'file'
89
+ && !isGalleryBranch(branch)
90
+ && branch.fileOptions?.accept === 'image';
91
+ }
92
+ export function validateCardConfigAgainstSeed(config, seed, options = {}) {
84
93
  const errors = [];
85
94
  const seen = new Set();
86
95
  const keepSingle = (f) => {
@@ -96,7 +105,9 @@ export function validateCardConfigAgainstSeed(config, seed) {
96
105
  seen.add(f.branchId);
97
106
  return { branchId: f.branchId };
98
107
  };
99
- const media = keepSingle(config.media);
108
+ const mediaBranch = config.media ? findBranchById(seed, config.media.branchId) : null;
109
+ const mediaAllowed = !options.mediaImageOnly || (mediaBranch !== null && isImageMediaBranch(mediaBranch));
110
+ const media = mediaAllowed ? keepSingle(config.media) : null;
100
111
  const header = keepSingle(config.header);
101
112
  const subtitle = keepSingle(config.subtitle);
102
113
  const metadata = [];
@@ -121,27 +132,37 @@ export function validateCardConfigAgainstSeed(config, seed) {
121
132
  // ---------------------------------------------------------------------------
122
133
  // Zod schema for shape validation
123
134
  // ---------------------------------------------------------------------------
135
+ /** Max serialized size of a form layout. Keeps the `seed_layouts` row far below D1's per-row limit. */
136
+ export const MAX_LAYOUT_BYTES = 256 * 1024;
137
+ const LAYOUT_ID_MAX = 64;
138
+ const LAYOUT_LABEL_MAX = 120;
139
+ const MAX_SECTIONS_PER_TAB = 50;
140
+ const MAX_FIELDS_PER_COLUMN = 100;
124
141
  export const layoutFieldSchema = z.object({ branchId: z.string().regex(/^br_[A-Za-z0-9]+$/) });
125
142
  export const layoutColumnSchema = z.object({
126
- id: z.string().min(1),
127
- fields: z.array(layoutFieldSchema),
143
+ id: z.string().min(1).max(LAYOUT_ID_MAX),
144
+ fields: z.array(layoutFieldSchema).max(MAX_FIELDS_PER_COLUMN),
128
145
  });
129
146
  export const layoutSectionSchema = z.object({
130
- id: z.string().min(1),
131
- label: z.string().optional(),
147
+ id: z.string().min(1).max(LAYOUT_ID_MAX),
148
+ label: z.string().max(LAYOUT_LABEL_MAX).optional(),
132
149
  hideLabel: z.boolean().optional(),
133
150
  hideBorder: z.boolean().optional(),
134
151
  collapsible: z.boolean().optional(),
135
152
  columns: z.array(layoutColumnSchema).min(1).max(4),
136
153
  });
137
154
  export const layoutTabSchema = z.object({
138
- id: z.string().min(1),
155
+ id: z.string().min(1).max(LAYOUT_ID_MAX),
139
156
  label: z.string().min(1).max(60),
140
- sections: z.array(layoutSectionSchema).min(1),
157
+ sections: z.array(layoutSectionSchema).min(1).max(MAX_SECTIONS_PER_TAB),
141
158
  });
142
- export const formLayoutSchema = z.object({
159
+ export const formLayoutSchema = z
160
+ .object({
143
161
  version: z.literal(1),
144
162
  tabs: z.array(layoutTabSchema).min(1).max(8),
163
+ })
164
+ .refine((layout) => new TextEncoder().encode(JSON.stringify(layout)).length <= MAX_LAYOUT_BYTES, {
165
+ message: `Layout exceeds the ${MAX_LAYOUT_BYTES / 1024} KB size limit.`,
145
166
  });
146
167
  // ---------------------------------------------------------------------------
147
168
  // Default layout generator
@@ -180,15 +201,40 @@ function buildSectionsForBranches(branches, newId) {
180
201
  }
181
202
  return sections;
182
203
  }
204
+ /**
205
+ * Builds the default editor layout for a Seed with no custom `layout`: full-width branches
206
+ * (richtext, json, galleries) each get their own dedicated section, the remaining branches are
207
+ * packed three per section in seed order, and a single main non-gallery image file branch
208
+ * (`fileOptions.accept === 'image'`) leads the Data tab alone in its own full-width section.
209
+ */
183
210
  export function generateDefaultLayout(seed, opts) {
184
211
  const newId = opts?.newId ?? defaultIdFactory;
185
212
  const layoutable = seed.branches.filter(isLayoutableBranch);
186
213
  const seo = layoutable.filter(isSeoBranch);
187
214
  const main = layoutable.filter((b) => !isSeoBranch(b));
215
+ const coverCandidates = main.filter(isCoverImageBranch);
216
+ const cover = coverCandidates.length === 1 ? coverCandidates[0] : null;
217
+ const dataTabId = newId();
218
+ let dataSections;
219
+ if (cover) {
220
+ const coverSection = {
221
+ id: newId(),
222
+ label: cover.label,
223
+ hideLabel: true,
224
+ columns: [{ id: newId(), fields: [{ branchId: cover.id }] }],
225
+ };
226
+ const rest = main.filter((b) => b.id !== cover.id);
227
+ dataSections = rest.length === 0
228
+ ? [coverSection]
229
+ : [coverSection, ...buildSectionsForBranches(rest, newId)];
230
+ }
231
+ else {
232
+ dataSections = buildSectionsForBranches(main, newId);
233
+ }
188
234
  const dataTab = {
189
- id: newId(),
235
+ id: dataTabId,
190
236
  label: 'Data',
191
- sections: buildSectionsForBranches(main, newId),
237
+ sections: dataSections,
192
238
  };
193
239
  const seoTab = {
194
240
  id: newId(),
@@ -267,3 +313,32 @@ export function validateLayoutAgainstSeed(layout, seed) {
267
313
  return { ok: false, errors, cleaned };
268
314
  return { ok: true, cleaned };
269
315
  }
316
+ /**
317
+ * Makes a stored layout safe to serve after the Seed changed. A layout that breaks the
318
+ * full-width rules falls back to the generated default; otherwise layoutable branches the
319
+ * layout never placed (added to the Seed later) are appended as trailing sections of the
320
+ * first tab, so the editor can always render and fill them.
321
+ */
322
+ export function reconcileLayoutWithSeed(layout, seed) {
323
+ const result = validateLayoutAgainstSeed(layout, seed);
324
+ if (!result.ok)
325
+ return generateDefaultLayout(seed);
326
+ const placed = new Set();
327
+ for (const tab of result.cleaned.tabs) {
328
+ for (const section of tab.sections) {
329
+ for (const column of section.columns) {
330
+ for (const field of column.fields)
331
+ placed.add(field.branchId);
332
+ }
333
+ }
334
+ }
335
+ const unplaced = seed.branches.filter((b) => isLayoutableBranch(b) && !placed.has(b.id));
336
+ if (unplaced.length === 0)
337
+ return result.cleaned;
338
+ const [firstTab, ...otherTabs] = result.cleaned.tabs;
339
+ const extended = {
340
+ ...firstTab,
341
+ sections: [...firstTab.sections, ...buildSectionsForBranches(unplaced, defaultIdFactory)],
342
+ };
343
+ return { ...result.cleaned, tabs: [extended, ...otherTabs] };
344
+ }
@@ -1,4 +1,4 @@
1
- import type { FormLayout, SeedViewConfig } from './seed-layout.js';
1
+ import type { FormLayout } from './seed-layout.js';
2
2
  export interface SeedLayoutRecord {
3
3
  slug: string;
4
4
  layout: FormLayout;
@@ -14,9 +14,5 @@ export interface ISeedLayoutRepository {
14
14
  upsert(slug: string, layout: FormLayout, updatedBy: string): Promise<void>;
15
15
  /** Remove the stored row — used by the "Reset" action. */
16
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
17
  }
22
18
  //# sourceMappingURL=seed-layout.repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"seed-layout.repository.d.ts","sourceRoot":"","sources":["../../src/dashboard-layout/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"}
1
+ {"version":3,"file":"seed-layout.repository.d.ts","sourceRoot":"","sources":["../../src/dashboard-layout/seed-layout.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD,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;CACpC"}
@@ -15,6 +15,13 @@ export declare const SYSTEM_COLUMNS: Set<string>;
15
15
  * @returns The table name.
16
16
  */
17
17
  export declare function tableName(seed: Seed): string;
18
+ /**
19
+ * Returns the draft SQL table name for a given Seed.
20
+ *
21
+ * @param seed The seed definition.
22
+ * @returns The draft table name.
23
+ */
24
+ export declare function draftTableName(seed: Seed): string;
18
25
  /**
19
26
  * Returns the FTS (Full-Text Search) virtual table name for a given Seed.
20
27
  *
@@ -184,6 +191,17 @@ export declare function generateDropTable(seed: Seed): string[];
184
191
  * @returns An array of SQL statements.
185
192
  */
186
193
  export declare function generateDropColumn(seed: Seed, alias: string): string[];
194
+ /**
195
+ * Returns the SQL statements that drop an orphan column: one left in the physical table after its
196
+ * branch was removed from the definition. Removes the conventional filter indexes first, since
197
+ * SQLite refuses to drop an indexed column.
198
+ *
199
+ * @param seed The seed definition (the column must not belong to it).
200
+ * @param column The orphan column name.
201
+ * @param inDraftTable Whether the draft table also holds the column.
202
+ * @returns An array of SQL statements.
203
+ */
204
+ export declare function generateDropOrphanColumn(seed: Seed, column: string, inDraftTable: boolean): string[];
187
205
  /**
188
206
  * Returns the `RENAME COLUMN` SQL statements for renaming a field's alias.
189
207
  * For a multi-relation branch, renames the junction table (+ drafts junction table) instead.
@@ -203,6 +221,19 @@ export declare function generateRenameColumn(seed: Seed, from: string, to: strin
203
221
  * @returns An array of SQL statements.
204
222
  */
205
223
  export declare function generateRetypeColumn(seed: Seed, branch: Branch): string[];
224
+ /**
225
+ * Returns `SELECT COUNT(*)` statements counting rows whose current value would be silently altered
226
+ * (zeroed or truncated) by the `CAST` in {@link generateRetypeColumn}. Empty when the target type
227
+ * is lossless for any input (`TEXT`).
228
+ *
229
+ * A text value is convertible only if it is a complete JSON number; `CAST` alone accepts trailing garbage.
230
+ *
231
+ * @param seed The seed definition.
232
+ * @param branch The target branch definition (carrying the new type).
233
+ * @throws {Error} If called on a multi-relation branch.
234
+ * @returns An array of SQL statements, each yielding a single `count` column.
235
+ */
236
+ export declare function generateRetypeIncompatibleCount(seed: Seed, branch: Branch): string[];
206
237
  /**
207
238
  * Additive statements that turn soft delete on for a table that already exists.
208
239
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../../src/engine/ddl/ddl.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../../src/engine/ddl/ddl.ts"],"names":[],"mappings":"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"}
@@ -41,7 +41,7 @@ export function tableName(seed) {
41
41
  * @param seed The seed definition.
42
42
  * @returns The draft table name.
43
43
  */
44
- function draftTableName(seed) {
44
+ export function draftTableName(seed) {
45
45
  return `content_${seed.slug}_drafts`;
46
46
  }
47
47
  /**
@@ -221,6 +221,37 @@ export function generateAddColumn(seed, branch) {
221
221
  export function generateAddDraftSnapshotColumn(seed) {
222
222
  return `ALTER TABLE content_${seed.slug}_drafts ADD COLUMN live_snapshot_at INTEGER;`;
223
223
  }
224
+ /**
225
+ * Lists the indexes a branch column carries (value index + blind index), as name/column pairs.
226
+ * Single source for both index creation and column teardown.
227
+ */
228
+ function branchIndexes(seed, branch) {
229
+ if (branch.type === 'relation' && branch.multiple === true)
230
+ return [];
231
+ const isRelation = branch.type === 'relation';
232
+ if (!isRelation && !resolvePolicies(branch).filter)
233
+ return [];
234
+ const indexes = [];
235
+ if (['text', 'number', 'date', 'boolean', 'relation'].includes(branch.type)) {
236
+ indexes.push({ name: `idx_${seed.slug}_${branch.alias}`, column: branch.alias });
237
+ }
238
+ if (hasBlindIndex(branch)) {
239
+ indexes.push({ name: `idx_${seed.slug}_${branch.alias}_bidx`, column: `${branch.alias}_bidx` });
240
+ }
241
+ return indexes;
242
+ }
243
+ /**
244
+ * Statements that detach a column from everything SQLite refuses to drop it from:
245
+ * its indexes and, for searchable branches, the FTS table + triggers (rebuilt by `planFtsRebuild`).
246
+ */
247
+ function generateColumnTeardown(seed, branch) {
248
+ const slug = seed.slug;
249
+ const stmts = branchIndexes(seed, branch).map(i => `DROP INDEX IF EXISTS ${i.name};`);
250
+ if (indexableSearchBranches(seed).some(b => b.alias === branch.alias)) {
251
+ stmts.push(`DROP TRIGGER IF EXISTS fts_${slug}_insert;`, `DROP TRIGGER IF EXISTS fts_${slug}_update;`, `DROP TRIGGER IF EXISTS fts_${slug}_delete;`, `DROP TABLE IF EXISTS ${ftsTableName(seed)};`);
252
+ }
253
+ return stmts;
254
+ }
224
255
  /**
225
256
  * Generates SQL index statements for system fields (status, created_at)
226
257
  * and indexable branch columns.
@@ -236,16 +267,8 @@ export function generateIndexes(seed) {
236
267
  `CREATE INDEX IF NOT EXISTS idx_${slug}_created_at ON ${table}(created_at);`,
237
268
  ];
238
269
  for (const branch of seed.branches) {
239
- if (branch.type === 'relation' && branch.multiple === true)
240
- continue;
241
- const isRelation = branch.type === 'relation';
242
- if (!isRelation && !resolvePolicies(branch).filter)
243
- continue;
244
- if (['text', 'number', 'date', 'boolean', 'relation'].includes(branch.type)) {
245
- indexes.push(`CREATE INDEX IF NOT EXISTS idx_${slug}_${branch.alias} ON ${table}(${branch.alias});`);
246
- }
247
- if (hasBlindIndex(branch)) {
248
- indexes.push(`CREATE INDEX IF NOT EXISTS idx_${slug}_${branch.alias}_bidx ON ${table}(${branch.alias}_bidx);`);
270
+ for (const { name, column } of branchIndexes(seed, branch)) {
271
+ indexes.push(`CREATE INDEX IF NOT EXISTS ${name} ON ${table}(${column});`);
249
272
  }
250
273
  }
251
274
  if (seed.softDelete) {
@@ -494,12 +517,33 @@ export function generateDropColumn(seed, alias) {
494
517
  `DROP TABLE IF EXISTS ${jt};`,
495
518
  ];
496
519
  }
497
- const stmts = [`ALTER TABLE ${tableName(seed)} DROP COLUMN ${alias};`];
520
+ const stmts = branch ? generateColumnTeardown(seed, branch) : [];
521
+ stmts.push(`ALTER TABLE ${tableName(seed)} DROP COLUMN ${alias};`);
498
522
  if (branch && seed.allowDrafts) {
499
523
  stmts.push(`ALTER TABLE ${draftTableName(seed)} DROP COLUMN ${alias};`);
500
524
  }
501
525
  return stmts;
502
526
  }
527
+ /**
528
+ * Returns the SQL statements that drop an orphan column: one left in the physical table after its
529
+ * branch was removed from the definition. Removes the conventional filter indexes first, since
530
+ * SQLite refuses to drop an indexed column.
531
+ *
532
+ * @param seed The seed definition (the column must not belong to it).
533
+ * @param column The orphan column name.
534
+ * @param inDraftTable Whether the draft table also holds the column.
535
+ * @returns An array of SQL statements.
536
+ */
537
+ export function generateDropOrphanColumn(seed, column, inDraftTable) {
538
+ const stmts = [
539
+ `DROP INDEX IF EXISTS idx_${seed.slug}_${column};`,
540
+ `DROP INDEX IF EXISTS idx_${seed.slug}_${column}_bidx;`,
541
+ `ALTER TABLE ${tableName(seed)} DROP COLUMN ${column};`,
542
+ ];
543
+ if (inDraftTable)
544
+ stmts.push(`ALTER TABLE ${draftTableName(seed)} DROP COLUMN ${column};`);
545
+ return stmts;
546
+ }
503
547
  /**
504
548
  * Returns the `RENAME COLUMN` SQL statements for renaming a field's alias.
505
549
  * For a multi-relation branch, renames the junction table (+ drafts junction table) instead.
@@ -547,9 +591,44 @@ export function generateRetypeColumn(seed, branch) {
547
591
  `ALTER TABLE ${table} DROP COLUMN ${alias};`,
548
592
  `ALTER TABLE ${table} RENAME COLUMN ${tmp} TO ${alias};`,
549
593
  ];
550
- const stmts = rebuild(tableName(seed));
594
+ const current = seed.branches.find(b => b.alias === alias);
595
+ const stmts = current ? generateColumnTeardown(seed, current) : [];
596
+ stmts.push(...rebuild(tableName(seed)));
551
597
  if (seed.allowDrafts)
552
598
  stmts.push(...rebuild(draftTableName(seed)));
599
+ for (const { name, column } of branchIndexes(seed, branch)) {
600
+ stmts.push(`CREATE INDEX IF NOT EXISTS ${name} ON ${tableName(seed)}(${column});`);
601
+ }
602
+ return stmts;
603
+ }
604
+ /**
605
+ * Returns `SELECT COUNT(*)` statements counting rows whose current value would be silently altered
606
+ * (zeroed or truncated) by the `CAST` in {@link generateRetypeColumn}. Empty when the target type
607
+ * is lossless for any input (`TEXT`).
608
+ *
609
+ * A text value is convertible only if it is a complete JSON number; `CAST` alone accepts trailing garbage.
610
+ *
611
+ * @param seed The seed definition.
612
+ * @param branch The target branch definition (carrying the new type).
613
+ * @throws {Error} If called on a multi-relation branch.
614
+ * @returns An array of SQL statements, each yielding a single `count` column.
615
+ */
616
+ export function generateRetypeIncompatibleCount(seed, branch) {
617
+ if (branch.type === 'relation' && branch.multiple === true) {
618
+ throw new Error(`Branch "${branch.alias}" is a multi-relation and has no column to retype`);
619
+ }
620
+ const { sqlType } = BRANCH_TYPE_SQL[branch.type];
621
+ if (sqlType === 'TEXT')
622
+ return [];
623
+ const col = branch.alias;
624
+ const jsonNumber = (types) => `(typeof(${col}) = 'text' AND CASE WHEN json_valid(${col}) THEN json_type(${col}) IN (${types}) ELSE 0 END)`;
625
+ const convertible = sqlType === 'REAL'
626
+ ? `typeof(${col}) IN ('integer', 'real') OR ${jsonNumber("'integer', 'real'")}`
627
+ : `typeof(${col}) = 'integer' OR (typeof(${col}) = 'real' AND ${col} = CAST(${col} AS INTEGER)) OR ${jsonNumber("'integer'")}`;
628
+ const count = (table) => `SELECT COUNT(*) AS count FROM ${table} WHERE ${col} IS NOT NULL AND NOT (${convertible});`;
629
+ const stmts = [count(tableName(seed))];
630
+ if (seed.allowDrafts)
631
+ stmts.push(count(draftTableName(seed)));
553
632
  return stmts;
554
633
  }
555
634
  /**
@@ -18,6 +18,9 @@ export interface ISchemaMutator {
18
18
  * type-change column rebuild, full type drop). The caller assembles the
19
19
  * statements; the impl validates identifiers and runs them as one D1 batch. */
20
20
  execDestructive(statements: string[]): Promise<void>;
21
+ /** Runs read-only `SELECT COUNT(*) AS count` statements as one batch and returns the summed count.
22
+ * Used to pre-check data compatibility before a lossy destructive operation. */
23
+ sumCounts(statements: string[]): Promise<number>;
21
24
  /** Reads all rows from a table for the specified columns only.
22
25
  * Used before DROP TABLE to collect R2 keys for media cleanup.
23
26
  * All identifiers are validated against `^[A-Za-z0-9_]+$`. */
@@ -1 +1 @@
1
- {"version":3,"file":"schema-mutator.d.ts","sourceRoot":"","sources":["../../../src/engine/ddl/schema-mutator.ts"],"names":[],"mappings":"AAGA;;yFAEyF;AACzF,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAA;IACtD;;8EAE0E;IAC1E,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAS5C,sCAAsC;IACtC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,kDAAkD;IAClD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,0DAA0D;IAC1D,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE;;oFAEgF;IAChF,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEpD;;mEAE+D;IAC/D,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;CAChF"}
1
+ {"version":3,"file":"schema-mutator.d.ts","sourceRoot":"","sources":["../../../src/engine/ddl/schema-mutator.ts"],"names":[],"mappings":"AAGA;;yFAEyF;AACzF,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAA;IACtD;;8EAE0E;IAC1E,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAS5C,sCAAsC;IACtC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,kDAAkD;IAClD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,0DAA0D;IAC1D,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE;;oFAEgF;IAChF,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEpD;qFACiF;IACjF,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAEhD;;mEAE+D;IAC/D,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;CAChF"}
@@ -0,0 +1,7 @@
1
+ import type { Seed } from '../types.js';
2
+ /** Blocks writes from requests that loaded a seed immediately before its purge began. */
3
+ export declare function generateSeedPurgeGuards(seed: Seed): {
4
+ create: string[];
5
+ drop: string[];
6
+ };
7
+ //# sourceMappingURL=seed-purge-guards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-purge-guards.d.ts","sourceRoot":"","sources":["../../../src/engine/ddl/seed-purge-guards.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAEvC,yFAAyF;AACzF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,GAAG;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAcxF"}
@@ -0,0 +1,19 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ /** Blocks writes from requests that loaded a seed immediately before its purge began. */
4
+ export function generateSeedPurgeGuards(seed) {
5
+ if (!/^[a-z0-9_]+$/.test(seed.slug))
6
+ throw new Error(`Unsafe seed slug: ${seed.slug}`);
7
+ const tables = [`content_${seed.slug}`, ...(seed.allowDrafts ? [`content_${seed.slug}_drafts`] : [])];
8
+ const operations = ['INSERT', 'UPDATE', 'DELETE'];
9
+ const create = [];
10
+ const drop = [];
11
+ for (const table of tables) {
12
+ for (const operation of operations) {
13
+ const name = `purge_guard_${table}_${operation.toLowerCase()}`;
14
+ create.push(`CREATE TRIGGER ${name} BEFORE ${operation} ON ${table} BEGIN SELECT RAISE(ABORT, 'seed_purge_in_progress'); END;`);
15
+ drop.push(`DROP TRIGGER IF EXISTS ${name};`);
16
+ }
17
+ }
18
+ return { create, drop };
19
+ }
@@ -1,4 +1,5 @@
1
- export * from './ddl.js';
2
- export * from './query.js';
3
- export * from './serialize.js';
1
+ export * from './ddl/ddl.js';
2
+ export * from './query/query.js';
3
+ export * from './query/serialize.js';
4
+ export * from './query/active-clause.js';
4
5
  //# sourceMappingURL=engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/engine/engine.ts"],"names":[],"mappings":"AACA,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/engine/engine.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA;AACpC,cAAc,0BAA0B,CAAA"}
@@ -1,4 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- export * from './ddl.js';
3
- export * from './query.js';
4
- export * from './serialize.js';
2
+ export * from './ddl/ddl.js';
3
+ export * from './query/query.js';
4
+ export * from './query/serialize.js';
5
+ export * from './query/active-clause.js';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @module engine/relation-audit
3
+ * Finds relation branches whose physical storage no longer matches the deployed definition.
4
+ *
5
+ * Releases before the additive relation gate could persist a changed `multiple`, `targetSeed` or
6
+ * `onDelete` without migrating the FK column, the `rel_<slug>_<alias>` junction table or the FK
7
+ * policy. The gate prevents new mismatches; this audit identifies databases that already have one.
8
+ *
9
+ * Read-only by contract: only `PRAGMA` and `SELECT COUNT(*)` statements are issued, through the
10
+ * injected executor. Nothing is repaired and no row is ever touched.
11
+ */
12
+ import type { Seed } from '../types.js';
13
+ import { type SchemaQueryExecutor } from './introspection.js';
14
+ /** What is wrong with the storage of one relation branch. */
15
+ export type RelationStorageIssue =
16
+ /** Definition says single (or multiple) but the data lives in the other shape. */
17
+ 'wrong_storage'
18
+ /** Neither the FK column nor the junction table exists. */
19
+ | 'missing_storage'
20
+ /** The FK points at a different table than `targetSeed`. */
21
+ | 'fk_target_mismatch'
22
+ /** The FK `ON DELETE` rule differs from the effective `onDelete`. */
23
+ | 'fk_on_delete_mismatch';
24
+ export interface RelationStorageFinding {
25
+ seed: string;
26
+ alias: string;
27
+ issue: RelationStorageIssue;
28
+ /** What the deployed definition requires, e.g. `junction rel_posts_tags`. */
29
+ expected: string;
30
+ /** What the database holds. */
31
+ actual: string;
32
+ /**
33
+ * Rows or values sitting in storage the definition no longer points at. Always 0 for
34
+ * `missing_storage` and the FK findings: those carry no stranded values by themselves.
35
+ */
36
+ strandedRows: number;
37
+ }
38
+ /**
39
+ * Compares every relation branch of the given (active) seeds against the physical tables.
40
+ *
41
+ * Seeds whose `content_<slug>` table is missing are skipped: that is plain schema drift and is
42
+ * reported by the schema diff, not a relation-storage mismatch. Findings are ordered by seed, then
43
+ * branch order, so the report is deterministic.
44
+ */
45
+ export declare function auditRelationStorage(executor: SchemaQueryExecutor, seeds: Seed[]): Promise<RelationStorageFinding[]>;
46
+ //# sourceMappingURL=relation-audit.d.ts.map