@beechcms/core 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/antivirus/noop-antivirus.provider.d.ts.map +1 -1
- package/dist/antivirus/virustotal-antivirus.provider.d.ts.map +1 -1
- package/dist/auth/account-email.d.ts +3 -0
- package/dist/auth/account-email.d.ts.map +1 -0
- package/dist/auth/account-email.js +9 -0
- package/dist/auth/password-reset-token.repository.d.ts +13 -2
- package/dist/auth/password-reset-token.repository.d.ts.map +1 -1
- package/dist/auth/user.repository.d.ts +69 -4
- package/dist/auth/user.repository.d.ts.map +1 -1
- package/dist/common/canonical-json.d.ts +2 -1
- package/dist/common/canonical-json.d.ts.map +1 -1
- package/dist/common/canonical-json.js +5 -3
- package/dist/content/content-scan.repository.d.ts +1 -1
- package/dist/content/content.repository.d.ts +156 -5
- package/dist/content/content.repository.d.ts.map +1 -1
- package/dist/content/content.repository.js +62 -0
- package/dist/content/seed-media-purge.repository.d.ts +34 -0
- package/dist/content/seed-media-purge.repository.d.ts.map +1 -0
- package/dist/content/seed.repository.d.ts +14 -0
- package/dist/content/seed.repository.d.ts.map +1 -1
- package/dist/dashboard-layout/content-view.d.ts +757 -0
- package/dist/dashboard-layout/content-view.d.ts.map +1 -0
- package/dist/dashboard-layout/content-view.js +285 -0
- package/dist/dashboard-layout/content-view.repository.d.ts +39 -0
- package/dist/dashboard-layout/content-view.repository.d.ts.map +1 -0
- package/dist/dashboard-layout/dashboard-layout.d.ts +5 -0
- package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -1
- package/dist/dashboard-layout/dashboard-layout.js +17 -9
- package/dist/dashboard-layout/kanban/kanban.d.ts +32 -18
- package/dist/dashboard-layout/kanban/kanban.d.ts.map +1 -1
- package/dist/dashboard-layout/kanban/kanban.js +45 -21
- package/dist/dashboard-layout/seed-layout.d.ts +22 -31
- package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
- package/dist/dashboard-layout/seed-layout.js +93 -18
- package/dist/dashboard-layout/seed-layout.repository.d.ts +1 -5
- package/dist/dashboard-layout/seed-layout.repository.d.ts.map +1 -1
- package/dist/engine/ddl/ddl.d.ts +31 -0
- package/dist/engine/ddl/ddl.d.ts.map +1 -1
- package/dist/engine/ddl/ddl.js +92 -13
- package/dist/engine/ddl/schema-mutator.d.ts +3 -0
- package/dist/engine/ddl/schema-mutator.d.ts.map +1 -1
- package/dist/engine/ddl/seed-purge-guards.d.ts +7 -0
- package/dist/engine/ddl/seed-purge-guards.d.ts.map +1 -0
- package/dist/engine/ddl/seed-purge-guards.js +19 -0
- package/dist/engine/engine.d.ts +4 -3
- package/dist/engine/engine.d.ts.map +1 -1
- package/dist/engine/engine.js +4 -3
- package/dist/engine/introspection/relation-audit.d.ts +46 -0
- package/dist/engine/introspection/relation-audit.d.ts.map +1 -0
- package/dist/engine/introspection/relation-audit.js +103 -0
- package/dist/engine/introspection/schema-fingerprint.d.ts +2 -1
- package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -1
- package/dist/engine/introspection/schema-fingerprint.js +8 -3
- package/dist/engine/privacy/policies.d.ts +1 -1
- package/dist/engine/privacy/policies.d.ts.map +1 -1
- package/dist/engine/privacy/policies.js +12 -6
- package/dist/engine/privacy/privacy.service.d.ts +13 -0
- package/dist/engine/privacy/privacy.service.d.ts.map +1 -1
- package/dist/engine/privacy/privacy.service.js +24 -0
- package/dist/engine/query/active-clause.d.ts +19 -0
- package/dist/engine/query/active-clause.d.ts.map +1 -0
- package/dist/engine/query/active-clause.js +27 -0
- package/dist/engine/query/query.d.ts +14 -1
- package/dist/engine/query/query.d.ts.map +1 -1
- package/dist/engine/query/query.js +105 -13
- package/dist/engine/seeds/candidate-ids.d.ts +14 -0
- package/dist/engine/seeds/candidate-ids.d.ts.map +1 -0
- package/dist/engine/seeds/candidate-ids.js +40 -0
- package/dist/engine/seeds/seed-definition-diff.d.ts +38 -0
- package/dist/engine/seeds/seed-definition-diff.d.ts.map +1 -0
- package/dist/engine/seeds/seed-definition-diff.js +51 -0
- package/dist/engine/seeds/seed-registry.d.ts +17 -0
- package/dist/engine/seeds/seed-registry.d.ts.map +1 -1
- package/dist/engine/seeds/seed-registry.js +27 -0
- package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -1
- package/dist/engine/seeds/seed-types-generator.js +4 -5
- package/dist/engine/seeds/seed-validation.d.ts +5 -0
- package/dist/engine/seeds/seed-validation.d.ts.map +1 -1
- package/dist/engine/seeds/seed-validation.js +39 -19
- package/dist/engine/seeds/tag-migration.d.ts +73 -0
- package/dist/engine/seeds/tag-migration.d.ts.map +1 -0
- package/dist/engine/seeds/tag-migration.js +212 -0
- package/dist/engine/seeds/tag-options.d.ts +15 -0
- package/dist/engine/seeds/tag-options.d.ts.map +1 -0
- package/dist/engine/seeds/tag-options.js +111 -0
- package/dist/engine/seeds/tag-value.d.ts +31 -0
- package/dist/engine/seeds/tag-value.d.ts.map +1 -0
- package/dist/engine/seeds/tag-value.js +51 -0
- package/dist/engine/types.d.ts +59 -7
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/engine/validation/cache.d.ts.map +1 -1
- package/dist/engine/validation/cache.js +20 -4
- package/dist/engine/validation/index.d.ts +12 -1
- package/dist/engine/validation/index.d.ts.map +1 -1
- package/dist/engine/validation/index.js +29 -33
- package/dist/engine/validation/primitives.d.ts +12 -0
- package/dist/engine/validation/primitives.d.ts.map +1 -1
- package/dist/engine/validation/primitives.js +22 -0
- package/dist/engine/validation/richtext-sanitizer.d.ts +9 -1
- package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
- package/dist/engine/validation/richtext-sanitizer.js +36 -7
- package/dist/engine/validation/schema-builders.d.ts +16 -0
- package/dist/engine/validation/schema-builders.d.ts.map +1 -1
- package/dist/engine/validation/schema-builders.js +125 -18
- package/dist/index.d.ts +34 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -11
- package/dist/media/file-types.d.ts +12 -0
- package/dist/media/file-types.d.ts.map +1 -1
- package/dist/media/file-types.js +24 -4
- package/dist/media/magic-bytes.d.ts.map +1 -1
- package/dist/media/magic-bytes.js +19 -9
- package/dist/media/media-transform.d.ts +8 -4
- package/dist/media/media-transform.d.ts.map +1 -1
- package/dist/media/media-transform.js +10 -6
- package/dist/media/media.repository.d.ts +5 -0
- package/dist/media/media.repository.d.ts.map +1 -1
- package/dist/notifications/notification-service.d.ts +1 -1
- package/dist/notifications/notification.repository.d.ts +11 -2
- package/dist/notifications/notification.repository.d.ts.map +1 -1
- package/dist/oauth/authorization-code.repository.d.ts +10 -5
- package/dist/oauth/authorization-code.repository.d.ts.map +1 -1
- package/dist/oauth/pkce.d.ts +1 -1
- package/dist/oauth/role-guard.d.ts +30 -11
- package/dist/oauth/role-guard.d.ts.map +1 -1
- package/dist/oauth/role-guard.js +27 -5
- package/dist/oauth/scopes.d.ts +1 -1
- package/dist/oauth/scopes.d.ts.map +1 -1
- package/dist/oauth/token.repository.d.ts +2 -0
- package/dist/oauth/token.repository.d.ts.map +1 -1
- package/dist/queue/queue.interface.d.ts +9 -1
- package/dist/queue/queue.interface.d.ts.map +1 -1
- package/dist/queue/queue.stub.d.ts +1 -1
- package/dist/queue/queue.stub.d.ts.map +1 -1
- package/dist/queue/queue.stub.js +2 -3
- package/dist/queue/scheduler.stub.d.ts.map +1 -1
- package/dist/rate-limit/token-bucket-rate-limiter.d.ts +7 -0
- package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -1
- package/dist/rate-limit/token-bucket-rate-limiter.js +66 -0
- package/dist/rbac/invitation.repository.d.ts +28 -10
- package/dist/rbac/invitation.repository.d.ts.map +1 -1
- package/dist/rbac/types.d.ts +40 -28
- package/dist/rbac/types.d.ts.map +1 -1
- package/dist/rbac/types.js +2 -1
- package/dist/schema/manifest-seeds.js +3 -3
- package/dist/search/search.repository.d.ts +5 -4
- package/dist/search/search.repository.d.ts.map +1 -1
- package/dist/search/vector-extractor.d.ts.map +1 -1
- package/dist/search/vector-extractor.js +23 -2
- package/dist/search/vector.repository.d.ts +2 -1
- package/dist/search/vector.repository.d.ts.map +1 -1
- package/dist/security/time-trap-token.repository.d.ts +5 -3
- package/dist/security/time-trap-token.repository.d.ts.map +1 -1
- package/dist/settings/site-settings.repository.d.ts +8 -0
- package/dist/settings/site-settings.repository.d.ts.map +1 -1
- package/dist/transfer/flat-seed.d.ts.map +1 -1
- package/dist/transfer/flat-seed.js +6 -1
- package/dist/transfer/row-mapping.d.ts.map +1 -1
- package/dist/transfer/row-mapping.js +9 -2
- package/package.json +9 -4
- package/dist/.tsbuildinfo +0 -1
- package/dist/automations-grammar-words.d.ts +0 -2
- package/dist/automations-grammar-words.d.ts.map +0 -1
- package/dist/automations-grammar-words.js +0 -8
- package/dist/automations.repository.interface.d.ts +0 -19
- package/dist/automations.repository.interface.d.ts.map +0 -1
- package/dist/automations.runner.interface.d.ts +0 -10
- 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 +0 -75
- package/dist/automations.types.d.ts.map +0 -1
- package/dist/automations.types.js +0 -3
- package/dist/clock.d.ts +0 -25
- package/dist/clock.d.ts.map +0 -1
- package/dist/clock.js +0 -11
- package/dist/content-scan.repository.d.ts +0 -9
- package/dist/content-scan.repository.d.ts.map +0 -1
- package/dist/content-scan.repository.js +0 -3
- package/dist/content.repository.d.ts +0 -247
- package/dist/content.repository.d.ts.map +0 -1
- package/dist/content.repository.js +0 -59
- package/dist/dashboard-layout/kanban/kanban-position.repository.d.ts +0 -19
- package/dist/dashboard-layout/kanban/kanban-position.repository.d.ts.map +0 -1
- package/dist/dashboard-layout/kanban/kanban-position.repository.js +0 -3
- package/dist/dashboard-layout.d.ts +0 -154
- package/dist/dashboard-layout.d.ts.map +0 -1
- package/dist/dashboard-layout.js +0 -186
- package/dist/dashboard-layout.repository.d.ts +0 -18
- package/dist/dashboard-layout.repository.d.ts.map +0 -1
- package/dist/dashboard-layout.repository.js +0 -3
- package/dist/dashboard-permissions.d.ts +0 -3
- package/dist/dashboard-permissions.d.ts.map +0 -1
- package/dist/dashboard-permissions.js +0 -11
- package/dist/dashboard-scopes.d.ts +0 -12
- package/dist/dashboard-scopes.d.ts.map +0 -1
- package/dist/dashboard-scopes.js +0 -30
- package/dist/define-seed.d.ts +0 -3
- package/dist/define-seed.d.ts.map +0 -1
- package/dist/define-seed.js +0 -5
- package/dist/demo-data.repository.d.ts +0 -5
- package/dist/demo-data.repository.d.ts.map +0 -1
- package/dist/demo-data.repository.js +0 -3
- package/dist/email.provider.d.ts +0 -32
- package/dist/email.provider.d.ts.map +0 -1
- package/dist/email.provider.js +0 -4
- package/dist/engine/ddl/seed-ddl-destructive.d.ts +0 -40
- package/dist/engine/ddl/seed-ddl-destructive.d.ts.map +0 -1
- package/dist/engine/ddl/seed-ddl-destructive.js +0 -149
- package/dist/engine/ddl.d.ts +0 -193
- package/dist/engine/ddl.d.ts.map +0 -1
- package/dist/engine/ddl.js +0 -518
- package/dist/engine/define-seed.d.ts +0 -3
- package/dist/engine/define-seed.d.ts.map +0 -1
- package/dist/engine/define-seed.js +0 -5
- package/dist/engine/introspection.d.ts +0 -101
- package/dist/engine/introspection.d.ts.map +0 -1
- package/dist/engine/introspection.js +0 -127
- package/dist/engine/localization.d.ts +0 -127
- package/dist/engine/localization.d.ts.map +0 -1
- package/dist/engine/localization.js +0 -212
- package/dist/engine/policies.d.ts +0 -58
- package/dist/engine/policies.d.ts.map +0 -1
- package/dist/engine/policies.js +0 -168
- package/dist/engine/privacy.service.d.ts +0 -65
- package/dist/engine/privacy.service.d.ts.map +0 -1
- package/dist/engine/privacy.service.js +0 -114
- package/dist/engine/query.d.ts +0 -23
- package/dist/engine/query.d.ts.map +0 -1
- package/dist/engine/query.js +0 -236
- package/dist/engine/relations.d.ts +0 -28
- package/dist/engine/relations.d.ts.map +0 -1
- package/dist/engine/relations.js +0 -25
- package/dist/engine/schema-fingerprint.d.ts +0 -73
- package/dist/engine/schema-fingerprint.d.ts.map +0 -1
- package/dist/engine/schema-fingerprint.js +0 -97
- package/dist/engine/schema-mutator.d.ts +0 -26
- package/dist/engine/schema-mutator.d.ts.map +0 -1
- package/dist/engine/schema-mutator.js +0 -3
- package/dist/engine/seed-ddl-destructive.d.ts +0 -40
- package/dist/engine/seed-ddl-destructive.d.ts.map +0 -1
- package/dist/engine/seed-ddl-destructive.js +0 -149
- package/dist/engine/seed-ddl.d.ts +0 -40
- package/dist/engine/seed-ddl.d.ts.map +0 -1
- package/dist/engine/seed-ddl.js +0 -100
- package/dist/engine/seed-registry.d.ts +0 -71
- package/dist/engine/seed-registry.d.ts.map +0 -1
- package/dist/engine/seed-registry.js +0 -167
- package/dist/engine/seed-types-generator.d.ts +0 -10
- package/dist/engine/seed-types-generator.d.ts.map +0 -1
- package/dist/engine/seed-types-generator.js +0 -89
- package/dist/engine/seed-validation.d.ts +0 -30
- package/dist/engine/seed-validation.d.ts.map +0 -1
- package/dist/engine/seed-validation.js +0 -319
- package/dist/engine/seeds.d.ts +0 -20
- package/dist/engine/seeds.d.ts.map +0 -1
- package/dist/engine/seeds.js +0 -21
- package/dist/engine/serialize.d.ts +0 -20
- package/dist/engine/serialize.d.ts.map +0 -1
- package/dist/engine/serialize.js +0 -160
- package/dist/engine.d.ts +0 -144
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -741
- package/dist/file-types.d.ts +0 -13
- package/dist/file-types.d.ts.map +0 -1
- package/dist/file-types.js +0 -85
- package/dist/hooks.d.ts +0 -29
- package/dist/hooks.d.ts.map +0 -1
- package/dist/hooks.js +0 -3
- package/dist/id-generator.d.ts +0 -26
- package/dist/id-generator.d.ts.map +0 -1
- package/dist/id-generator.js +0 -12
- package/dist/idempotency.repository.d.ts +0 -13
- package/dist/idempotency.repository.d.ts.map +0 -1
- package/dist/idempotency.repository.js +0 -3
- package/dist/kanban-position.repository.d.ts +0 -19
- package/dist/kanban-position.repository.d.ts.map +0 -1
- package/dist/kanban-position.repository.js +0 -3
- package/dist/kanban.d.ts +0 -75
- package/dist/kanban.d.ts.map +0 -1
- package/dist/kanban.js +0 -82
- package/dist/layout-permissions.d.ts +0 -3
- package/dist/layout-permissions.d.ts.map +0 -1
- package/dist/layout-permissions.js +0 -11
- package/dist/media.repository.d.ts +0 -61
- package/dist/media.repository.d.ts.map +0 -1
- package/dist/media.repository.js +0 -3
- package/dist/policies.d.ts +0 -10
- package/dist/policies.d.ts.map +0 -1
- package/dist/policies.js +0 -33
- package/dist/queue.interface.d.ts +0 -39
- package/dist/queue.interface.d.ts.map +0 -1
- package/dist/queue.interface.js +0 -3
- package/dist/queue.stub.d.ts +0 -6
- package/dist/queue.stub.d.ts.map +0 -1
- package/dist/queue.stub.js +0 -8
- package/dist/relations.d.ts +0 -28
- package/dist/relations.d.ts.map +0 -1
- package/dist/relations.js +0 -25
- package/dist/richtext-render.d.ts +0 -11
- package/dist/richtext-render.d.ts.map +0 -1
- package/dist/richtext-render.js +0 -87
- package/dist/richtext.d.ts +0 -11
- package/dist/richtext.d.ts.map +0 -1
- package/dist/richtext.js +0 -13
- package/dist/scheduler.interface.d.ts +0 -4
- package/dist/scheduler.interface.d.ts.map +0 -1
- package/dist/scheduler.interface.js +0 -3
- package/dist/scheduler.stub.d.ts +0 -5
- package/dist/scheduler.stub.d.ts.map +0 -1
- package/dist/scheduler.stub.js +0 -7
- package/dist/schema-mutator.d.ts +0 -26
- package/dist/schema-mutator.d.ts.map +0 -1
- package/dist/schema-mutator.js +0 -3
- package/dist/seed-ddl-destructive.d.ts +0 -40
- package/dist/seed-ddl-destructive.d.ts.map +0 -1
- package/dist/seed-ddl-destructive.js +0 -149
- package/dist/seed-ddl.d.ts +0 -40
- package/dist/seed-ddl.d.ts.map +0 -1
- package/dist/seed-ddl.js +0 -100
- package/dist/seed-layout.d.ts +0 -145
- package/dist/seed-layout.d.ts.map +0 -1
- package/dist/seed-layout.js +0 -192
- package/dist/seed-layout.repository.d.ts +0 -22
- package/dist/seed-layout.repository.d.ts.map +0 -1
- package/dist/seed-layout.repository.js +0 -3
- package/dist/seed-registry.d.ts +0 -71
- package/dist/seed-registry.d.ts.map +0 -1
- package/dist/seed-registry.js +0 -143
- package/dist/seed-types-generator.d.ts +0 -10
- package/dist/seed-types-generator.d.ts.map +0 -1
- package/dist/seed-types-generator.js +0 -86
- package/dist/seed-validation.d.ts +0 -23
- package/dist/seed-validation.d.ts.map +0 -1
- package/dist/seed-validation.js +0 -224
- package/dist/seed.repository.d.ts +0 -37
- package/dist/seed.repository.d.ts.map +0 -1
- package/dist/seed.repository.js +0 -3
- package/dist/seeds.d.ts +0 -20
- package/dist/seeds.d.ts.map +0 -1
- package/dist/seeds.js +0 -21
- package/dist/site-settings.repository.d.ts +0 -16
- package/dist/site-settings.repository.d.ts.map +0 -1
- package/dist/site-settings.repository.js +0 -3
- package/dist/slug-utils.d.ts +0 -18
- package/dist/slug-utils.d.ts.map +0 -1
- package/dist/slug-utils.js +0 -32
- package/dist/storage.d.ts +0 -46
- package/dist/storage.d.ts.map +0 -1
- package/dist/storage.js +0 -3
- package/dist/types.d.ts +0 -243
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/validation.d.ts +0 -50
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -660
- package/dist/webhook-validation.d.ts +0 -3
- package/dist/webhook-validation.d.ts.map +0 -1
- package/dist/webhook-validation.js +0 -6
- /package/dist/{automations.repository.interface.js → content/seed-media-purge.repository.js} +0 -0
- /package/dist/{automations.runner.interface.js → dashboard-layout/content-view.repository.js} +0 -0
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
-
import { AUTOMATION_RESERVED_WORDS } from '../automations/automations-grammar-words.js';
|
|
4
|
-
import { SQL_RESERVED_WORDS } from './sql-reserved-words.js';
|
|
5
|
-
const BRANCH_ID_RE = /^br_[A-Za-z0-9]+$/;
|
|
6
|
-
export class SeedRegistry {
|
|
7
|
-
seedMap;
|
|
8
|
-
orderedSeeds;
|
|
9
|
-
constructor(seeds) {
|
|
10
|
-
for (const seed of seeds) {
|
|
11
|
-
const idsInSeed = new Set();
|
|
12
|
-
for (const branch of seed.branches) {
|
|
13
|
-
if (AUTOMATION_RESERVED_WORDS.has(branch.alias)) {
|
|
14
|
-
throw new Error(`Seed "${seed.slug}" uses reserved alias "${branch.alias}". `
|
|
15
|
-
+ `Pick a different alias — this word is used by the automation template grammar.`);
|
|
16
|
-
}
|
|
17
|
-
if (SQL_RESERVED_WORDS.has(branch.alias.toLowerCase())) {
|
|
18
|
-
throw new Error(`Seed "${seed.slug}" uses SQL reserved keyword "${branch.alias}". `
|
|
19
|
-
+ `Please pick a different alias to prevent database errors.`);
|
|
20
|
-
}
|
|
21
|
-
if (!branch.id || !BRANCH_ID_RE.test(branch.id)) {
|
|
22
|
-
throw new Error(`Seed "${seed.slug}" branch "${branch.alias}" has invalid id "${branch.id}". `
|
|
23
|
-
+ `Expected format ^br_[A-Za-z0-9]+$.`);
|
|
24
|
-
}
|
|
25
|
-
if (idsInSeed.has(branch.id)) {
|
|
26
|
-
throw new Error(`Seed "${seed.slug}" has duplicate branch id "${branch.id}".`);
|
|
27
|
-
}
|
|
28
|
-
idsInSeed.add(branch.id);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
this.orderedSeeds = seeds;
|
|
32
|
-
this.seedMap = new Map(seeds.map(seed => [seed.slug, seed]));
|
|
33
|
-
}
|
|
34
|
-
all() {
|
|
35
|
-
return this.orderedSeeds;
|
|
36
|
-
}
|
|
37
|
-
get(slug) {
|
|
38
|
-
return this.seedMap.get(slug) ?? null;
|
|
39
|
-
}
|
|
40
|
-
visibleInDashboard() {
|
|
41
|
-
return this.orderedSeeds.filter(seed => seed.dashboard?.hidden !== true);
|
|
42
|
-
}
|
|
43
|
-
publicReadable() {
|
|
44
|
-
return this.orderedSeeds.filter(seed => seed.allowPublicRead === true);
|
|
45
|
-
}
|
|
46
|
-
draftEnabled() {
|
|
47
|
-
return this.orderedSeeds.filter(seed => seed.allowDrafts === true);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Named subclass of SeedRegistry for use in test suites.
|
|
52
|
-
* Gives tests a semantic name without requiring any dependency on factory.ts.
|
|
53
|
-
*/
|
|
54
|
-
export class InMemorySeedRegistry extends SeedRegistry {
|
|
55
|
-
}
|
|
56
|
-
/** Finds a branch by its stable id within a Seed. Returns null if not found. */
|
|
57
|
-
export function findBranchById(seed, id) {
|
|
58
|
-
return seed.branches.find(b => b.id === id) ?? null;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Returns the next free branch id for a seed in the form `br_NN` (zero-padded to 2,
|
|
62
|
-
* growing as needed). Scans existing ids, never reuses a number already present —
|
|
63
|
-
* even if a branch was removed, its id is not recycled (ids must be globally stable
|
|
64
|
-
* for the life of the seed so layouts/automations/FTS triggers never collide).
|
|
65
|
-
*/
|
|
66
|
-
export function nextBranchId(seed) {
|
|
67
|
-
let max = 0;
|
|
68
|
-
for (const b of seed.branches) {
|
|
69
|
-
const m = /^br_0*([0-9]+)$/.exec(b.id);
|
|
70
|
-
if (m)
|
|
71
|
-
max = Math.max(max, parseInt(m[1], 10));
|
|
72
|
-
}
|
|
73
|
-
const n = max + 1;
|
|
74
|
-
return `br_${String(n).padStart(2, '0')}`;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Returns the input seeds reordered so that every seed appears AFTER all the
|
|
78
|
-
* seeds it depends on (its `relation` branch targets). Pure function — does
|
|
79
|
-
* not read from any global registry.
|
|
80
|
-
*
|
|
81
|
-
* Uses Kahn's algorithm (BFS on in-degree) for cycle detection and topological
|
|
82
|
-
* ordering. Sprint 3 (CLI / migration runner) consumes this to create tables in
|
|
83
|
-
* the correct order without disabling SQLite FK constraints.
|
|
84
|
-
*
|
|
85
|
-
* @throws {Error} When a relation targets an unknown slug not in `seeds`.
|
|
86
|
-
* @throws {Error} When a cyclic dependency is detected, listing involved slugs.
|
|
87
|
-
*/
|
|
88
|
-
export function sortSeedsByDependencies(seeds) {
|
|
89
|
-
const slugSet = new Set(seeds.map(s => s.slug));
|
|
90
|
-
// Validate all relation targets exist in the input array
|
|
91
|
-
for (const seed of seeds) {
|
|
92
|
-
for (const branch of seed.branches) {
|
|
93
|
-
if (branch.type === 'relation' && branch.targetSeed) {
|
|
94
|
-
if (!slugSet.has(branch.targetSeed)) {
|
|
95
|
-
throw new Error(`Seed '${seed.slug}' relates to unknown target '${branch.targetSeed}'`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Build adjacency: inDegree[slug] = number of dependencies
|
|
101
|
-
// dependents[slug] = slugs that depend on slug
|
|
102
|
-
const inDegree = new Map();
|
|
103
|
-
const dependents = new Map();
|
|
104
|
-
for (const seed of seeds) {
|
|
105
|
-
if (!inDegree.has(seed.slug))
|
|
106
|
-
inDegree.set(seed.slug, 0);
|
|
107
|
-
if (!dependents.has(seed.slug))
|
|
108
|
-
dependents.set(seed.slug, []);
|
|
109
|
-
}
|
|
110
|
-
for (const seed of seeds) {
|
|
111
|
-
// Deduplicate targets to avoid inflating inDegree on multi-branch refs to same seed
|
|
112
|
-
const targets = new Set(seed.branches
|
|
113
|
-
.filter(b => b.type === 'relation' && b.targetSeed)
|
|
114
|
-
.map(b => b.targetSeed));
|
|
115
|
-
for (const target of targets) {
|
|
116
|
-
if (target === seed.slug)
|
|
117
|
-
continue; // self-reference needs no ordering
|
|
118
|
-
// seed depends on target → increment seed's in-degree
|
|
119
|
-
inDegree.set(seed.slug, (inDegree.get(seed.slug) ?? 0) + 1);
|
|
120
|
-
dependents.get(target).push(seed.slug);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// Kahn's BFS: start with seeds that have no dependencies
|
|
124
|
-
const seedBySlug = new Map(seeds.map(s => [s.slug, s]));
|
|
125
|
-
const queue = [];
|
|
126
|
-
for (const [slug, deg] of inDegree) {
|
|
127
|
-
if (deg === 0)
|
|
128
|
-
queue.push(slug);
|
|
129
|
-
}
|
|
130
|
-
const result = [];
|
|
131
|
-
while (queue.length > 0) {
|
|
132
|
-
const slug = queue.shift();
|
|
133
|
-
result.push(seedBySlug.get(slug));
|
|
134
|
-
for (const dependent of dependents.get(slug) ?? []) {
|
|
135
|
-
const newDeg = (inDegree.get(dependent) ?? 0) - 1;
|
|
136
|
-
inDegree.set(dependent, newDeg);
|
|
137
|
-
if (newDeg === 0)
|
|
138
|
-
queue.push(dependent);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
if (result.length !== seeds.length) {
|
|
142
|
-
// Isolate true cycle members
|
|
143
|
-
const stuck = new Set([...inDegree.entries()]
|
|
144
|
-
.filter(([, deg]) => deg > 0)
|
|
145
|
-
.map(([slug]) => slug));
|
|
146
|
-
const cycleNodes = [...stuck].filter(slug => {
|
|
147
|
-
const visited = new Set();
|
|
148
|
-
const queue = [...(dependents.get(slug) ?? [])].filter(s => stuck.has(s));
|
|
149
|
-
while (queue.length > 0) {
|
|
150
|
-
const curr = queue.shift();
|
|
151
|
-
if (curr === slug)
|
|
152
|
-
return true;
|
|
153
|
-
if (!visited.has(curr)) {
|
|
154
|
-
visited.add(curr);
|
|
155
|
-
for (const next of dependents.get(curr) ?? []) {
|
|
156
|
-
if (stuck.has(next))
|
|
157
|
-
queue.push(next);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return false;
|
|
162
|
-
});
|
|
163
|
-
const cycle = (cycleNodes.length > 0 ? cycleNodes : [...stuck]).sort();
|
|
164
|
-
throw new Error(`Cyclic dependency detected among seeds: ${cycle.join(', ')}`);
|
|
165
|
-
}
|
|
166
|
-
return result;
|
|
167
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Seed, Branch } from './types.js';
|
|
2
|
-
/** slug/alias → PascalCase identifier. 'blog-posts' → 'BlogPosts'. */
|
|
3
|
-
export declare function pascalCase(input: string): string;
|
|
4
|
-
/** Maps a single Branch to its TypeScript type expression (no optional marker). */
|
|
5
|
-
export declare function tsTypeForBranch(branch: Branch): string;
|
|
6
|
-
/** Emits one exported interface for a single Seed. */
|
|
7
|
-
export declare function interfaceForSeed(seed: Seed): string;
|
|
8
|
-
/** Pure entry point. Deterministic: sorts seeds by slug for stable diffs. */
|
|
9
|
-
export declare function generateSeedTypes(seeds: Seed[]): string;
|
|
10
|
-
//# sourceMappingURL=seed-types-generator.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"seed-types-generator.d.ts","sourceRoot":"","sources":["../../src/engine/seed-types-generator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAe9C,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD;AAMD,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAoCtD;AAWD,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAYnD;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAcvD"}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
-
const HEADER = '// Questo file è generato automaticamente da BeechCMS CLI. Non modificarlo direttamente.\n';
|
|
4
|
-
/** System columns emitted by generateCreateTable — always present, never optional. */
|
|
5
|
-
const SYSTEM_FIELDS = ` id: string\n` +
|
|
6
|
-
` slug: string\n` +
|
|
7
|
-
` status: 'draft' | 'review' | 'published' | 'archived'\n`;
|
|
8
|
-
const SYSTEM_TIMESTAMPS = ` created_at: number\n` +
|
|
9
|
-
` updated_at: number\n`;
|
|
10
|
-
/** slug/alias → PascalCase identifier. 'blog-posts' → 'BlogPosts'. */
|
|
11
|
-
export function pascalCase(input) {
|
|
12
|
-
return input
|
|
13
|
-
.split(/[^A-Za-z0-9]+/)
|
|
14
|
-
.filter(Boolean)
|
|
15
|
-
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
16
|
-
.join('');
|
|
17
|
-
}
|
|
18
|
-
function literalUnion(options) {
|
|
19
|
-
return options.map(o => `'${o.replace(/'/g, "\\'")}'`).join(' | ');
|
|
20
|
-
}
|
|
21
|
-
/** Maps a single Branch to its TypeScript type expression (no optional marker). */
|
|
22
|
-
export function tsTypeForBranch(branch) {
|
|
23
|
-
switch (branch.type) {
|
|
24
|
-
case 'number':
|
|
25
|
-
case 'date':
|
|
26
|
-
return 'number';
|
|
27
|
-
case 'boolean':
|
|
28
|
-
return 'boolean';
|
|
29
|
-
case 'json':
|
|
30
|
-
return 'unknown';
|
|
31
|
-
case 'text':
|
|
32
|
-
case 'richtext':
|
|
33
|
-
case 'file': {
|
|
34
|
-
const base = branch.type === 'text' && branch.options?.length
|
|
35
|
-
? literalUnion(branch.options)
|
|
36
|
-
: 'string';
|
|
37
|
-
return branch.type === 'file' && branch.multiple ? `${base}[]` : base;
|
|
38
|
-
}
|
|
39
|
-
case 'tags': {
|
|
40
|
-
const base = branch.options?.length ? `(${literalUnion(branch.options)})` : 'string';
|
|
41
|
-
return `${base}[]`;
|
|
42
|
-
}
|
|
43
|
-
case 'relation':
|
|
44
|
-
return branch.multiple ? 'string[]' : 'string';
|
|
45
|
-
case 'repeater': {
|
|
46
|
-
const inner = (branch.fields ?? [])
|
|
47
|
-
.map(f => `${propName(f)}${optional(f)}: ${tsTypeForBranch(f)}`)
|
|
48
|
-
.join('; ');
|
|
49
|
-
return `Array<{ ${inner} }>`;
|
|
50
|
-
}
|
|
51
|
-
default: {
|
|
52
|
-
// Exhaustiveness guard — fails the build if BranchType gains a member.
|
|
53
|
-
const _never = branch.type;
|
|
54
|
-
return _never;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function optional(branch) {
|
|
59
|
-
return branch.requiredOnCreate ? '' : '?';
|
|
60
|
-
}
|
|
61
|
-
function propName(branch) {
|
|
62
|
-
// alias is a valid SQL column; quote only if not a plain identifier.
|
|
63
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(branch.alias) ? branch.alias : `'${branch.alias}'`;
|
|
64
|
-
}
|
|
65
|
-
/** Emits one exported interface for a single Seed. */
|
|
66
|
-
export function interfaceForSeed(seed) {
|
|
67
|
-
const name = pascalCase(seed.slug);
|
|
68
|
-
const props = seed.branches
|
|
69
|
-
.map(b => ` ${propName(b)}${optional(b)}: ${tsTypeForBranch(b)}`)
|
|
70
|
-
.join('\n');
|
|
71
|
-
return (`export interface ${name} {\n` +
|
|
72
|
-
SYSTEM_FIELDS +
|
|
73
|
-
(props ? props + '\n' : '') +
|
|
74
|
-
SYSTEM_TIMESTAMPS +
|
|
75
|
-
`}\n`);
|
|
76
|
-
}
|
|
77
|
-
/** Pure entry point. Deterministic: sorts seeds by slug for stable diffs. */
|
|
78
|
-
export function generateSeedTypes(seeds) {
|
|
79
|
-
const sorted = [...seeds].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
80
|
-
const interfaces = sorted.map(interfaceForSeed).join('\n');
|
|
81
|
-
const registryProps = sorted
|
|
82
|
-
.map(s => ` ${propName({ alias: s.slug })}: ${pascalCase(s.slug)}`)
|
|
83
|
-
.join('\n');
|
|
84
|
-
const databaseRegistry = `export interface BeechDatabase {\n` +
|
|
85
|
-
(registryProps ? registryProps + '\n' : '') +
|
|
86
|
-
`}\n\n` +
|
|
87
|
-
`export type SeedRegistryTypes = BeechDatabase\n`;
|
|
88
|
-
return `${HEADER}\n${interfaces}\n${databaseRegistry}`;
|
|
89
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { Seed } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Allowed charset for a branch alias. An alias becomes a raw SQL column name in
|
|
4
|
-
* CREATE TABLE / ADD COLUMN / CREATE INDEX and in SELECT/WHERE/ORDER BY clauses
|
|
5
|
-
* (see engine.ts), so it MUST be restricted to a safe identifier charset to
|
|
6
|
-
* prevent DDL/query injection. Exported so the seeds API rename route reuses the
|
|
7
|
-
* exact same guard — do not inline a divergent copy.
|
|
8
|
-
*/
|
|
9
|
-
export declare const BRANCH_ALIAS_RE: RegExp;
|
|
10
|
-
/**
|
|
11
|
-
* Allowed charset for a seed slug identifier.
|
|
12
|
-
* Must consist solely of lowercase ASCII letters, digits, and underscores.
|
|
13
|
-
* Exported so route handlers and validation functions reuse the exact same guard.
|
|
14
|
-
*/
|
|
15
|
-
export declare const SEED_SLUG_RE: RegExp;
|
|
16
|
-
export declare const SLUG_RE: RegExp;
|
|
17
|
-
export interface SeedValidationIssue {
|
|
18
|
-
slug: string;
|
|
19
|
-
messages: string[];
|
|
20
|
-
fatal: boolean;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Pure, console-free, throw-free validation of a seed set.
|
|
24
|
-
* The single seed being created/edited should be validated in the context of
|
|
25
|
-
* the full active set: call validateSeedDefinitions([...otherActiveSeeds, edited]).
|
|
26
|
-
*/
|
|
27
|
-
export declare function validateSeedDefinitions(seeds: Seed[]): SeedValidationIssue[];
|
|
28
|
-
/** Convenience: true iff no fatal issues. */
|
|
29
|
-
export declare function isSeedSetValid(seeds: Seed[]): boolean;
|
|
30
|
-
//# sourceMappingURL=seed-validation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"seed-validation.d.ts","sourceRoot":"","sources":["../../src/engine/seed-validation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAQtC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,QAAyB,CAAA;AAErD;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAAiB,CAAA;AAC1C,eAAO,MAAM,OAAO,QAAe,CAAA;AAGnC,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,EAAE,OAAO,CAAA;CACf;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,mBAAmB,EAAE,CAkT5E;AAED,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAErD"}
|
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
-
import { AUTOMATION_RESERVED_WORDS } from '../automations/automations-grammar-words.js';
|
|
4
|
-
import { SYSTEM_COLUMNS } from './ddl.js';
|
|
5
|
-
import { sortSeedsByDependencies } from './seed-registry.js';
|
|
6
|
-
import { SQL_RESERVED_WORDS } from './sql-reserved-words.js';
|
|
7
|
-
const BRANCH_ID_RE = /^br_[A-Za-z0-9]+$/;
|
|
8
|
-
/**
|
|
9
|
-
* Allowed charset for a branch alias. An alias becomes a raw SQL column name in
|
|
10
|
-
* CREATE TABLE / ADD COLUMN / CREATE INDEX and in SELECT/WHERE/ORDER BY clauses
|
|
11
|
-
* (see engine.ts), so it MUST be restricted to a safe identifier charset to
|
|
12
|
-
* prevent DDL/query injection. Exported so the seeds API rename route reuses the
|
|
13
|
-
* exact same guard — do not inline a divergent copy.
|
|
14
|
-
*/
|
|
15
|
-
export const BRANCH_ALIAS_RE = /^[a-z][a-zA-Z0-9_]*$/;
|
|
16
|
-
/**
|
|
17
|
-
* Allowed charset for a seed slug identifier.
|
|
18
|
-
* Must consist solely of lowercase ASCII letters, digits, and underscores.
|
|
19
|
-
* Exported so route handlers and validation functions reuse the exact same guard.
|
|
20
|
-
*/
|
|
21
|
-
export const SEED_SLUG_RE = /^[a-z0-9_]+$/;
|
|
22
|
-
export const SLUG_RE = SEED_SLUG_RE;
|
|
23
|
-
/**
|
|
24
|
-
* Pure, console-free, throw-free validation of a seed set.
|
|
25
|
-
* The single seed being created/edited should be validated in the context of
|
|
26
|
-
* the full active set: call validateSeedDefinitions([...otherActiveSeeds, edited]).
|
|
27
|
-
*/
|
|
28
|
-
export function validateSeedDefinitions(seeds) {
|
|
29
|
-
const result = [];
|
|
30
|
-
// ── Fatal 1: unknown relation targets ────────────────────────────────────
|
|
31
|
-
const slugSet = new Set(seeds.map(s => s.slug));
|
|
32
|
-
for (const seed of seeds) {
|
|
33
|
-
const messages = [];
|
|
34
|
-
for (const branch of seed.branches) {
|
|
35
|
-
if (branch.type === 'relation' && branch.targetSeed) {
|
|
36
|
-
if (!slugSet.has(branch.targetSeed)) {
|
|
37
|
-
messages.push(`branch '${branch.alias}' targets unknown seed '${branch.targetSeed}'`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (messages.length > 0)
|
|
42
|
-
result.push({ slug: seed.slug, messages, fatal: true });
|
|
43
|
-
}
|
|
44
|
-
// ── Fatal 2: multi-relation with SET NULL ────────────────────────────────
|
|
45
|
-
for (const seed of seeds) {
|
|
46
|
-
const messages = [];
|
|
47
|
-
for (const branch of seed.branches) {
|
|
48
|
-
if (branch.type === 'relation' && branch.multiple === true && branch.onDelete === 'SET NULL') {
|
|
49
|
-
messages.push(`Branch '${branch.alias}': multi-relations cannot use ON DELETE SET NULL. ` +
|
|
50
|
-
`Use 'CASCADE' or 'RESTRICT'.`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (messages.length > 0)
|
|
54
|
-
result.push({ slug: seed.slug, messages, fatal: true });
|
|
55
|
-
}
|
|
56
|
-
// ── Fatal 3: junction table name collisions / length ─────────────────────
|
|
57
|
-
{
|
|
58
|
-
const junctionNames = new Set();
|
|
59
|
-
for (const seed of seeds) {
|
|
60
|
-
const messages = [];
|
|
61
|
-
for (const branch of seed.branches) {
|
|
62
|
-
if (branch.type !== 'relation' || branch.multiple !== true)
|
|
63
|
-
continue;
|
|
64
|
-
const name = `rel_${seed.slug}_${branch.alias}`;
|
|
65
|
-
if (name.length > 256) {
|
|
66
|
-
messages.push(`Junction table name '${name}' exceeds 256 characters (${name.length})`);
|
|
67
|
-
}
|
|
68
|
-
if (junctionNames.has(name)) {
|
|
69
|
-
messages.push(`Junction table name collision: '${name}' already used by another seed/branch`);
|
|
70
|
-
}
|
|
71
|
-
junctionNames.add(name);
|
|
72
|
-
}
|
|
73
|
-
if (messages.length > 0)
|
|
74
|
-
result.push({ slug: seed.slug, messages, fatal: true });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// ── Fatal 4: dependency cycles ────────────────────────────────────────────
|
|
78
|
-
const hasUnknownTargets = result.some(e => e.fatal);
|
|
79
|
-
if (!hasUnknownTargets) {
|
|
80
|
-
try {
|
|
81
|
-
sortSeedsByDependencies(seeds);
|
|
82
|
-
}
|
|
83
|
-
catch (err) {
|
|
84
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
85
|
-
const cycleMatch = msg.match(/Cyclic dependency detected among seeds: (.*)/);
|
|
86
|
-
if (cycleMatch) {
|
|
87
|
-
const cycleSlugs = cycleMatch[1].split(', ');
|
|
88
|
-
for (const slug of cycleSlugs) {
|
|
89
|
-
result.push({ slug, messages: [msg], fatal: true });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
result.push({ slug: '<graph>', messages: [msg], fatal: true });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// ── Fatal 5: branch id format / uniqueness ───────────────────────────────
|
|
98
|
-
for (const seed of seeds) {
|
|
99
|
-
const messages = [];
|
|
100
|
-
const idsInSeed = new Set();
|
|
101
|
-
for (const branch of seed.branches) {
|
|
102
|
-
if (!branch.id || !BRANCH_ID_RE.test(branch.id)) {
|
|
103
|
-
messages.push(`branch '${branch.alias}' has invalid id '${branch.id}'. Expected format ^br_[A-Za-z0-9]+$.`);
|
|
104
|
-
}
|
|
105
|
-
else if (idsInSeed.has(branch.id)) {
|
|
106
|
-
messages.push(`duplicate branch id '${branch.id}'`);
|
|
107
|
-
}
|
|
108
|
-
if (branch.id)
|
|
109
|
-
idsInSeed.add(branch.id);
|
|
110
|
-
// Alias becomes a raw SQL column name — reject anything outside the safe
|
|
111
|
-
// identifier charset before it can reach DDL/query string interpolation.
|
|
112
|
-
if (typeof branch.alias !== 'string' || !BRANCH_ALIAS_RE.test(branch.alias)) {
|
|
113
|
-
messages.push(`branch alias '${branch.alias}' is invalid. Expected format ${BRANCH_ALIAS_RE.source} ` +
|
|
114
|
-
`(lowercase letter followed by alphanumeric characters or underscores).`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (messages.length > 0)
|
|
118
|
-
result.push({ slug: seed.slug, messages, fatal: true });
|
|
119
|
-
}
|
|
120
|
-
// ── Fatal 6: reserved aliases ─────────────────────────────────────────────
|
|
121
|
-
for (const seed of seeds) {
|
|
122
|
-
const messages = [];
|
|
123
|
-
for (const branch of seed.branches) {
|
|
124
|
-
if (SYSTEM_COLUMNS.has(branch.alias)) {
|
|
125
|
-
messages.push(`branch '${branch.alias}' collides with reserved system column. ` +
|
|
126
|
-
`System columns (id, slug, status, created_at, updated_at) cannot be used as branch aliases.`);
|
|
127
|
-
}
|
|
128
|
-
else if (AUTOMATION_RESERVED_WORDS.has(branch.alias)) {
|
|
129
|
-
messages.push(`branch '${branch.alias}' uses reserved alias. ` +
|
|
130
|
-
`This word is used by the automation template grammar.`);
|
|
131
|
-
}
|
|
132
|
-
else if (SQL_RESERVED_WORDS.has(branch.alias.toLowerCase())) {
|
|
133
|
-
messages.push(`branch '${branch.alias}' uses SQL reserved keyword. ` +
|
|
134
|
-
`Please pick a different alias to prevent database errors.`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (messages.length > 0)
|
|
138
|
-
result.push({ slug: seed.slug, messages, fatal: true });
|
|
139
|
-
}
|
|
140
|
-
// ── Fatal 10: repeater sub-field constraints ─────────────────────────────
|
|
141
|
-
{
|
|
142
|
-
const REPEATER_DISALLOWED_SUBTYPES = new Set(['repeater', 'relation', 'file']);
|
|
143
|
-
for (const seed of seeds) {
|
|
144
|
-
const messages = [];
|
|
145
|
-
for (const branch of seed.branches) {
|
|
146
|
-
if (branch.type !== 'repeater')
|
|
147
|
-
continue;
|
|
148
|
-
const subIds = new Set();
|
|
149
|
-
const subAliases = new Set();
|
|
150
|
-
for (const sub of branch.fields ?? []) {
|
|
151
|
-
if (REPEATER_DISALLOWED_SUBTYPES.has(sub.type)) {
|
|
152
|
-
messages.push(`branch '${branch.alias}': sub-field '${sub.alias}' has disallowed type '${sub.type}'. ` +
|
|
153
|
-
`Repeater sub-fields cannot be 'repeater', 'relation', or 'file' in v1.`);
|
|
154
|
-
}
|
|
155
|
-
if (!sub.id || !BRANCH_ID_RE.test(sub.id)) {
|
|
156
|
-
messages.push(`branch '${branch.alias}': sub-field '${sub.alias}' has invalid id '${sub.id}'. ` +
|
|
157
|
-
`Expected format ^br_[A-Za-z0-9]+$.`);
|
|
158
|
-
}
|
|
159
|
-
else if (subIds.has(sub.id)) {
|
|
160
|
-
messages.push(`branch '${branch.alias}': duplicate sub-field id '${sub.id}'`);
|
|
161
|
-
}
|
|
162
|
-
if (sub.id)
|
|
163
|
-
subIds.add(sub.id);
|
|
164
|
-
if (typeof sub.alias !== 'string' || !BRANCH_ALIAS_RE.test(sub.alias)) {
|
|
165
|
-
messages.push(`branch '${branch.alias}': sub-field alias '${sub.alias}' is invalid. ` +
|
|
166
|
-
`Expected format ${BRANCH_ALIAS_RE.source}.`);
|
|
167
|
-
}
|
|
168
|
-
else if (subAliases.has(sub.alias)) {
|
|
169
|
-
messages.push(`branch '${branch.alias}': duplicate sub-field alias '${sub.alias}'`);
|
|
170
|
-
}
|
|
171
|
-
subAliases.add(sub.alias);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (messages.length > 0)
|
|
175
|
-
result.push({ slug: seed.slug, messages, fatal: true });
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// ── Warning 7: duplicate slug ─────────────────────────────────────────────
|
|
179
|
-
{
|
|
180
|
-
const slugsSeen = new Set();
|
|
181
|
-
for (const seed of seeds) {
|
|
182
|
-
const messages = [];
|
|
183
|
-
if (slugsSeen.has(seed.slug)) {
|
|
184
|
-
messages.push(`duplicate slug "${seed.slug}" — each seed must have a unique slug`);
|
|
185
|
-
}
|
|
186
|
-
slugsSeen.add(seed.slug);
|
|
187
|
-
if (messages.length > 0)
|
|
188
|
-
result.push({ slug: seed.slug, messages, fatal: false });
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
// ── Warning 8: duplicate branch alias within a seed ───────────────────────
|
|
192
|
-
for (const seed of seeds) {
|
|
193
|
-
const messages = [];
|
|
194
|
-
const aliasesSeen = new Set();
|
|
195
|
-
for (const branch of seed.branches) {
|
|
196
|
-
if (aliasesSeen.has(branch.alias)) {
|
|
197
|
-
messages.push(`duplicate branch alias "${branch.alias}"`);
|
|
198
|
-
}
|
|
199
|
-
aliasesSeen.add(branch.alias);
|
|
200
|
-
}
|
|
201
|
-
if (messages.length > 0)
|
|
202
|
-
result.push({ slug: seed.slug, messages, fatal: false });
|
|
203
|
-
}
|
|
204
|
-
// ── Warning 9: displayNameAlias not in branches ───────────────────────────
|
|
205
|
-
for (const seed of seeds) {
|
|
206
|
-
const allAliases = new Set(seed.branches.map(b => b.alias));
|
|
207
|
-
if (!allAliases.has(seed.displayNameAlias)) {
|
|
208
|
-
result.push({
|
|
209
|
-
slug: seed.slug,
|
|
210
|
-
messages: [`displayNameAlias "${seed.displayNameAlias}" not found in branches`],
|
|
211
|
-
fatal: false,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
// ── Fatal 11 / Warning 10: repeater cardinality bounds ───────────────────
|
|
216
|
-
{
|
|
217
|
-
for (const seed of seeds) {
|
|
218
|
-
const fatals = [];
|
|
219
|
-
const warnings = [];
|
|
220
|
-
for (const branch of seed.branches) {
|
|
221
|
-
const hasBounds = branch.minItems !== undefined || branch.maxItems !== undefined;
|
|
222
|
-
if (!hasBounds)
|
|
223
|
-
continue;
|
|
224
|
-
if (branch.type !== 'repeater') {
|
|
225
|
-
warnings.push(`branch '${branch.alias}': minItems/maxItems are ignored on type ` +
|
|
226
|
-
`'${branch.type}' (repeater-only).`);
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
for (const [key, val] of [['minItems', branch.minItems], ['maxItems', branch.maxItems]]) {
|
|
230
|
-
if (val !== undefined && (!Number.isInteger(val) || val < 0)) {
|
|
231
|
-
fatals.push(`branch '${branch.alias}': ${key} must be a non-negative integer (got ${val}).`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (Number.isInteger(branch.minItems) && Number.isInteger(branch.maxItems) &&
|
|
235
|
-
branch.minItems > branch.maxItems) {
|
|
236
|
-
fatals.push(`branch '${branch.alias}': minItems (${branch.minItems}) must be <= maxItems (${branch.maxItems}).`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (fatals.length > 0)
|
|
240
|
-
result.push({ slug: seed.slug, messages: fatals, fatal: true });
|
|
241
|
-
if (warnings.length > 0)
|
|
242
|
-
result.push({ slug: seed.slug, messages: warnings, fatal: false });
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
// ── Fatal 12: displayNameAlias format validation ───────────────────────────
|
|
246
|
-
for (const seed of seeds) {
|
|
247
|
-
if (typeof seed.displayNameAlias !== 'string' || !BRANCH_ALIAS_RE.test(seed.displayNameAlias)) {
|
|
248
|
-
result.push({
|
|
249
|
-
slug: seed.slug,
|
|
250
|
-
messages: [
|
|
251
|
-
`displayNameAlias '${seed.displayNameAlias}' is invalid. Expected format ${BRANCH_ALIAS_RE.source} ` +
|
|
252
|
-
`(lowercase letter followed by alphanumeric characters or underscores).`,
|
|
253
|
-
],
|
|
254
|
-
fatal: true,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
else if (SYSTEM_COLUMNS.has(seed.displayNameAlias)) {
|
|
258
|
-
result.push({
|
|
259
|
-
slug: seed.slug,
|
|
260
|
-
messages: [
|
|
261
|
-
`displayNameAlias '${seed.displayNameAlias}' collides with reserved system column. ` +
|
|
262
|
-
`System columns (id, slug, status, created_at, updated_at) cannot be used as displayNameAlias.`,
|
|
263
|
-
],
|
|
264
|
-
fatal: true,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
else if (SQL_RESERVED_WORDS.has(seed.displayNameAlias.toLowerCase())) {
|
|
268
|
-
result.push({
|
|
269
|
-
slug: seed.slug,
|
|
270
|
-
messages: [
|
|
271
|
-
`displayNameAlias '${seed.displayNameAlias}' uses SQL reserved keyword. ` +
|
|
272
|
-
`Please pick a different alias to prevent database errors.`,
|
|
273
|
-
],
|
|
274
|
-
fatal: true,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
// ── Fatal 13: retentionDays validation ─────────────────────────────────────
|
|
279
|
-
for (const seed of seeds) {
|
|
280
|
-
if (seed.retentionDays !== undefined && (!Number.isInteger(seed.retentionDays) || seed.retentionDays <= 0)) {
|
|
281
|
-
result.push({
|
|
282
|
-
slug: seed.slug,
|
|
283
|
-
messages: [
|
|
284
|
-
`retentionDays must be a positive integer >= 1 (got ${seed.retentionDays}).`,
|
|
285
|
-
],
|
|
286
|
-
fatal: true,
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
// ── Fatal 14: publicEdit validation ─────────────────────────────────────────
|
|
291
|
-
for (const seed of seeds) {
|
|
292
|
-
const messages = [];
|
|
293
|
-
for (const branch of seed.branches) {
|
|
294
|
-
if (branch.policies?.publicEdit !== undefined && typeof branch.policies.publicEdit !== 'boolean') {
|
|
295
|
-
messages.push(`branch '${branch.alias}': policies.publicEdit must be a boolean`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (messages.length > 0)
|
|
299
|
-
result.push({ slug: seed.slug, messages, fatal: true });
|
|
300
|
-
}
|
|
301
|
-
// ── Fatal 15: seed slug format validation ─────────────────────────────────
|
|
302
|
-
for (const seed of seeds) {
|
|
303
|
-
if (typeof seed.slug !== 'string' || !SEED_SLUG_RE.test(seed.slug)) {
|
|
304
|
-
result.push({
|
|
305
|
-
slug: seed.slug,
|
|
306
|
-
messages: [
|
|
307
|
-
`slug '${seed.slug}' is invalid. Expected format ${SEED_SLUG_RE.source} ` +
|
|
308
|
-
`(lowercase letters, numbers, and underscores only).`,
|
|
309
|
-
],
|
|
310
|
-
fatal: true,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return result;
|
|
315
|
-
}
|
|
316
|
-
/** Convenience: true iff no fatal issues. */
|
|
317
|
-
export function isSeedSetValid(seeds) {
|
|
318
|
-
return validateSeedDefinitions(seeds).every(i => !i.fatal);
|
|
319
|
-
}
|
package/dist/engine/seeds.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Seed Registry: configurazione degli schemi di contenuto.
|
|
3
|
-
* In v0.4.0 ogni Seed genera una tabella `content_{slug}` con colonne reali.
|
|
4
|
-
* `branch.alias` è il nome della colonna SQL.
|
|
5
|
-
*/
|
|
6
|
-
import type { Seed } from './types.js';
|
|
7
|
-
/**
|
|
8
|
-
* Registro globale dei Seed.
|
|
9
|
-
* In produzione, questo viene popolato dalle definizioni dell'utente.
|
|
10
|
-
*/
|
|
11
|
-
export declare const SEED_REGISTRY: Record<string, Seed>;
|
|
12
|
-
/**
|
|
13
|
-
* Registra un set di Seed nel registro globale.
|
|
14
|
-
*/
|
|
15
|
-
export declare function registerSeeds(seeds: Seed[]): void;
|
|
16
|
-
/**
|
|
17
|
-
* Ritorna il Seed per lo slug dato, o null se non esiste.
|
|
18
|
-
*/
|
|
19
|
-
export declare function getSeed(slug: string): Seed | null;
|
|
20
|
-
//# sourceMappingURL=seeds.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"seeds.d.ts","sourceRoot":"","sources":["../../src/engine/seeds.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAM,CAAA;AAErD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,QAI1C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEjD"}
|
package/dist/engine/seeds.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
-
/**
|
|
4
|
-
* Registro globale dei Seed.
|
|
5
|
-
* In produzione, questo viene popolato dalle definizioni dell'utente.
|
|
6
|
-
*/
|
|
7
|
-
export const SEED_REGISTRY = {};
|
|
8
|
-
/**
|
|
9
|
-
* Registra un set di Seed nel registro globale.
|
|
10
|
-
*/
|
|
11
|
-
export function registerSeeds(seeds) {
|
|
12
|
-
seeds.forEach(seed => {
|
|
13
|
-
SEED_REGISTRY[seed.slug] = seed;
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Ritorna il Seed per lo slug dato, o null se non esiste.
|
|
18
|
-
*/
|
|
19
|
-
export function getSeed(slug) {
|
|
20
|
-
return SEED_REGISTRY[slug] ?? null;
|
|
21
|
-
}
|