@better-forge/config-as-code 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BetterStatusPage contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,10 @@
1
- # Temporary Holding Version
1
+ # @better-forge/config-as-code
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Configuration as code: the objects of an application as YAML documents, exported and applied back.
4
+
5
+ - `createConfigEngine` reads a stream of documents (`---` separated), each with a `kind` registered by the application, checks the whole file, then either reports what would change (`dryRun`) or applies it in one transaction, writes the audit entries after the commit and exports the documents again. A kind supplies how to parse, plan, apply and export its documents; the engine supplies the checks that do not depend on the kind (keys, unknown fields, `__proto__`, at most 100 problems), vault references by name, and the order of everything.
6
+ - `createConfigRoutes` serves `GET /export` and `POST /validate`, `POST /apply` with the permission checks per kind (API token resource or user role), the media type and size checks.
7
+ - `toYaml`, `toYamlStream`, `readDocuments` write and read the YAML (1.1-safe quoting, no folding, limits on documents and aliases).
8
+ - Entity keys: `isValidEntityKey`, `slugifyKey`, `keyForNew` and `backfillKeys` give every object the stable key a document refers to it by.
9
+
10
+ The database, the transaction and the audit log are supplied by the application. Depends on `@better-forge/contracts` (token permissions) and `yaml`.
@@ -0,0 +1,68 @@
1
+ export type Json = Record<string, unknown>;
2
+ export declare const isObject: (value: unknown) => value is Json;
3
+ export interface ConfigProblem {
4
+ path: string;
5
+ message: string;
6
+ }
7
+ /** The file is not valid; `problems` lists what is wrong with it, not just the first thing. */
8
+ export declare class ConfigInvalidError extends Error {
9
+ readonly problems: ConfigProblem[];
10
+ constructor(problems: ConfigProblem[]);
11
+ }
12
+ /** Collects what is wrong with a file. */
13
+ export declare class Problems {
14
+ readonly list: ConfigProblem[];
15
+ add: (path: string, message: string) => void;
16
+ get any(): boolean;
17
+ }
18
+ /** Shortens text that comes from a request before it is repeated in an answer, so an error cannot be made to echo a megabyte. */
19
+ export declare function clip(value: string, max?: number): string;
20
+ /** Whether `value` nests objects or lists deeper than `max` levels. Checked without recursion, so it cannot overflow the stack. */
21
+ export declare function nestedDeeperThan(value: unknown, max: number): boolean;
22
+ /** The same value written with its keys in a fixed order, so equal settings compare equal whatever their order. */
23
+ export declare function canonical(value: unknown): string;
24
+ /** Orders documents by key. A tie sorts the second one first, which never happens: keys are unique. */
25
+ export declare const byKey: (a: {
26
+ key: string;
27
+ }, b: {
28
+ key: string;
29
+ }) => 1 | -1;
30
+ /** Reports every field of `entry` that is not in `allowed`. */
31
+ export declare function unknownFields(entry: Json, allowed: readonly string[], path: string, problems: Problems): void;
32
+ /** Where a name the parser would treat specially (`__proto__`) appears in a document, or null. Walks without recursion. */
33
+ export declare function forbiddenNameAt(document: unknown, label: string): string | null;
34
+ /** A dependency cycle among `edges` (key -> keys it depends on), as `a → b → a`, or null. */
35
+ export declare function findCycle(edges: Map<string, string[]>): string | null;
36
+ /** The vaults and secrets of an installation, as far as a file needs to name them. */
37
+ export interface VaultRows {
38
+ vaults: ReadonlyArray<{
39
+ id: number;
40
+ name: string;
41
+ }>;
42
+ secrets: ReadonlyArray<{
43
+ id: number;
44
+ vaultId: number;
45
+ name: string;
46
+ }>;
47
+ }
48
+ /** A vault secret named instead of numbered, so the file means the same on another installation. */
49
+ export interface ConfigVaultRef {
50
+ vault: string;
51
+ secret: string;
52
+ fieldMapping?: Record<string, string>;
53
+ }
54
+ /** `{ vault: 'Production', secret: 'db-login' }` back to the ids this installation knows them by. */
55
+ export declare function createVaultResolver(rows: VaultRows): (value: unknown, path: string, problems: Problems) => unknown;
56
+ /** The file would refer to something it cannot name unambiguously, so it could not be read back. */
57
+ export declare class ConfigExportError extends Error {
58
+ }
59
+ /** Replaces every `vault: { vaultId, secretId }` reference in a config with the names of the vault and secret. */
60
+ export declare function createVaultExporter(rows: VaultRows): (value: unknown) => unknown;
61
+ /** A list of keys (`items: [a, b]`), without repeats; problems are reported at `path`. */
62
+ export declare function keyList(value: unknown, path: string, problems: Problems, isValidKey: (key: unknown) => boolean): string[];
63
+ /** The key of a document, or '' (with a problem) when it has none that is usable; a repeat within `seen` is a problem too. */
64
+ export declare function documentKey(entry: Json, path: string, seen: Set<string>, problems: Problems, keys: {
65
+ isValid: (key: unknown) => boolean;
66
+ error: string;
67
+ }): string;
68
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,189 @@
1
+ export const isObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
2
+ /** The file is not valid; `problems` lists what is wrong with it, not just the first thing. */
3
+ export class ConfigInvalidError extends Error {
4
+ problems;
5
+ constructor(problems) {
6
+ super(problems[0] ? `${problems[0].path}: ${problems[0].message}` : 'Invalid configuration');
7
+ this.problems = problems;
8
+ }
9
+ }
10
+ /** Most problems a file reports: the first ones are the ones to fix first. */
11
+ const MAX_PROBLEMS = 100;
12
+ /** Collects what is wrong with a file. */
13
+ export class Problems {
14
+ list = [];
15
+ add = (path, message) => { if (this.list.length < MAX_PROBLEMS)
16
+ this.list.push({ path, message }); };
17
+ get any() { return this.list.length > 0; }
18
+ }
19
+ /** Shortens text that comes from a request before it is repeated in an answer, so an error cannot be made to echo a megabyte. */
20
+ export function clip(value, max = 80) {
21
+ return value.length > max ? `${value.slice(0, max)}…` : value;
22
+ }
23
+ /** Whether `value` nests objects or lists deeper than `max` levels. Checked without recursion, so it cannot overflow the stack. */
24
+ export function nestedDeeperThan(value, max) {
25
+ const pending = [[value, 0]];
26
+ while (pending.length) {
27
+ const [current, depth] = pending.pop();
28
+ if (!current || typeof current !== 'object')
29
+ continue;
30
+ if (depth >= max)
31
+ return true;
32
+ for (const inner of Object.values(current))
33
+ pending.push([inner, depth + 1]);
34
+ }
35
+ return false;
36
+ }
37
+ /** The same value written with its keys in a fixed order, so equal settings compare equal whatever their order. */
38
+ export function canonical(value) {
39
+ return JSON.stringify(value, (_key, inner) => isObject(inner) ? Object.fromEntries(Object.keys(inner).sort().map((key) => [key, inner[key]])) : inner);
40
+ }
41
+ /** Orders documents by key. A tie sorts the second one first, which never happens: keys are unique. */
42
+ export const byKey = (a, b) => (a.key < b.key ? -1 : 1);
43
+ /** Reports every field of `entry` that is not in `allowed`. */
44
+ export function unknownFields(entry, allowed, path, problems) {
45
+ for (const field of Object.keys(entry))
46
+ if (!allowed.includes(field))
47
+ problems.add(`${path}.${clip(field)}`, 'is not a known setting');
48
+ }
49
+ /** Where a name the parser would treat specially (`__proto__`) appears in a document, or null. Walks without recursion. */
50
+ export function forbiddenNameAt(document, label) {
51
+ const pending = [[document, label]];
52
+ while (pending.length) {
53
+ const [value, path] = pending.pop();
54
+ if (!value || typeof value !== 'object')
55
+ continue;
56
+ if (!Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, '__proto__'))
57
+ return `${path}.__proto__`;
58
+ const entries = Array.isArray(value) ? value.map((item, index) => [`[${index}]`, item]) : Object.entries(value);
59
+ for (const [name, inner] of entries)
60
+ pending.push([inner, name.startsWith('[') ? `${path}${name}` : `${path}.${clip(name)}`]);
61
+ }
62
+ return null;
63
+ }
64
+ /** A dependency cycle among `edges` (key -> keys it depends on), as `a → b → a`, or null. */
65
+ export function findCycle(edges) {
66
+ const done = new Set();
67
+ const stack = [];
68
+ const visit = (key) => {
69
+ const at = stack.indexOf(key);
70
+ if (at >= 0)
71
+ return [...stack.slice(at), key].join(' → ');
72
+ if (done.has(key))
73
+ return null;
74
+ stack.push(key);
75
+ for (const next of edges.get(key) ?? []) {
76
+ const cycle = visit(next);
77
+ if (cycle)
78
+ return cycle;
79
+ }
80
+ stack.pop();
81
+ done.add(key);
82
+ return null;
83
+ };
84
+ for (const key of edges.keys()) {
85
+ const cycle = visit(key);
86
+ if (cycle)
87
+ return cycle;
88
+ }
89
+ return null;
90
+ }
91
+ /** `{ vault: 'Production', secret: 'db-login' }` back to the ids this installation knows them by. */
92
+ export function createVaultResolver(rows) {
93
+ const vaultsByName = new Map();
94
+ for (const vault of rows.vaults)
95
+ vaultsByName.set(vault.name, [...(vaultsByName.get(vault.name) ?? []), vault.id]);
96
+ const secretId = new Map(rows.secrets.map((secret) => [`${secret.vaultId}:${secret.name}`, secret.id]));
97
+ return function resolve(value, path, problems) {
98
+ if (Array.isArray(value))
99
+ return value.map((item, index) => resolve(item, `${path}[${index}]`, problems));
100
+ if (!isObject(value))
101
+ return value;
102
+ return Object.fromEntries(Object.entries(value).map(([key, inner]) => {
103
+ if (key !== 'vault')
104
+ return [key, resolve(inner, `${path}.${key}`, problems)];
105
+ const at = `${path}.vault`;
106
+ if (!isObject(inner) || typeof inner['vault'] !== 'string' || typeof inner['secret'] !== 'string') {
107
+ problems.add(at, 'must name the vault and the secret: { vault: <name>, secret: <name> }');
108
+ return [key, inner];
109
+ }
110
+ if ('vaultId' in inner || 'secretId' in inner)
111
+ problems.add(at, 'a configuration file names a vault secret, it does not use numeric ids');
112
+ const found = vaultsByName.get(inner['vault']) ?? [];
113
+ if (found.length !== 1) {
114
+ problems.add(at, found.length === 0
115
+ ? (inner['vault'].startsWith('(deleted') ? `${clip(inner['vault'])} refers to a vault that no longer exists` : `unknown vault "${clip(inner['vault'])}"`)
116
+ : `more than one vault is named "${clip(inner['vault'])}"; rename one of them`);
117
+ return [key, inner];
118
+ }
119
+ const secret = secretId.get(`${found[0]}:${inner['secret']}`);
120
+ if (secret === undefined) {
121
+ problems.add(at, inner['secret'].startsWith('(deleted') ? `${clip(inner['secret'])} refers to a secret that no longer exists` : `vault "${clip(inner['vault'])}" has no secret "${clip(inner['secret'])}"`);
122
+ return [key, inner];
123
+ }
124
+ return [key, { vaultId: found[0], secretId: secret, ...(isObject(inner['fieldMapping']) ? { fieldMapping: inner['fieldMapping'] } : {}) }];
125
+ }));
126
+ };
127
+ }
128
+ /** The file would refer to something it cannot name unambiguously, so it could not be read back. */
129
+ export class ConfigExportError extends Error {
130
+ }
131
+ const deleted = (what, id) => `(deleted ${what} ${String(id)})`;
132
+ function isVaultRef(value) {
133
+ return isObject(value) && typeof value['vaultId'] === 'number' && typeof value['secretId'] === 'number';
134
+ }
135
+ /** Replaces every `vault: { vaultId, secretId }` reference in a config with the names of the vault and secret. */
136
+ export function createVaultExporter(rows) {
137
+ const nameCount = new Map();
138
+ for (const vault of rows.vaults)
139
+ nameCount.set(vault.name, (nameCount.get(vault.name) ?? 0) + 1);
140
+ const vaultName = new Map(rows.vaults.map((vault) => [vault.id, vault.name]));
141
+ const secret = new Map(rows.secrets.map((row) => [row.id, row]));
142
+ function exportRef(ref) {
143
+ const vault = vaultName.get(ref.vaultId);
144
+ const found = secret.get(ref.secretId);
145
+ if (vault !== undefined && (nameCount.get(vault) ?? 0) > 1) {
146
+ throw new ConfigExportError(`More than one vault is named "${vault}", so a reference to it cannot be exported. Rename one of them.`);
147
+ }
148
+ return {
149
+ vault: vault ?? deleted('vault', ref.vaultId),
150
+ secret: found && found.vaultId === ref.vaultId ? found.name : deleted('secret', ref.secretId),
151
+ ...(isObject(ref.fieldMapping) ? { fieldMapping: ref.fieldMapping } : {}),
152
+ };
153
+ }
154
+ return function exportRefs(value) {
155
+ if (Array.isArray(value))
156
+ return value.map((item) => exportRefs(item));
157
+ if (!isObject(value))
158
+ return value;
159
+ return Object.fromEntries(Object.entries(value).map(([key, inner]) => [key, key === 'vault' && isVaultRef(inner) ? exportRef(inner) : exportRefs(inner)]));
160
+ };
161
+ }
162
+ // ── Fields every kind of document reads the same way ─────────────────────────
163
+ /** A list of keys (`items: [a, b]`), without repeats; problems are reported at `path`. */
164
+ export function keyList(value, path, problems, isValidKey) {
165
+ if (value === undefined)
166
+ return [];
167
+ if (!Array.isArray(value) || !value.every((item) => typeof item === 'string')) {
168
+ problems.add(path, 'must be a list of keys');
169
+ return [];
170
+ }
171
+ for (const key of value)
172
+ if (!isValidKey(key))
173
+ problems.add(path, `"${clip(key)}" is not a valid key`);
174
+ return [...new Set(value)];
175
+ }
176
+ /** The key of a document, or '' (with a problem) when it has none that is usable; a repeat within `seen` is a problem too. */
177
+ export function documentKey(entry, path, seen, problems, keys) {
178
+ const key = entry['key'];
179
+ if (!keys.isValid(key)) {
180
+ problems.add(`${path}.key`, key === undefined ? 'is required' : keys.error);
181
+ return '';
182
+ }
183
+ const text = key;
184
+ if (seen.has(text))
185
+ problems.add(`${path}.key`, `"${clip(text)}" is used by another document`);
186
+ seen.add(text);
187
+ return text;
188
+ }
189
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import { Problems, type Json, type VaultRows } from './document.js';
2
+ export type ChangeAction = 'create' | 'update' | 'delete' | 'unchanged';
3
+ export interface ConfigChange {
4
+ kind: string;
5
+ /** The key of the object the document describes. */
6
+ key?: string;
7
+ action: ChangeAction;
8
+ /** For an update: which settings differ. Never their values, which may be secrets. */
9
+ fields?: string[];
10
+ }
11
+ export interface ImportResult {
12
+ dryRun: boolean;
13
+ /** `delete` is present only for an import with `prune`. */
14
+ summary: Record<'create' | 'update' | 'unchanged', number> & {
15
+ delete?: number;
16
+ };
17
+ changes: ConfigChange[];
18
+ }
19
+ /** What one change did, for the audit log. */
20
+ export interface AuditEntry {
21
+ action: 'create' | 'update' | 'delete';
22
+ entity: string;
23
+ id: number | string;
24
+ name: string;
25
+ diff: Json;
26
+ }
27
+ export interface ImportOptions<Actor> {
28
+ dryRun: boolean;
29
+ actor: Actor;
30
+ /** Set when the request is made with an API token: what it may do is decided per kind. */
31
+ token?: {
32
+ scopes: readonly string[];
33
+ } | undefined;
34
+ /** Also delete the objects of the kinds the documents hold that the documents do not mention. Off by default. */
35
+ prune?: boolean;
36
+ }
37
+ /** What a kind sees while it reads the documents of a file. */
38
+ export interface PlanContext<State> {
39
+ state: State;
40
+ token: {
41
+ scopes: readonly string[];
42
+ } | undefined;
43
+ problems: Problems;
44
+ /** `{ vault: <name>, secret: <name> }` references in a config, turned into the ids of this installation. */
45
+ resolveVaults: (value: unknown, path: string) => unknown;
46
+ /** The documents of a kind the file holds, as that kind parsed them. */
47
+ desired: (kind: string) => unknown[];
48
+ /** The keys of a kind that an import with `prune` deletes: they do not exist afterwards, so nothing may link to them. */
49
+ removed: (kind: string) => ReadonlySet<string>;
50
+ }
51
+ /** What a kind sees while it writes. */
52
+ export interface ApplyContext<State> {
53
+ state: State;
54
+ now: number;
55
+ /** Ids by key, per kind: the objects that exist now, plus those created so far in this run. */
56
+ ids: (kind: string) => Map<string, number>;
57
+ }
58
+ /**
59
+ * One kind of document. `parse` reads a document of the kind (reporting problems), `plan` compares what the file
60
+ * holds with the installation and says what would change, `apply` writes it, `link` runs after every kind is written
61
+ * (for references to objects that might be created later in the same file), `export` writes the kind out.
62
+ */
63
+ export interface ConfigKind<State, ExportState, Desired = any, Planned = any> {
64
+ kind: string;
65
+ parse: (entry: Json, index: number, seen: Set<string>, problems: Problems) => Desired | null;
66
+ plan: (desired: Desired[], ctx: PlanContext<State>) => {
67
+ planned: Planned;
68
+ changes: ConfigChange[];
69
+ };
70
+ /** The ids of the objects of this kind that exist, by key. */
71
+ existingIds: (state: State) => Map<string, number>;
72
+ apply: (planned: Planned, ctx: ApplyContext<State>) => Promise<AuditEntry[]>;
73
+ link?: (planned: Planned, ctx: ApplyContext<State>) => Promise<void>;
74
+ /** Deletes the objects with these keys, and whatever links to them. A kind without it is never pruned. */
75
+ remove?: (keys: string[], ctx: ApplyContext<State>) => Promise<AuditEntry[]>;
76
+ /** The documents of this kind, sorted by key. `only` limits them to one key (and nothing else is written out). */
77
+ export: (state: ExportState, ctx: ExportContext) => Json[];
78
+ }
79
+ export interface ExportContext {
80
+ /** Replaces the vault references of a config with the names of the vault and secret. */
81
+ exportVaultRefs: (value: unknown) => unknown;
82
+ /** The rows to write out: all of them, or only the one asked for. */
83
+ wanted: <T extends {
84
+ key: string;
85
+ }>(rows: T[], kind: string) => T[];
86
+ /** Whether the kind is part of this export (a kind may be left out for a reader who may not see it). */
87
+ included: (kind: string) => boolean;
88
+ }
89
+ export interface ConfigEngineOptions<State, ExportState, Actor> {
90
+ /** The kinds, in the order messages name them. */
91
+ kinds: ReadonlyArray<ConfigKind<State, ExportState>>;
92
+ /** The order kinds are planned, written and exported in (kind names); defaults to `kinds`. */
93
+ order?: readonly string[];
94
+ /** Reads what the installation holds. Called inside the transaction when the file is applied. */
95
+ loadState: () => Promise<State>;
96
+ /** Reads what an export needs; a kind not in `included` need not be read. */
97
+ loadExportState: (included: ReadonlySet<string>) => Promise<ExportState>;
98
+ vaultRows: (state: State) => VaultRows;
99
+ exportVaultRows: (state: ExportState) => VaultRows;
100
+ /** Runs `work` so that everything it writes happens, or none of it does. */
101
+ transaction: <T>(work: () => Promise<T>) => Promise<T>;
102
+ audit: {
103
+ write: (actor: Actor, action: 'create' | 'update' | 'delete', entity: string, id: number | string | null, name: string, diff?: Json) => Promise<void>;
104
+ };
105
+ /** Runs once after a successful apply that changed something, before the audit entries are written. */
106
+ afterApply?: () => Promise<void>;
107
+ /** Entity type of the summary entry written to the audit log. */
108
+ auditEntity?: string;
109
+ }
110
+ /** Makes the installation match the documents, completely or not at all, and writes it out again as documents. */
111
+ export declare function createConfigEngine<State, ExportState, Actor>(options: ConfigEngineOptions<State, ExportState, Actor>): {
112
+ kinds: string[];
113
+ kindsIn: (documents: unknown[]) => Set<string>;
114
+ importDocuments: (documents: unknown[], importOptions: ImportOptions<Actor>) => Promise<ImportResult>;
115
+ exportDocuments: (only?: {
116
+ kind: string;
117
+ key: string;
118
+ }, exportOptions?: {
119
+ included?: ReadonlySet<string>;
120
+ }) => Promise<Json[]>;
121
+ pickDocument: (documents: Json[], kind: string, key: string | undefined) => Json | undefined;
122
+ };
123
+ export type ConfigEngine<State, ExportState, Actor> = ReturnType<typeof createConfigEngine<State, ExportState, Actor>>;
124
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiG,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAA;AAElK,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAA;AAEvE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,YAAY,CAAA;IACpB,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAA;IACf,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,GAAG,WAAW,EAAE,MAAM,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChF,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,UAAU;IAAG,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE;AAErI,MAAM,WAAW,aAAa,CAAC,KAAK;IAClC,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,KAAK,CAAA;IACZ,0FAA0F;IAC1F,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,SAAS,CAAA;IACjD,iHAAiH;IACjH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,+DAA+D;AAC/D,MAAM,WAAW,WAAW,CAAC,KAAK;IAChC,KAAK,EAAE,KAAK,CAAA;IACZ,KAAK,EAAE;QAAE,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,SAAS,CAAA;IAChD,QAAQ,EAAE,QAAQ,CAAA;IAClB,4GAA4G;IAC5G,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IACxD,wEAAwE;IACxE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,EAAE,CAAA;IACpC,yHAAyH;IACzH,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,WAAW,CAAC,MAAM,CAAC,CAAA;CAC/C;AAED,wCAAwC;AACxC,MAAM,WAAW,YAAY,CAAC,KAAK;IACjC,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,+FAA+F;IAC/F,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC3C;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG;IAC1E,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,GAAG,IAAI,CAAA;IAC5F,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,YAAY,EAAE,CAAA;KAAE,CAAA;IACpG,8DAA8D;IAC9D,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAClD,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IAC5E,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE,0GAA0G;IAC1G,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IAC5E,kHAAkH;IAClH,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,aAAa,KAAK,IAAI,EAAE,CAAA;CAC3D;AAED,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;IAC5C,qEAAqE;IACrE,MAAM,EAAE,CAAC,CAAC,SAAS;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC,EAAE,CAAA;IACnE,wGAAwG;IACxG,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CACpC;AAED,MAAM,WAAW,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK;IAC5D,kDAAkD;IAClD,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAA;IACpD,8FAA8F;IAC9F,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,iGAAiG;IACjG,SAAS,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,CAAA;IAC/B,6EAA6E;IAC7E,eAAe,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;IACxE,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAA;IACtC,eAAe,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,SAAS,CAAA;IAClD,4EAA4E;IAC5E,WAAW,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IACtD,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAA;IAChK,uGAAuG;IACvG,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,kHAAkH;AAClH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC;;yBAMvF,OAAO,EAAE,KAAG,GAAG,CAAC,MAAM,CAAC;iCA4FT,OAAO,EAAE,iBAAiB,aAAa,CAAC,KAAK,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC;6BAqC1E;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,kBAAiB;QAAE,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;KAAE,KAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;8BAa5G,IAAI,EAAE,QAAQ,MAAM,OAAO,MAAM,GAAG,SAAS,KAAG,IAAI,GAAG,SAAS;EAKlG;AAED,MAAM,MAAM,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,IAAI,UAAU,CAAC,OAAO,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAA"}
package/dist/engine.js ADDED
@@ -0,0 +1,163 @@
1
+ import { clip, ConfigInvalidError, createVaultExporter, createVaultResolver, forbiddenNameAt, isObject, Problems } from './document.js';
2
+ /** Makes the installation match the documents, completely or not at all, and writes it out again as documents. */
3
+ export function createConfigEngine(options) {
4
+ const kinds = new Map(options.kinds.map((kind) => [kind.kind, kind]));
5
+ const names = options.kinds.map((kind) => kind.kind);
6
+ const order = (options.order ?? names).map((name) => kinds.get(name));
7
+ /** The kinds a list of documents contains, so that the caller can check it may import each. Documents of no known kind are ignored here. */
8
+ function kindsIn(documents) {
9
+ const found = new Set();
10
+ for (const document of documents) {
11
+ if (isObject(document) && names.includes(document['kind']))
12
+ found.add(document['kind']);
13
+ }
14
+ return found;
15
+ }
16
+ function parseDocuments(documents, problems) {
17
+ const desired = new Map(names.map((name) => [name, []]));
18
+ const seenKeys = new Map(names.map((name) => [name, new Set()]));
19
+ documents.forEach((document, index) => {
20
+ const label = `document ${index + 1}`;
21
+ const poisoned = forbiddenNameAt(document, label);
22
+ if (poisoned) {
23
+ problems.add(poisoned, '"__proto__" is not allowed as a name');
24
+ return;
25
+ }
26
+ if (!isObject(document)) {
27
+ problems.add(label, 'must be an object with a kind');
28
+ return;
29
+ }
30
+ const kind = kinds.get(document['kind']);
31
+ if (!kind) {
32
+ problems.add(`${label}.kind`, document['kind'] === undefined
33
+ ? `is required: one of ${names.join(', ')}`
34
+ : `"${clip(String(document['kind']))}" is not a kind; use one of ${names.join(', ')}`);
35
+ return;
36
+ }
37
+ const parsed = kind.parse(document, index, seenKeys.get(kind.kind), problems);
38
+ if (parsed)
39
+ desired.get(kind.kind).push(parsed);
40
+ });
41
+ return desired;
42
+ }
43
+ /** The keys to delete, per kind. A kind with no document in the file is left alone, so an empty file prunes nothing. */
44
+ function stale(state, desired) {
45
+ const found = new Map();
46
+ for (const kind of order) {
47
+ const documents = desired.get(kind.kind) ?? [];
48
+ if (!kind.remove || documents.length === 0)
49
+ continue;
50
+ const wanted = new Set(documents.map((document) => document.key));
51
+ const keys = [...kind.existingIds(state).keys()].filter((key) => !wanted.has(key)).sort();
52
+ if (keys.length)
53
+ found.set(kind.kind, keys);
54
+ }
55
+ return found;
56
+ }
57
+ function plan(state, desired, token, prune) {
58
+ const problems = new Problems();
59
+ const resolve = createVaultResolver(options.vaultRows(state));
60
+ const removals = prune ? stale(state, desired) : new Map();
61
+ const ctx = {
62
+ state, token, problems,
63
+ resolveVaults: (value, path) => resolve(value, path, problems),
64
+ desired: (kind) => desired.get(kind) ?? [],
65
+ removed: (kind) => new Set(removals.get(kind) ?? []),
66
+ };
67
+ const planned = new Map();
68
+ const changes = [];
69
+ for (const kind of order) {
70
+ const result = kind.plan(desired.get(kind.kind) ?? [], ctx);
71
+ planned.set(kind.kind, result.planned);
72
+ changes.push(...result.changes);
73
+ }
74
+ if (problems.any)
75
+ throw new ConfigInvalidError(problems.list);
76
+ for (const kind of order)
77
+ for (const key of removals.get(kind.kind) ?? [])
78
+ changes.push({ kind: kind.kind, key, action: 'delete' });
79
+ return { planned, changes, removals };
80
+ }
81
+ async function execute(state, planned, removals) {
82
+ const ids = new Map(names.map((name) => [name, kinds.get(name).existingIds(state)]));
83
+ const ctx = { state, now: Date.now(), ids: (kind) => ids.get(kind) };
84
+ const audit = [];
85
+ for (const kind of order)
86
+ audit.push(...await kind.apply(planned.get(kind.kind), ctx));
87
+ for (const kind of order)
88
+ await kind.link?.(planned.get(kind.kind), ctx);
89
+ // Last, and in the reverse order, so that what links to a kind is deleted before the kind.
90
+ for (const kind of [...order].reverse()) {
91
+ const keys = removals.get(kind.kind);
92
+ if (keys)
93
+ audit.push(...await kind.remove(keys, ctx));
94
+ }
95
+ return audit;
96
+ }
97
+ function summarise(changes, prune) {
98
+ const summary = { create: 0, update: 0, delete: 0, unchanged: 0 };
99
+ for (const change of changes)
100
+ summary[change.action] += 1;
101
+ if (!prune)
102
+ return { create: summary.create, update: summary.update, unchanged: summary.unchanged };
103
+ return summary;
104
+ }
105
+ /**
106
+ * Makes the installation match the documents: an object whose key exists is updated, one whose key does not is created.
107
+ * Nothing is removed unless `prune` is set. Everything is checked before anything is written, and the writes are one transaction, so a
108
+ * stream is applied completely or not at all. With `dryRun` nothing is written and the result says what would happen.
109
+ */
110
+ async function importDocuments(documents, importOptions) {
111
+ const problems = new Problems();
112
+ const desired = parseDocuments(documents, problems);
113
+ if (problems.any)
114
+ throw new ConfigInvalidError(problems.list);
115
+ const result = (changes) => ({ dryRun: importOptions.dryRun, summary: summarise(changes, !!importOptions.prune), changes });
116
+ if (importOptions.dryRun)
117
+ return result(plan(await options.loadState(), desired, importOptions.token, !!importOptions.prune).changes);
118
+ const { changes, audit } = await options.transaction(async () => {
119
+ const state = await options.loadState();
120
+ const { planned, changes, removals } = plan(state, desired, importOptions.token, !!importOptions.prune);
121
+ const audit = changes.some((change) => change.action !== 'unchanged') ? await execute(state, planned, removals) : [];
122
+ return { changes, audit };
123
+ });
124
+ // The changes are committed. A failure past this point must not turn a successful import into an error (the
125
+ // client would retry, and a second apply is a no-op), so it is logged instead.
126
+ if (audit.length) {
127
+ try {
128
+ await options.afterApply?.();
129
+ for (const entry of audit)
130
+ await options.audit.write(importOptions.actor, entry.action, entry.entity, entry.id, entry.name, entry.diff);
131
+ const summary = summarise(changes, !!importOptions.prune);
132
+ await options.audit.write(importOptions.actor, 'update', options.auditEntity ?? 'config', null, 'Configuration import', {
133
+ created: summary.create, updated: summary.update, ...(importOptions.prune ? { deleted: summary.delete ?? 0 } : {}), unchanged: summary.unchanged,
134
+ });
135
+ }
136
+ catch (error) {
137
+ console.error('[config] Imported, but could not finish the follow-up work:', error);
138
+ }
139
+ }
140
+ return result(changes);
141
+ }
142
+ /**
143
+ * The documents of every kind, in a fixed order that does not depend on this installation's numeric ids. Secrets are
144
+ * masked by the kinds, and runtime state is left out. `only` writes out one document; `included` leaves out kinds.
145
+ */
146
+ async function exportDocuments(only, exportOptions = {}) {
147
+ const included = exportOptions.included ?? new Set(names);
148
+ const state = await options.loadExportState(included);
149
+ const ctx = {
150
+ exportVaultRefs: createVaultExporter(options.exportVaultRows(state)),
151
+ // Only the rows asked for are written out, so a vault name that is ambiguous for some other object does not matter.
152
+ wanted: (rows, kind) => (only ? rows.filter((row) => only.kind === kind && row.key === only.key) : rows),
153
+ included: (kind) => included.has(kind),
154
+ };
155
+ return order.filter((kind) => included.has(kind.kind)).flatMap((kind) => kind.export(state, ctx));
156
+ }
157
+ /** The one document of a kind and key, or undefined when there is none. */
158
+ function pickDocument(documents, kind, key) {
159
+ return documents.find((document) => document['kind'] === kind && document['key'] === key);
160
+ }
161
+ return { kinds: names, kindsIn, importDocuments, exportDocuments, pickDocument };
162
+ }
163
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}