@beechcms/core 0.8.0 → 0.8.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/.tsbuildinfo +1 -1
- package/dist/antivirus/noop-antivirus.provider.d.ts.map +1 -1
- package/dist/antivirus/virustotal-antivirus.provider.d.ts.map +1 -1
- package/dist/automations-grammar-words.d.ts +2 -0
- package/dist/automations-grammar-words.d.ts.map +1 -0
- package/dist/automations-grammar-words.js +8 -0
- package/dist/automations.repository.interface.d.ts +19 -0
- package/dist/automations.repository.interface.d.ts.map +1 -0
- package/dist/automations.repository.interface.js +3 -0
- package/dist/automations.runner.interface.d.ts +10 -0
- package/dist/automations.runner.interface.d.ts.map +1 -0
- package/dist/automations.runner.interface.js +3 -0
- package/dist/automations.runner.stub.d.ts +5 -0
- package/dist/automations.runner.stub.d.ts.map +1 -0
- package/dist/automations.runner.stub.js +7 -0
- package/dist/automations.types.d.ts +75 -0
- package/dist/automations.types.d.ts.map +1 -0
- package/dist/automations.types.js +3 -0
- package/dist/clock.d.ts +25 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +11 -0
- package/dist/common/canonical-json.d.ts +30 -0
- package/dist/common/canonical-json.d.ts.map +1 -0
- package/dist/common/canonical-json.js +89 -0
- package/dist/content/content.repository.d.ts +2 -2
- package/dist/content/content.repository.d.ts.map +1 -1
- package/dist/content/deletion-ledger.d.ts +39 -0
- package/dist/content/deletion-ledger.d.ts.map +1 -0
- package/dist/content/deletion-ledger.js +3 -0
- package/dist/content-scan.repository.d.ts +9 -0
- package/dist/content-scan.repository.d.ts.map +1 -0
- package/dist/content-scan.repository.js +3 -0
- package/dist/content.repository.d.ts +247 -0
- package/dist/content.repository.d.ts.map +1 -0
- package/dist/content.repository.js +59 -0
- package/dist/dashboard-layout.d.ts +154 -0
- package/dist/dashboard-layout.d.ts.map +1 -0
- package/dist/dashboard-layout.js +186 -0
- package/dist/dashboard-layout.repository.d.ts +18 -0
- package/dist/dashboard-layout.repository.d.ts.map +1 -0
- package/dist/dashboard-layout.repository.js +3 -0
- package/dist/dashboard-permissions.d.ts +3 -0
- package/dist/dashboard-permissions.d.ts.map +1 -0
- package/dist/dashboard-permissions.js +11 -0
- package/dist/dashboard-scopes.d.ts +12 -0
- package/dist/dashboard-scopes.d.ts.map +1 -0
- package/dist/dashboard-scopes.js +30 -0
- package/dist/define-seed.d.ts +3 -0
- package/dist/define-seed.d.ts.map +1 -0
- package/dist/define-seed.js +5 -0
- package/dist/demo-data.repository.d.ts +5 -0
- package/dist/demo-data.repository.d.ts.map +1 -0
- package/dist/demo-data.repository.js +3 -0
- package/dist/email.provider.d.ts +32 -0
- package/dist/email.provider.d.ts.map +1 -0
- package/dist/email.provider.js +4 -0
- package/dist/engine/ddl/ddl.d.ts +216 -0
- package/dist/engine/ddl/ddl.d.ts.map +1 -0
- package/dist/engine/ddl/ddl.js +574 -0
- package/dist/engine/ddl/schema-mutator.d.ts +26 -0
- package/dist/engine/ddl/schema-mutator.d.ts.map +1 -0
- package/dist/engine/ddl/schema-mutator.js +3 -0
- package/dist/engine/ddl/seed-ddl-destructive.d.ts +40 -0
- package/dist/engine/ddl/seed-ddl-destructive.d.ts.map +1 -0
- package/dist/engine/ddl/seed-ddl-destructive.js +149 -0
- package/dist/engine/ddl/seed-ddl.d.ts +43 -0
- package/dist/engine/ddl/seed-ddl.d.ts.map +1 -0
- package/dist/engine/ddl/seed-ddl.js +115 -0
- package/dist/engine/introspection/introspection.d.ts +101 -0
- package/dist/engine/introspection/introspection.d.ts.map +1 -0
- package/dist/engine/introspection/introspection.js +127 -0
- package/dist/engine/introspection/schema-fingerprint.d.ts +73 -0
- package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -0
- package/dist/engine/introspection/schema-fingerprint.js +97 -0
- package/dist/engine/introspection.d.ts +101 -0
- package/dist/engine/introspection.d.ts.map +1 -0
- package/dist/engine/introspection.js +127 -0
- package/dist/engine/localization/localization.d.ts +127 -0
- package/dist/engine/localization/localization.d.ts.map +1 -0
- package/dist/engine/localization/localization.js +212 -0
- package/dist/engine/localization.d.ts +127 -0
- package/dist/engine/localization.d.ts.map +1 -0
- package/dist/engine/localization.js +212 -0
- package/dist/engine/privacy/policies.d.ts +58 -0
- package/dist/engine/privacy/policies.d.ts.map +1 -0
- package/dist/engine/privacy/policies.js +185 -0
- package/dist/engine/privacy/privacy.service.d.ts +65 -0
- package/dist/engine/privacy/privacy.service.d.ts.map +1 -0
- package/dist/engine/privacy/privacy.service.js +114 -0
- package/dist/engine/privacy.service.d.ts.map +1 -1
- package/dist/engine/query/query.d.ts +23 -0
- package/dist/engine/query/query.d.ts.map +1 -0
- package/dist/engine/query/query.js +301 -0
- package/dist/engine/query/relations.d.ts +28 -0
- package/dist/engine/query/relations.d.ts.map +1 -0
- package/dist/engine/query/relations.js +25 -0
- package/dist/engine/query/serialize.d.ts +22 -0
- package/dist/engine/query/serialize.d.ts.map +1 -0
- package/dist/engine/query/serialize.js +175 -0
- package/dist/engine/schema-fingerprint.d.ts +73 -0
- package/dist/engine/schema-fingerprint.d.ts.map +1 -0
- package/dist/engine/schema-fingerprint.js +97 -0
- package/dist/engine/seed-registry.d.ts.map +1 -1
- package/dist/engine/seeds/define-seed.d.ts +3 -0
- package/dist/engine/seeds/define-seed.d.ts.map +1 -0
- package/dist/engine/seeds/define-seed.js +5 -0
- package/dist/engine/seeds/seed-registry.d.ts +71 -0
- package/dist/engine/seeds/seed-registry.d.ts.map +1 -0
- package/dist/engine/seeds/seed-registry.js +167 -0
- package/dist/engine/seeds/seed-types-generator.d.ts +25 -0
- package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -0
- package/dist/engine/seeds/seed-types-generator.js +117 -0
- package/dist/engine/seeds/seed-validation.d.ts +30 -0
- package/dist/engine/seeds/seed-validation.d.ts.map +1 -0
- package/dist/engine/seeds/seed-validation.js +364 -0
- package/dist/engine/seeds/seeds.d.ts +20 -0
- package/dist/engine/seeds/seeds.d.ts.map +1 -0
- package/dist/engine/seeds/seeds.js +21 -0
- package/dist/engine/validation/emptiness.d.ts +12 -0
- package/dist/engine/validation/emptiness.d.ts.map +1 -0
- package/dist/engine/validation/emptiness.js +33 -0
- package/dist/engine/validation/index.d.ts +1 -1
- package/dist/engine/validation/index.d.ts.map +1 -1
- package/dist/engine.d.ts +144 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +741 -0
- package/dist/file-types.d.ts +13 -0
- package/dist/file-types.d.ts.map +1 -0
- package/dist/file-types.js +85 -0
- package/dist/hooks.d.ts +29 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +3 -0
- package/dist/id-generator.d.ts +26 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +12 -0
- package/dist/idempotency.repository.d.ts +13 -0
- package/dist/idempotency.repository.d.ts.map +1 -0
- package/dist/idempotency.repository.js +3 -0
- package/dist/kanban-position.repository.d.ts +19 -0
- package/dist/kanban-position.repository.d.ts.map +1 -0
- package/dist/kanban-position.repository.js +3 -0
- package/dist/kanban.d.ts +75 -0
- package/dist/kanban.d.ts.map +1 -0
- package/dist/kanban.js +82 -0
- package/dist/layout-permissions.d.ts +3 -0
- package/dist/layout-permissions.d.ts.map +1 -0
- package/dist/layout-permissions.js +11 -0
- package/dist/media/image-transformer.d.ts +34 -0
- package/dist/media/image-transformer.d.ts.map +1 -0
- package/dist/media/image-transformer.js +3 -0
- package/dist/media/media-transform.d.ts +88 -0
- package/dist/media/media-transform.d.ts.map +1 -0
- package/dist/media/media-transform.js +193 -0
- package/dist/media.repository.d.ts +61 -0
- package/dist/media.repository.d.ts.map +1 -0
- package/dist/media.repository.js +3 -0
- package/dist/notifications/notification-service.d.ts +1 -1
- package/dist/oauth/pkce.d.ts +1 -1
- package/dist/oauth/role-guard.d.ts.map +1 -1
- package/dist/oauth/scopes.d.ts +1 -1
- package/dist/oauth/scopes.d.ts.map +1 -1
- package/dist/policies.d.ts +10 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +33 -0
- package/dist/queue/queue.stub.d.ts.map +1 -1
- package/dist/queue/scheduler.stub.d.ts.map +1 -1
- package/dist/queue.interface.d.ts +39 -0
- package/dist/queue.interface.d.ts.map +1 -0
- package/dist/queue.interface.js +3 -0
- package/dist/queue.stub.d.ts +6 -0
- package/dist/queue.stub.d.ts.map +1 -0
- package/dist/queue.stub.js +8 -0
- package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -1
- package/dist/rbac/evaluate.d.ts +57 -0
- package/dist/rbac/evaluate.d.ts.map +1 -0
- package/dist/rbac/evaluate.js +113 -0
- package/dist/rbac/invitation.repository.d.ts +66 -0
- package/dist/rbac/invitation.repository.d.ts.map +1 -0
- package/dist/rbac/invitation.repository.js +3 -0
- package/dist/rbac/permissions.d.ts +39 -0
- package/dist/rbac/permissions.d.ts.map +1 -0
- package/dist/rbac/permissions.js +43 -0
- package/dist/rbac/types.d.ts +118 -0
- package/dist/rbac/types.d.ts.map +1 -0
- package/dist/rbac/types.js +3 -0
- package/dist/relations.d.ts +28 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +25 -0
- package/dist/richtext-render.d.ts +11 -0
- package/dist/richtext-render.d.ts.map +1 -0
- package/dist/richtext-render.js +87 -0
- package/dist/richtext.d.ts +11 -0
- package/dist/richtext.d.ts.map +1 -0
- package/dist/richtext.js +13 -0
- package/dist/scheduler.interface.d.ts +4 -0
- package/dist/scheduler.interface.d.ts.map +1 -0
- package/dist/scheduler.interface.js +3 -0
- package/dist/scheduler.stub.d.ts +5 -0
- package/dist/scheduler.stub.d.ts.map +1 -0
- package/dist/scheduler.stub.js +7 -0
- package/dist/schema/canonical.d.ts +15 -0
- package/dist/schema/canonical.d.ts.map +1 -0
- package/dist/schema/canonical.js +37 -0
- package/dist/schema/define.d.ts +42 -0
- package/dist/schema/define.d.ts.map +1 -0
- package/dist/schema/define.js +56 -0
- package/dist/schema/emit.d.ts +14 -0
- package/dist/schema/emit.d.ts.map +1 -0
- package/dist/schema/emit.js +43 -0
- package/dist/schema/index.d.ts +12 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +13 -0
- package/dist/schema/manifest-seeds.d.ts +12 -0
- package/dist/schema/manifest-seeds.d.ts.map +1 -0
- package/dist/schema/manifest-seeds.js +48 -0
- package/dist/schema/manifest-validation.d.ts +13 -0
- package/dist/schema/manifest-validation.d.ts.map +1 -0
- package/dist/schema/manifest-validation.js +41 -0
- package/dist/schema/manifest.types.d.ts +54 -0
- package/dist/schema/manifest.types.d.ts.map +1 -0
- package/dist/schema/manifest.types.js +7 -0
- package/dist/schema-mutator.d.ts +26 -0
- package/dist/schema-mutator.d.ts.map +1 -0
- package/dist/schema-mutator.js +3 -0
- package/dist/seed-ddl-destructive.d.ts +40 -0
- package/dist/seed-ddl-destructive.d.ts.map +1 -0
- package/dist/seed-ddl-destructive.js +149 -0
- package/dist/seed-ddl.d.ts +40 -0
- package/dist/seed-ddl.d.ts.map +1 -0
- package/dist/seed-ddl.js +100 -0
- package/dist/seed-layout.d.ts +145 -0
- package/dist/seed-layout.d.ts.map +1 -0
- package/dist/seed-layout.js +192 -0
- package/dist/seed-layout.repository.d.ts +22 -0
- package/dist/seed-layout.repository.d.ts.map +1 -0
- package/dist/seed-layout.repository.js +3 -0
- package/dist/seed-registry.d.ts +71 -0
- package/dist/seed-registry.d.ts.map +1 -0
- package/dist/seed-registry.js +143 -0
- package/dist/seed-types-generator.d.ts +1 -1
- package/dist/seed-types-generator.d.ts.map +1 -1
- package/dist/seed-validation.d.ts +23 -0
- package/dist/seed-validation.d.ts.map +1 -0
- package/dist/seed-validation.js +224 -0
- package/dist/seed.repository.d.ts +37 -0
- package/dist/seed.repository.d.ts.map +1 -0
- package/dist/seed.repository.js +3 -0
- package/dist/seeds.d.ts +20 -0
- package/dist/seeds.d.ts.map +1 -0
- package/dist/seeds.js +21 -0
- package/dist/site-settings.repository.d.ts +16 -0
- package/dist/site-settings.repository.d.ts.map +1 -0
- package/dist/site-settings.repository.js +3 -0
- package/dist/slug-utils.d.ts +18 -0
- package/dist/slug-utils.d.ts.map +1 -0
- package/dist/slug-utils.js +32 -0
- package/dist/storage.d.ts +46 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +3 -0
- package/dist/transfer/csv.d.ts +34 -0
- package/dist/transfer/csv.d.ts.map +1 -0
- package/dist/transfer/csv.js +135 -0
- package/dist/transfer/flat-seed.d.ts +13 -0
- package/dist/transfer/flat-seed.d.ts.map +1 -0
- package/dist/transfer/flat-seed.js +45 -0
- package/dist/transfer/index.d.ts +7 -0
- package/dist/transfer/index.d.ts.map +1 -0
- package/dist/transfer/index.js +8 -0
- package/dist/transfer/ndjson.d.ts +25 -0
- package/dist/transfer/ndjson.d.ts.map +1 -0
- package/dist/transfer/ndjson.js +62 -0
- package/dist/transfer/row-mapping.d.ts +36 -0
- package/dist/transfer/row-mapping.d.ts.map +1 -0
- package/dist/transfer/row-mapping.js +90 -0
- package/dist/transfer/transfer.constants.d.ts +20 -0
- package/dist/transfer/transfer.constants.d.ts.map +1 -0
- package/dist/transfer/transfer.constants.js +21 -0
- package/dist/transfer/transfer.types.d.ts +46 -0
- package/dist/transfer/transfer.types.d.ts.map +1 -0
- package/dist/transfer/transfer.types.js +6 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/validation.d.ts +50 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +660 -0
- package/dist/webhook-validation.d.ts +3 -0
- package/dist/webhook-validation.d.ts.map +1 -0
- package/dist/webhook-validation.js +6 -0
- package/package.json +4 -4
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
/**
|
|
4
|
+
* @module engine/schema-fingerprint
|
|
5
|
+
* A deterministic name for "the shape this API answers in".
|
|
6
|
+
*
|
|
7
|
+
* Computed from live seed definitions (`introspectSeedDefinitions`), embedded in generated client
|
|
8
|
+
* types at build time (sprint 3), returned on every public response as `X-Schema-Revision`
|
|
9
|
+
* (sprint 4), and compared by the client at request time (sprint 5). Both ends MUST compute it
|
|
10
|
+
* with this function — two implementations would be free to disagree, and a drift detector whose
|
|
11
|
+
* ends disagree is worse than none.
|
|
12
|
+
*
|
|
13
|
+
* What is hashed is a CONTRACT PROJECTION, not the whole seed definition: labels, hints, dashboard
|
|
14
|
+
* icons and layout cannot change a single response byte, and hashing them would fire a hard
|
|
15
|
+
* `BeechProblem` in someone else's production client over a typo fix.
|
|
16
|
+
*/
|
|
17
|
+
import { canonicalStringify } from '../../common/canonical-json.js';
|
|
18
|
+
import { resolvePolicies } from '../privacy/policies.js';
|
|
19
|
+
/**
|
|
20
|
+
* Bumped ONLY when the projection below or the canonical byte format changes — i.e. when
|
|
21
|
+
* fingerprints computed by two builds are no longer comparable. It is carried in the fingerprint
|
|
22
|
+
* string itself so a version skew reads as a mismatch instead of as an accidental match.
|
|
23
|
+
*/
|
|
24
|
+
export const SCHEMA_FINGERPRINT_VERSION = 1;
|
|
25
|
+
function projectBranch(branch) {
|
|
26
|
+
const policies = resolvePolicies(branch);
|
|
27
|
+
return {
|
|
28
|
+
alias: branch.alias,
|
|
29
|
+
type: branch.type,
|
|
30
|
+
...(branch.format !== undefined ? { format: branch.format } : {}),
|
|
31
|
+
...(branch.multiple !== undefined ? { multiple: branch.multiple } : {}),
|
|
32
|
+
// Option order is the contract: it is the order a select renders and a union type lists.
|
|
33
|
+
...(branch.options !== undefined ? { options: [...branch.options] } : {}),
|
|
34
|
+
requiredOnCreate: branch.requiredOnCreate ?? false,
|
|
35
|
+
requiredOnUpdate: branch.requiredOnUpdate ?? false,
|
|
36
|
+
...(branch.targetSeed !== undefined ? { targetSeed: branch.targetSeed } : {}),
|
|
37
|
+
visibility: policies.visibility,
|
|
38
|
+
publicRead: policies.public,
|
|
39
|
+
publicEdit: policies.publicEdit,
|
|
40
|
+
...(branch.minItems !== undefined ? { minItems: branch.minItems } : {}),
|
|
41
|
+
...(branch.maxItems !== undefined ? { maxItems: branch.maxItems } : {}),
|
|
42
|
+
// Spread only when true so every schema without localized branches keeps its fingerprint.
|
|
43
|
+
...(branch.localized === true ? { localized: true } : {}),
|
|
44
|
+
...(branch.fields ? { fields: branch.fields.map(projectBranch) } : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Reduces seeds to what a response shape depends on.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately EXCLUDED, and each for a reason a future reader must not "fix":
|
|
51
|
+
* - `id` (`br_XX`) — a stable internal handle; it never appears in a payload, and minting order
|
|
52
|
+
* differs between an environment built by migration and one grown through the dashboard.
|
|
53
|
+
* - `label`, `labelPlural`, `hint`, `dashboard`, `layout` — presentation, never response shape.
|
|
54
|
+
* - `retentionDays`, `onDelete`, `policies.search|filter|sort`, `numberOptions`, `fileOptions`,
|
|
55
|
+
* `policies.classification|privacy` — behaviour and storage concerns that leave the response
|
|
56
|
+
* shape identical. (`privacy` DOES change a stored value's encoding, but the API type stays
|
|
57
|
+
* `string`; drift there is a data concern, not a type concern.)
|
|
58
|
+
* - anything physical (column order, index names) — see the VETO Audit.
|
|
59
|
+
*
|
|
60
|
+
* Seeds are sorted by slug; BRANCH ORDER IS PRESERVED, because it is the declared field order the
|
|
61
|
+
* generated types and the dashboard form both follow.
|
|
62
|
+
*/
|
|
63
|
+
export function projectSchemaContract(seeds) {
|
|
64
|
+
return {
|
|
65
|
+
fingerprintVersion: SCHEMA_FINGERPRINT_VERSION,
|
|
66
|
+
seeds: [...seeds]
|
|
67
|
+
.sort((a, b) => a.slug.localeCompare(b.slug))
|
|
68
|
+
.map(seed => ({
|
|
69
|
+
slug: seed.slug,
|
|
70
|
+
displayNameAlias: seed.displayNameAlias,
|
|
71
|
+
allowDrafts: seed.allowDrafts ?? false,
|
|
72
|
+
allowPublicRead: seed.allowPublicRead ?? false,
|
|
73
|
+
allowPublicPost: seed.allowPublicPost ?? false,
|
|
74
|
+
allowPublicEdit: seed.allowPublicEdit ?? false,
|
|
75
|
+
branches: seed.branches.map(projectBranch),
|
|
76
|
+
})),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function toHex(buffer) {
|
|
80
|
+
return Array.from(new Uint8Array(buffer), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* `v{VERSION}:{32 lowercase hex}` — SHA-256 over the canonical JSON of the contract projection,
|
|
84
|
+
* truncated to 128 bits.
|
|
85
|
+
*
|
|
86
|
+
* Truncation is deliberate: this value travels on EVERY public response as `X-Schema-Revision`,
|
|
87
|
+
* and 128 bits of a SHA-256 digest carries no realistic collision risk for a value space of a few
|
|
88
|
+
* hundred schema revisions. It is a revision marker, never a security token.
|
|
89
|
+
*
|
|
90
|
+
* Web Crypto only — the same API the Worker and Node ≥ 18 both expose, as `webhook-crypto.ts`
|
|
91
|
+
* already relies on. `@beechcms/core` gains no dependency.
|
|
92
|
+
*/
|
|
93
|
+
export async function computeSchemaFingerprint(seeds) {
|
|
94
|
+
const bytes = new TextEncoder().encode(canonicalStringify(projectSchemaContract(seeds)));
|
|
95
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes);
|
|
96
|
+
return `v${SCHEMA_FINGERPRINT_VERSION}:${toHex(digest).slice(0, 32)}`;
|
|
97
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module engine/introspection
|
|
3
|
+
* Reads live schema state out of D1 through an INJECTED executor.
|
|
4
|
+
*
|
|
5
|
+
* Executor-agnostic on purpose: the CLI reaches D1 through a synchronous shell/SQLite path
|
|
6
|
+
* (`queryD1`) and the Worker through its `D1Database` binding, and neither tier may import the
|
|
7
|
+
* other (`graphify path "createBeechApp" "queryD1"` must keep returning no path). `@beechcms/core`
|
|
8
|
+
* owns the STATEMENTS and the SHAPES; the caller owns the connection.
|
|
9
|
+
*
|
|
10
|
+
* Read-only by contract: every statement here is a `PRAGMA` or a `SELECT`. Schema mutation stays
|
|
11
|
+
* the exclusive business of the Botanical Engine's DDL path (`engine/seed-ddl.ts`).
|
|
12
|
+
*/
|
|
13
|
+
import type { Seed } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* The one capability the introspection primitive needs from a database connection.
|
|
16
|
+
*
|
|
17
|
+
* Implementations MUST: execute the statement as-is, return one object per row with column names
|
|
18
|
+
* as keys, and reject/throw on failure. They MUST NOT cache, batch, rewrite or retry — a stale
|
|
19
|
+
* answer here becomes a wrong fingerprint, which becomes a false "your types are stale" error in
|
|
20
|
+
* someone else's production client.
|
|
21
|
+
*/
|
|
22
|
+
export interface SchemaQueryExecutor {
|
|
23
|
+
all<T extends Record<string, unknown>>(sql: string): Promise<T[]>;
|
|
24
|
+
}
|
|
25
|
+
/** Thrown when live state cannot be read or cannot be trusted. */
|
|
26
|
+
export declare class IntrospectionError extends Error {
|
|
27
|
+
readonly cause?: unknown | undefined;
|
|
28
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
29
|
+
}
|
|
30
|
+
/** One physical column, as SQLite reports it. */
|
|
31
|
+
export interface LiveColumn {
|
|
32
|
+
name: string;
|
|
33
|
+
/** Declared storage type, upper-cased (`TEXT` | `INTEGER` | `REAL` | …). */
|
|
34
|
+
sqlType: string;
|
|
35
|
+
notNull: boolean;
|
|
36
|
+
isPk: boolean;
|
|
37
|
+
/** Declared DEFAULT expression verbatim, or null. */
|
|
38
|
+
defaultValue: string | null;
|
|
39
|
+
}
|
|
40
|
+
/** One foreign-key constraint on a physical table. */
|
|
41
|
+
export interface LiveForeignKey {
|
|
42
|
+
/** Local column carrying the reference. */
|
|
43
|
+
column: string;
|
|
44
|
+
/** Referenced table, e.g. `content_team`. */
|
|
45
|
+
targetTable: string;
|
|
46
|
+
/** Referenced column, e.g. `id`. */
|
|
47
|
+
targetColumn: string;
|
|
48
|
+
/** Upper-cased, e.g. `SET NULL`. */
|
|
49
|
+
onDelete: string;
|
|
50
|
+
/** Upper-cased, e.g. `NO ACTION`. */
|
|
51
|
+
onUpdate: string;
|
|
52
|
+
}
|
|
53
|
+
/** One index on a physical table. SQLite's implicit `sqlite_autoindex_*` entries are excluded. */
|
|
54
|
+
export interface LiveIndex {
|
|
55
|
+
name: string;
|
|
56
|
+
unique: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** The physical state of one table. `exists: false` means SQLite reports no such table. */
|
|
59
|
+
export interface LiveTable {
|
|
60
|
+
name: string;
|
|
61
|
+
exists: boolean;
|
|
62
|
+
/** Physical order, as returned by `PRAGMA table_info` (cid order). */
|
|
63
|
+
columns: LiveColumn[];
|
|
64
|
+
/** Sorted by `column`, then `targetTable`, for deterministic output. */
|
|
65
|
+
foreignKeys: LiveForeignKey[];
|
|
66
|
+
/** Sorted by `name`. */
|
|
67
|
+
indexes: LiveIndex[];
|
|
68
|
+
}
|
|
69
|
+
/** The physical state of a set of tables. `tables` is sorted by name. */
|
|
70
|
+
export interface LiveSchema {
|
|
71
|
+
tables: LiveTable[];
|
|
72
|
+
}
|
|
73
|
+
/** Returns `name` unchanged when it is a safe SQL identifier; throws otherwise. */
|
|
74
|
+
export declare function assertSafeIdentifier(name: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Physical state of one table. A table SQLite does not know returns `{ exists: false }` with empty
|
|
77
|
+
* lists rather than throwing: "missing" is a legitimate diff outcome, not an error.
|
|
78
|
+
*
|
|
79
|
+
* `PRAGMA table_info` answers an unknown table with zero rows on the SQLite path and raises on some
|
|
80
|
+
* wrangler paths, so both outcomes collapse to the same result.
|
|
81
|
+
*/
|
|
82
|
+
export declare function introspectTable(executor: SchemaQueryExecutor, table: string): Promise<LiveTable>;
|
|
83
|
+
/**
|
|
84
|
+
* Names of the physical tables SQLite holds, optionally restricted to a `LIKE` prefix
|
|
85
|
+
* (e.g. `content_`). Sorted, `sqlite_*` internals excluded.
|
|
86
|
+
*/
|
|
87
|
+
export declare function listTables(executor: SchemaQueryExecutor, prefix?: string): Promise<string[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Physical state of several tables at once. With no explicit list, every `content_*` table is
|
|
90
|
+
* introspected — the set a schema export or a drift check cares about.
|
|
91
|
+
*/
|
|
92
|
+
export declare function introspectSchema(executor: SchemaQueryExecutor, tables?: string[]): Promise<LiveSchema>;
|
|
93
|
+
/**
|
|
94
|
+
* The DECLARED schema: every active seed definition, read live from D1.
|
|
95
|
+
*
|
|
96
|
+
* This — not `beech.schema.ts` — is the source of truth for generated types and for the schema
|
|
97
|
+
* fingerprint (feature brief, business rule 1). The manifest file is a desired-state artifact and
|
|
98
|
+
* may legitimately be ahead of, or behind, what is deployed.
|
|
99
|
+
*/
|
|
100
|
+
export declare function introspectSeedDefinitions(executor: SchemaQueryExecutor): Promise<Seed[]>;
|
|
101
|
+
//# sourceMappingURL=introspection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../../src/engine/introspection.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAA;CAClE;AAED,kEAAkE;AAClE,qBAAa,kBAAmB,SAAQ,KAAK;IACd,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;gBAAzC,OAAO,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,OAAO,YAAA;CAItD;AAED,iDAAiD;AACjD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,OAAO,CAAA;IACb,qDAAqD;IACrD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAA;IACd,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,kGAAkG;AAClG,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,2FAA2F;AAC3F,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,sEAAsE;IACtE,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,wEAAwE;IACxE,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,wBAAwB;IACxB,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAA;CACpB;AA6CD,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOzD;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,SAAS,CAAC,CAgDpB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,UAAU,CAAC,CAUrB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,EAAE,CAAC,CAoBjB"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
/** Thrown when live state cannot be read or cannot be trusted. */
|
|
4
|
+
export class IntrospectionError extends Error {
|
|
5
|
+
cause;
|
|
6
|
+
constructor(message, cause) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
this.name = 'IntrospectionError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* SQLite identifiers cannot be bound as parameters, so every table name reaching a PRAGMA is
|
|
14
|
+
* interpolated — and therefore must be proven safe first. Table names in this system are always
|
|
15
|
+
* `content_{slug}` / `rel_{slug}_{alias}` / a structural table, all of which match this pattern.
|
|
16
|
+
*/
|
|
17
|
+
const SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
18
|
+
/** Returns `name` unchanged when it is a safe SQL identifier; throws otherwise. */
|
|
19
|
+
export function assertSafeIdentifier(name) {
|
|
20
|
+
if (!SAFE_IDENTIFIER.test(name)) {
|
|
21
|
+
throw new IntrospectionError(`Refusing to introspect '${name}': not a plain SQL identifier. Table names are never user input in BeechCMS.`);
|
|
22
|
+
}
|
|
23
|
+
return name;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Physical state of one table. A table SQLite does not know returns `{ exists: false }` with empty
|
|
27
|
+
* lists rather than throwing: "missing" is a legitimate diff outcome, not an error.
|
|
28
|
+
*
|
|
29
|
+
* `PRAGMA table_info` answers an unknown table with zero rows on the SQLite path and raises on some
|
|
30
|
+
* wrangler paths, so both outcomes collapse to the same result.
|
|
31
|
+
*/
|
|
32
|
+
export async function introspectTable(executor, table) {
|
|
33
|
+
const name = assertSafeIdentifier(table);
|
|
34
|
+
const empty = { name, exists: false, columns: [], foreignKeys: [], indexes: [] };
|
|
35
|
+
let info;
|
|
36
|
+
try {
|
|
37
|
+
info = await executor.all(`PRAGMA table_info(${name})`);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return empty;
|
|
41
|
+
}
|
|
42
|
+
if (info.length === 0)
|
|
43
|
+
return empty;
|
|
44
|
+
const columns = info.map(row => ({
|
|
45
|
+
name: row.name,
|
|
46
|
+
sqlType: String(row.type ?? '').toUpperCase(),
|
|
47
|
+
notNull: row.notnull === 1,
|
|
48
|
+
isPk: row.pk > 0,
|
|
49
|
+
defaultValue: row.dflt_value ?? null,
|
|
50
|
+
}));
|
|
51
|
+
let fkRows = [];
|
|
52
|
+
let indexRows = [];
|
|
53
|
+
try {
|
|
54
|
+
fkRows = await executor.all(`PRAGMA foreign_key_list(${name})`);
|
|
55
|
+
indexRows = await executor.all(`PRAGMA index_list(${name})`);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// A table with neither FKs nor indexes answers with zero rows on some drivers and raises on
|
|
59
|
+
// others; an unreadable constraint list must not hide the column list we already have.
|
|
60
|
+
}
|
|
61
|
+
const foreignKeys = fkRows
|
|
62
|
+
.map(row => ({
|
|
63
|
+
column: row.from,
|
|
64
|
+
targetTable: row.table,
|
|
65
|
+
targetColumn: row.to ?? 'id',
|
|
66
|
+
onDelete: String(row.on_delete ?? '').toUpperCase(),
|
|
67
|
+
onUpdate: String(row.on_update ?? '').toUpperCase(),
|
|
68
|
+
}))
|
|
69
|
+
.sort((a, b) => a.column.localeCompare(b.column) || a.targetTable.localeCompare(b.targetTable));
|
|
70
|
+
const indexes = indexRows
|
|
71
|
+
// Implicit indexes SQLite mints for UNIQUE/PK are not schema an author declared, and their
|
|
72
|
+
// names are not stable across environments.
|
|
73
|
+
.filter(row => !row.name.startsWith('sqlite_autoindex_'))
|
|
74
|
+
.map(row => ({ name: row.name, unique: row.unique === 1 }))
|
|
75
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
76
|
+
return { name, exists: true, columns, foreignKeys, indexes };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Names of the physical tables SQLite holds, optionally restricted to a `LIKE` prefix
|
|
80
|
+
* (e.g. `content_`). Sorted, `sqlite_*` internals excluded.
|
|
81
|
+
*/
|
|
82
|
+
export async function listTables(executor, prefix) {
|
|
83
|
+
if (prefix !== undefined)
|
|
84
|
+
assertSafeIdentifier(`${prefix}x`);
|
|
85
|
+
const filter = prefix ? ` AND name LIKE '${prefix}%'` : '';
|
|
86
|
+
const rows = await executor.all(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'${filter} ORDER BY name ASC`);
|
|
87
|
+
return rows.map(row => row.name);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Physical state of several tables at once. With no explicit list, every `content_*` table is
|
|
91
|
+
* introspected — the set a schema export or a drift check cares about.
|
|
92
|
+
*/
|
|
93
|
+
export async function introspectSchema(executor, tables) {
|
|
94
|
+
const names = tables ?? await listTables(executor, 'content_');
|
|
95
|
+
const sorted = [...names].sort((a, b) => a.localeCompare(b));
|
|
96
|
+
const result = [];
|
|
97
|
+
// Sequential on purpose: the CLI executor is a synchronous shell/SQLite call behind a Promise,
|
|
98
|
+
// and D1 rejects unbounded concurrent statements from one request.
|
|
99
|
+
for (const name of sorted) {
|
|
100
|
+
result.push(await introspectTable(executor, name));
|
|
101
|
+
}
|
|
102
|
+
return { tables: result };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The DECLARED schema: every active seed definition, read live from D1.
|
|
106
|
+
*
|
|
107
|
+
* This — not `beech.schema.ts` — is the source of truth for generated types and for the schema
|
|
108
|
+
* fingerprint (feature brief, business rule 1). The manifest file is a desired-state artifact and
|
|
109
|
+
* may legitimately be ahead of, or behind, what is deployed.
|
|
110
|
+
*/
|
|
111
|
+
export async function introspectSeedDefinitions(executor) {
|
|
112
|
+
let rows;
|
|
113
|
+
try {
|
|
114
|
+
rows = await executor.all(`SELECT slug, definition FROM seeds WHERE status = 'active' ORDER BY slug ASC`);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
throw new IntrospectionError('Failed to read the `seeds` table from D1.', error);
|
|
118
|
+
}
|
|
119
|
+
return rows.map(row => {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(row.definition);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw new IntrospectionError(`Seed '${row.slug}' holds a definition that is not valid JSON; D1 state is corrupt.`, error);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module engine/localization
|
|
3
|
+
* Field-level localization primitives for the Botanical Engine. A localized branch keeps its native
|
|
4
|
+
* TEXT column and stores a locale dictionary in it (`{"it":"Scarpa","en":"Shoe"}`), so ids, relations
|
|
5
|
+
* and metrics are untouched and toggling `Branch.localized` never emits DDL. Pure functions: no I/O and
|
|
6
|
+
* no settings access — callers pass the project's {@link LocaleConfig}.
|
|
7
|
+
*/
|
|
8
|
+
import type { Branch, BranchType, Seed } from '../types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Locale code grammar (v1): ISO 639 language (2–3 lowercase letters), optionally followed by an ISO 3166
|
|
11
|
+
* region (2 uppercase letters) or a UN M.49 area (3 digits) — `it`, `en`, `pt-BR`, `es-419`. Script
|
|
12
|
+
* subtags (`zh-Hant`) are outside v1. The grammar doubles as an injection guard: validated codes are
|
|
13
|
+
* later interpolated into SQLite JSON paths.
|
|
14
|
+
*/
|
|
15
|
+
export declare const LOCALE_CODE_RE: RegExp;
|
|
16
|
+
/** Branch types that may carry `localized: true`. */
|
|
17
|
+
export declare const LOCALIZABLE_BRANCH_TYPES: ReadonlySet<BranchType>;
|
|
18
|
+
/**
|
|
19
|
+
* Project-level language configuration. Invariant (guaranteed by its producer): `locales` is non-empty,
|
|
20
|
+
* every entry matches {@link LOCALE_CODE_RE}, and it contains `defaultLocale`.
|
|
21
|
+
*/
|
|
22
|
+
export interface LocaleConfig {
|
|
23
|
+
readonly locales: readonly string[];
|
|
24
|
+
readonly defaultLocale: string;
|
|
25
|
+
}
|
|
26
|
+
/** A locale dictionary as stored in the branch column: locale code → value. */
|
|
27
|
+
export type LocalizedDictionary = Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* A validated write for a localized branch: registered locale code → new value, or `null` meaning
|
|
30
|
+
* "clear this locale". Locales absent from the patch must be left untouched by the write path.
|
|
31
|
+
*/
|
|
32
|
+
export type LocalizedPatch = Record<string, unknown>;
|
|
33
|
+
/** True when `value` is a string matching {@link LOCALE_CODE_RE}. */
|
|
34
|
+
export declare function isLocaleCode(value: unknown): value is string;
|
|
35
|
+
/**
|
|
36
|
+
* True when the branch is localized AND its type supports it. A malformed definition that slipped past
|
|
37
|
+
* seed validation (e.g. `localized: true` on a `number`) is treated as not localized, never as a dictionary.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isLocalizedBranch(branch: Pick<Branch, 'type' | 'localized'>): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Structural check used on READ paths (no config available): a non-empty plain object whose every key
|
|
42
|
+
* matches the locale-code grammar. A richtext envelope (`schemaVersion`, `doc`) and a TipTap doc
|
|
43
|
+
* (`type`, `content`) never match.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isLocaleDictionary(value: unknown): value is LocalizedDictionary;
|
|
46
|
+
/**
|
|
47
|
+
* Stricter check used on WRITE paths: a locale dictionary with at least one REGISTERED locale key.
|
|
48
|
+
* Without the registration requirement a legitimate json value such as `{"url": "…", "alt": "…"}` —
|
|
49
|
+
* whose keys happen to match the grammar — would be read as a dictionary of unregistered locales and
|
|
50
|
+
* silently emptied.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isLocalizedWriteDictionary(value: unknown, config: LocaleConfig): value is LocalizedDictionary;
|
|
53
|
+
/**
|
|
54
|
+
* Normalises any accepted write value of a localized branch into a {@link LocalizedPatch}:
|
|
55
|
+
* - a write dictionary keeps its registered locales, in `config.locales` order; unregistered locale
|
|
56
|
+
* keys are dropped;
|
|
57
|
+
* - any other value (string, richtext doc, non-dictionary json) is the default-locale value;
|
|
58
|
+
* - `null`, `undefined` and blank strings become `null` ("clear this locale").
|
|
59
|
+
* Idempotent: a patch passed back in yields the same patch.
|
|
60
|
+
*/
|
|
61
|
+
export declare function toLocalizedPatch(value: unknown, config: LocaleConfig): LocalizedPatch;
|
|
62
|
+
/**
|
|
63
|
+
* Removes `null` / `undefined` / blank-string entries. Returns `null` when nothing remains, so an
|
|
64
|
+
* all-cleared dictionary is stored as SQL NULL rather than `{}`. Keys are otherwise preserved as-is —
|
|
65
|
+
* including locales no longer registered, which must never be dropped by a write (brief §2, no data loss).
|
|
66
|
+
*/
|
|
67
|
+
export declare function compactLocalizedDictionary(value: LocalizedDictionary): LocalizedDictionary | null;
|
|
68
|
+
/**
|
|
69
|
+
* The settings subset {@link resolveLocaleConfig} reads. Structural, so this module stays free of
|
|
70
|
+
* settings imports; `SiteSettings` satisfies it.
|
|
71
|
+
*/
|
|
72
|
+
export interface LocaleSettings {
|
|
73
|
+
readonly locales?: readonly string[] | null;
|
|
74
|
+
readonly defaultLocale?: string | null;
|
|
75
|
+
readonly defaultLanguage: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Builds the project's {@link LocaleConfig}, repairing whatever a hand-edited settings row could break so
|
|
79
|
+
* the LocaleConfig invariant always holds:
|
|
80
|
+
* - `locales`: the stored list minus invalid codes and duplicates; when nothing remains (never configured,
|
|
81
|
+
* `[]`, corrupt), the implicit single-language config `[defaultLanguage]` (brief §2), which keeps the
|
|
82
|
+
* feature invisible to a mono-lingual project;
|
|
83
|
+
* - `defaultLocale`: the stored value when it belongs to `locales`, else `locales[0]`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function resolveLocaleConfig(settings: LocaleSettings): LocaleConfig;
|
|
86
|
+
/**
|
|
87
|
+
* Merges a write into the value stored for a localized branch. The write path's only way to persist a
|
|
88
|
+
* localized value, so a translation the write does not mention is never dropped (brief §2):
|
|
89
|
+
* - `write === null` clears the whole field (an explicit null keeps its pre-localization meaning);
|
|
90
|
+
* - otherwise the write is normalised with {@link toLocalizedPatch}: every registered locale it names
|
|
91
|
+
* overwrites that locale, a `null` entry clears that locale only, and every other stored locale —
|
|
92
|
+
* including ones no longer registered — is kept as-is;
|
|
93
|
+
* - a stored legacy value (written before the branch became localized) is the default-locale value.
|
|
94
|
+
* @returns The compacted dictionary, or `null` when no locale keeps a value.
|
|
95
|
+
*/
|
|
96
|
+
export declare function applyLocalizedPatch(branch: Pick<Branch, 'type' | 'localized'>, stored: unknown, write: unknown, config: LocaleConfig): LocalizedDictionary | null;
|
|
97
|
+
/** Aliases of the localized branches a write carries — the fields it must merge rather than replace. */
|
|
98
|
+
export declare function localizedAliasesIn(seed: Pick<Seed, 'branches'>, data: Record<string, unknown>): string[];
|
|
99
|
+
/**
|
|
100
|
+
* Returns `data` with every localized branch it carries merged into `stored` via
|
|
101
|
+
* {@link applyLocalizedPatch}; other keys pass through untouched. `stored` is the entry's current values
|
|
102
|
+
* (`null` on create, which only compacts). Returns `data` itself when `config` is undefined or no localized
|
|
103
|
+
* branch is written.
|
|
104
|
+
*/
|
|
105
|
+
export declare function mergeLocalizedFields(seed: Pick<Seed, 'branches'>, stored: Record<string, unknown> | null, data: Record<string, unknown>, config: LocaleConfig | undefined): Record<string, unknown>;
|
|
106
|
+
/**
|
|
107
|
+
* Resolves a stored localized value to one language: the requested locale, then the default locale, then the
|
|
108
|
+
* first stored translation (stored key order), then `null`. A legacy value that is not a dictionary is returned
|
|
109
|
+
* as-is. Non-localized branches return `value` unchanged. `buildSelectQuery` applies the same chain in SQL, so
|
|
110
|
+
* filters and ORDER BY compare exactly what a reader sees.
|
|
111
|
+
*/
|
|
112
|
+
export declare function resolveLocalizedValue(branch: Pick<Branch, 'type' | 'localized'>, value: unknown, locale: string, config: LocaleConfig): unknown;
|
|
113
|
+
/**
|
|
114
|
+
* Returns a copy of `data` with every localized branch resolved to `locale` (default: the default locale)
|
|
115
|
+
* via {@link resolveLocalizedValue}. Returns `data` itself when `config` is undefined.
|
|
116
|
+
*/
|
|
117
|
+
export declare function resolveLocalizedFields(seed: Pick<Seed, 'branches'>, data: Record<string, unknown>, config: LocaleConfig | undefined, locale?: string): Record<string, unknown>;
|
|
118
|
+
/**
|
|
119
|
+
* The whole dictionary of a stored localized value, for readers asking every language (`?lang=all`):
|
|
120
|
+
* a stored dictionary as-is — unregistered locales included, nothing is hidden (brief §2) — a legacy
|
|
121
|
+
* value as `{ [defaultLocale]: value }` (the write path's reading of it), a blank value as `null`.
|
|
122
|
+
* Non-localized branches return `value` unchanged.
|
|
123
|
+
*/
|
|
124
|
+
export declare function asLocaleDictionary(branch: Pick<Branch, 'type' | 'localized'>, value: unknown, config: LocaleConfig): unknown;
|
|
125
|
+
/** Returns a copy of `data` with every localized branch it carries passed through {@link asLocaleDictionary}. */
|
|
126
|
+
export declare function asLocaleDictionaries(seed: Pick<Seed, 'branches'>, data: Record<string, unknown>, config: LocaleConfig): Record<string, unknown>;
|
|
127
|
+
//# sourceMappingURL=localization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localization.d.ts","sourceRoot":"","sources":["../../../src/engine/localization/localization.ts"],"names":[],"mappings":"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"}
|