@betterportal/config-manager 10.6.24 → 10.6.26
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/lib/.bsb/clients/service-betterportal-config-manager.d.ts +1 -1
- package/lib/.bsb/clients/service-betterportal-config-manager.js +1 -1
- package/lib/bp-contracts/org.betterportal.config-manager.json +1 -1
- package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts +1 -0
- package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/adminApi.js +257 -196
- package/lib/plugins/service-betterportal-config-manager/adminApi.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/preview-environments/_renderer.bootstrap5/GET.js +2 -2
- package/lib/plugins/service-betterportal-config-manager/bp-routes/preview-environments/_renderer.bootstrap5/GET.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_renderer.bootstrap5/GET.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_renderer.bootstrap5/GET.js +20 -19
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_renderer.bootstrap5/GET.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/index.d.ts +0 -1
- package/lib/plugins/service-betterportal-config-manager/index.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/index.js +3 -5
- package/lib/plugins/service-betterportal-config-manager/index.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/menuEditor.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/menuEditor.js +58 -6
- package/lib/plugins/service-betterportal-config-manager/menuEditor.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/previewEnvironments.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/previewEnvironments.js +5 -3
- package/lib/plugins/service-betterportal-config-manager/previewEnvironments.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/routeMounts.d.ts +16 -0
- package/lib/plugins/service-betterportal-config-manager/routeMounts.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/routeMounts.js +8 -0
- package/lib/plugins/service-betterportal-config-manager/routeMounts.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts +3 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.js +28 -10
- package/lib/plugins/service-betterportal-config-manager/storage/core.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/entities.d.ts +20 -0
- package/lib/plugins/service-betterportal-config-manager/storage/entities.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/storage/entities.js +162 -0
- package/lib/plugins/service-betterportal-config-manager/storage/entities.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.d.ts +7 -0
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.js +201 -56
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts +8 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.js +9 -2
- package/lib/plugins/service-betterportal-config-manager/syncApi.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/tenantManagement.d.ts +2 -0
- package/lib/plugins/service-betterportal-config-manager/tenantManagement.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/tenantManagement.js +13 -2
- package/lib/plugins/service-betterportal-config-manager/tenantManagement.js.map +1 -1
- package/lib/schemas/service-betterportal-config-manager.json +1 -1
- package/lib/schemas/service-betterportal-config-manager.plugin.json +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BetterPortalConfig } from "@betterportal/framework";
|
|
2
|
+
/** Each entry is an independently persisted/revisioned configuration aggregate. */
|
|
3
|
+
export interface ConfigEntity {
|
|
4
|
+
kind: string;
|
|
5
|
+
id: string;
|
|
6
|
+
value: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
export interface ConfigEntityRow extends ConfigEntity {
|
|
9
|
+
revision: string | number;
|
|
10
|
+
}
|
|
11
|
+
export declare const entityKey: (kind: string, id: string) => string;
|
|
12
|
+
export declare const keyOf: (entity: ConfigEntity) => string;
|
|
13
|
+
export declare function splitConfig(config: BetterPortalConfig): Map<string, ConfigEntity>;
|
|
14
|
+
export declare function assembleConfig(entities: Iterable<ConfigEntity>): unknown;
|
|
15
|
+
export declare function changedEntityKeys(before: Map<string, ConfigEntity>, after: Map<string, ConfigEntity>): string[];
|
|
16
|
+
/** References are both DB foreign keys and shared locks during dependent writes. */
|
|
17
|
+
export declare function entityReferences(entity: ConfigEntity, entities: Map<string, ConfigEntity>): string[];
|
|
18
|
+
/** Uniqueness claims must also serialize concurrent inserts with different IDs. */
|
|
19
|
+
export declare function entityClaims(entity: ConfigEntity): string[];
|
|
20
|
+
//# sourceMappingURL=entities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../../../src/plugins/service-betterportal-config-manager/storage/entities.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC5B;AACD,MAAM,WAAW,eAAgB,SAAQ,YAAY;IAAG,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE;AACnF,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,IAAI,MAAM,KAAG,MAAoC,CAAC;AAC1F,eAAO,MAAM,KAAK,GAAI,QAAQ,YAAY,KAAG,MAA2C,CAAC;AAEzF,wBAAgB,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAiCjF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,OAAO,CA4BxE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,MAAM,EAAE,CAI/G;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,MAAM,EAAE,CAkCpG;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,EAAE,CAO3D"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
export const entityKey = (kind, id) => JSON.stringify([kind, id]);
|
|
3
|
+
export const keyOf = (entity) => entityKey(entity.kind, entity.id);
|
|
4
|
+
export function splitConfig(config) {
|
|
5
|
+
const entities = new Map();
|
|
6
|
+
const add = (kind, id, value) => {
|
|
7
|
+
const entity = { kind, id, value: structuredClone(value) };
|
|
8
|
+
if (entities.has(keyOf(entity)))
|
|
9
|
+
throw new Error(`Duplicate config entity ${kind}/${id}`);
|
|
10
|
+
entities.set(keyOf(entity), entity);
|
|
11
|
+
};
|
|
12
|
+
add("settings", "platform", config.configManagement);
|
|
13
|
+
for (const kind of ["tenants", "apps", "platformServices", "sharedServiceCatalog", "sharedServiceActivations", "previewEnvironmentGroups", "previewEnvironmentDeployments"]) {
|
|
14
|
+
for (const value of config[kind]) {
|
|
15
|
+
// Presence is stored separately; polling must never dirty a tenant record.
|
|
16
|
+
const entry = structuredClone(value);
|
|
17
|
+
if (kind === "tenants") {
|
|
18
|
+
const tenant = entry;
|
|
19
|
+
for (const service of tenant.services) {
|
|
20
|
+
delete service.lastSeenAt;
|
|
21
|
+
delete service.lastSyncAt;
|
|
22
|
+
add("tenantServices", service.id, { ...service, tenantId: tenant.id });
|
|
23
|
+
}
|
|
24
|
+
tenant.services = [];
|
|
25
|
+
}
|
|
26
|
+
if (kind === "previewEnvironmentDeployments") {
|
|
27
|
+
const deployment = entry;
|
|
28
|
+
if (deployment.effectiveConfig)
|
|
29
|
+
add("previewConfigs", deployment.id, { id: deployment.id, ...deployment.effectiveConfig });
|
|
30
|
+
delete deployment.effectiveConfig;
|
|
31
|
+
}
|
|
32
|
+
add(kind, value.id, entry);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const value of config.manifestCache)
|
|
36
|
+
add("manifestCache", value.serviceId, value);
|
|
37
|
+
for (const kind of ["bindings", "grants"])
|
|
38
|
+
for (const value of config.m2m[kind])
|
|
39
|
+
add(kind, value.id, value);
|
|
40
|
+
for (const value of config.webhooks.targets)
|
|
41
|
+
add("webhookTargets", value.id, value);
|
|
42
|
+
return entities;
|
|
43
|
+
}
|
|
44
|
+
export function assembleConfig(entities) {
|
|
45
|
+
const result = {
|
|
46
|
+
tenants: [], apps: [], platformServices: [], sharedServiceCatalog: [], sharedServiceActivations: [],
|
|
47
|
+
manifestCache: [], previewEnvironmentGroups: [], previewEnvironmentDeployments: [],
|
|
48
|
+
m2m: { bindings: [], grants: [] }, webhooks: { targets: [] }
|
|
49
|
+
};
|
|
50
|
+
const services = [];
|
|
51
|
+
const previews = [];
|
|
52
|
+
for (const { kind, value } of entities) {
|
|
53
|
+
if (kind === "previewConfigs") {
|
|
54
|
+
previews.push(value);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (kind === "tenantServices") {
|
|
58
|
+
services.push(value);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (kind === "settings")
|
|
62
|
+
result.configManagement = structuredClone(value);
|
|
63
|
+
else if (kind === "bindings" || kind === "grants")
|
|
64
|
+
result.m2m[kind].push(structuredClone(value));
|
|
65
|
+
else if (kind === "webhookTargets")
|
|
66
|
+
result.webhooks.targets.push(structuredClone(value));
|
|
67
|
+
else if (Array.isArray(result[kind]))
|
|
68
|
+
result[kind].push(structuredClone(value));
|
|
69
|
+
else
|
|
70
|
+
throw new Error(`Unknown config entity kind: ${kind}`);
|
|
71
|
+
}
|
|
72
|
+
for (const { tenantId, ...service } of services) {
|
|
73
|
+
const tenant = result.tenants.find((candidate) => candidate.id === tenantId);
|
|
74
|
+
if (!tenant)
|
|
75
|
+
throw new Error(`Service ${service.id} references missing tenant ${tenantId}`);
|
|
76
|
+
tenant.services.push(structuredClone(service));
|
|
77
|
+
}
|
|
78
|
+
for (const { id, ...effectiveConfig } of previews) {
|
|
79
|
+
const deployment = result.previewEnvironmentDeployments.find((candidate) => candidate.id === id);
|
|
80
|
+
if (!deployment)
|
|
81
|
+
throw new Error(`Preview config ${id} references missing deployment`);
|
|
82
|
+
deployment.effectiveConfig = structuredClone(effectiveConfig);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
export function changedEntityKeys(before, after) {
|
|
87
|
+
return [...new Set([...before.keys(), ...after.keys()])].filter(key => !isDeepStrictEqual(before.get(key)?.value, after.get(key)?.value)).sort();
|
|
88
|
+
}
|
|
89
|
+
/** References are both DB foreign keys and shared locks during dependent writes. */
|
|
90
|
+
export function entityReferences(entity, entities) {
|
|
91
|
+
const refs = new Set();
|
|
92
|
+
const add = (kind, id) => { if (typeof id === "string" && id)
|
|
93
|
+
refs.add(entityKey(kind, id)); };
|
|
94
|
+
const service = (id) => {
|
|
95
|
+
for (const kind of ["tenantServices", "platformServices", "sharedServiceCatalog", "sharedServiceActivations"]) {
|
|
96
|
+
if (entities.has(entityKey(kind, id))) {
|
|
97
|
+
add(kind, id);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const { kind, value } = entity;
|
|
103
|
+
if (kind === "previewConfigs")
|
|
104
|
+
add("previewEnvironmentDeployments", entity.id);
|
|
105
|
+
if (kind === "settings") {
|
|
106
|
+
add("tenants", value.adminTenantId);
|
|
107
|
+
add("apps", value.managementAppId);
|
|
108
|
+
}
|
|
109
|
+
if (kind === "tenants")
|
|
110
|
+
for (const id of value.activatedPlatformServices)
|
|
111
|
+
add("platformServices", id);
|
|
112
|
+
if (["tenantServices", "apps", "sharedServiceActivations", "bindings", "grants", "webhookTargets", "previewEnvironmentDeployments"].includes(kind)) {
|
|
113
|
+
add("tenants", value.tenantId);
|
|
114
|
+
add("apps", value.appId);
|
|
115
|
+
}
|
|
116
|
+
if (kind === "sharedServiceActivations")
|
|
117
|
+
add("sharedServiceCatalog", value.sharedServiceId);
|
|
118
|
+
if (kind === "webhookTargets" || kind === "manifestCache")
|
|
119
|
+
service(value.serviceId);
|
|
120
|
+
if (kind === "bindings") {
|
|
121
|
+
service(value.sourceServiceId);
|
|
122
|
+
service(value.targetServiceId);
|
|
123
|
+
}
|
|
124
|
+
if (kind === "grants")
|
|
125
|
+
add("bindings", value.bindingId);
|
|
126
|
+
if (kind === "previewEnvironmentGroups") {
|
|
127
|
+
add("tenants", value.sourceTenantId);
|
|
128
|
+
add("apps", value.sourceAppId);
|
|
129
|
+
}
|
|
130
|
+
if (kind === "previewEnvironmentDeployments")
|
|
131
|
+
add("previewEnvironmentGroups", value.groupId);
|
|
132
|
+
if (kind === "apps") {
|
|
133
|
+
// Walk nested route, auth, shell, fragment and permission service references.
|
|
134
|
+
const walk = (input) => {
|
|
135
|
+
if (!input || typeof input !== "object")
|
|
136
|
+
return;
|
|
137
|
+
for (const [name, child] of Object.entries(input)) {
|
|
138
|
+
if (name === "serviceId" && typeof child === "string")
|
|
139
|
+
service(child);
|
|
140
|
+
else
|
|
141
|
+
walk(child);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
walk(value);
|
|
145
|
+
}
|
|
146
|
+
refs.delete(keyOf(entity));
|
|
147
|
+
return [...refs].sort();
|
|
148
|
+
}
|
|
149
|
+
/** Uniqueness claims must also serialize concurrent inserts with different IDs. */
|
|
150
|
+
export function entityClaims(entity) {
|
|
151
|
+
const { kind, value } = entity;
|
|
152
|
+
if (kind === "apps")
|
|
153
|
+
return [...new Set(value.hostnames.map((hostname) => `hostname:${hostname.toLowerCase()}`))];
|
|
154
|
+
if (kind === "previewEnvironmentDeployments")
|
|
155
|
+
return [`preview:${value.groupId}:${value.key}`];
|
|
156
|
+
if (kind === "bindings" && value.enabled)
|
|
157
|
+
return [`binding:${JSON.stringify([value.tenantId, value.appId, value.sourceServiceId, value.requestId, value.mode])}`];
|
|
158
|
+
if (kind === "grants" && value.enabled)
|
|
159
|
+
return [`grant:${value.bindingId}`];
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.js","sourceRoot":"","sources":["../../../../src/plugins/service-betterportal-config-manager/storage/entities.ts"],"names":[],"mappings":"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"}
|
|
@@ -64,6 +64,9 @@ export declare class PostgresStorage extends BaseStorage {
|
|
|
64
64
|
private readonly deliveriesTable;
|
|
65
65
|
private readonly outboxTable;
|
|
66
66
|
private readonly activityTable;
|
|
67
|
+
private readonly entitiesTable;
|
|
68
|
+
private readonly referencesTable;
|
|
69
|
+
private readonly revisionSequence;
|
|
67
70
|
private activityLoad?;
|
|
68
71
|
private activityLoadedAt;
|
|
69
72
|
private pool;
|
|
@@ -81,10 +84,13 @@ export declare class PostgresStorage extends BaseStorage {
|
|
|
81
84
|
notify?: boolean;
|
|
82
85
|
completeAction?: PendingActionCompletion;
|
|
83
86
|
}): Promise<void>;
|
|
87
|
+
private writeReferences;
|
|
84
88
|
invalidate(): void;
|
|
85
89
|
validateApiKey(apiKey: string): ReturnType<BaseStorage["validateApiKey"]>;
|
|
90
|
+
private readSnapshot;
|
|
86
91
|
private readConfig;
|
|
87
92
|
private cloneSnapshot;
|
|
93
|
+
close(): Promise<void>;
|
|
88
94
|
touchServiceActivity(serviceId: string, field: "lastSeenAt" | "lastSyncAt"): Promise<void>;
|
|
89
95
|
private loadServiceActivity;
|
|
90
96
|
loadOrCreateIdentity<T extends object>(create: () => T): Promise<T>;
|
|
@@ -120,6 +126,7 @@ export declare class PostgresStorage extends BaseStorage {
|
|
|
120
126
|
private getPool;
|
|
121
127
|
private ensureSchema;
|
|
122
128
|
private migrate;
|
|
129
|
+
private migrateEntities;
|
|
123
130
|
private importLegacyFiles;
|
|
124
131
|
private insertWebhookDelivery;
|
|
125
132
|
private insertOutbox;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../../../src/plugins/service-betterportal-config-manager/storage/postgres.ts"],"names":[],"mappings":"AAKA,OAAO,EAA4B,KAAK,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC5G,OAAO,EACL,WAAW,EAMX,KAAK,sBAAsB,EAC5B,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../../../src/plugins/service-betterportal-config-manager/storage/postgres.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,13 +5,14 @@ import { Pool } from "pg";
|
|
|
5
5
|
import * as yaml from "yaml";
|
|
6
6
|
import { BetterPortalConfigSchema } from "@betterportal/framework";
|
|
7
7
|
import { BaseStorage, ConfigRevisionConflictError, hashApiKey, migrateOfficialPluginIds, migrateRouteOperations, migrateRouteParamSyntax } from "./core.js";
|
|
8
|
+
import { assembleConfig, changedEntityKeys, entityClaims, entityReferences, keyOf, splitConfig } from "./entities.js";
|
|
8
9
|
function quotePgIdent(identifier) {
|
|
9
10
|
if (!/^[a-z_][a-z0-9_]*$/i.test(identifier))
|
|
10
11
|
throw new Error(`Invalid PostgreSQL identifier: ${identifier}`);
|
|
11
12
|
return `"${identifier.replace(/"/g, "\"\"")}"`;
|
|
12
13
|
}
|
|
13
14
|
export { ConfigRevisionConflictError } from "./core.js";
|
|
14
|
-
const SCHEMA_VERSION =
|
|
15
|
+
const SCHEMA_VERSION = 2;
|
|
15
16
|
export class PostgresStorage extends BaseStorage {
|
|
16
17
|
backend = "postgres";
|
|
17
18
|
connectionString;
|
|
@@ -24,6 +25,9 @@ export class PostgresStorage extends BaseStorage {
|
|
|
24
25
|
deliveriesTable;
|
|
25
26
|
outboxTable;
|
|
26
27
|
activityTable;
|
|
28
|
+
entitiesTable;
|
|
29
|
+
referencesTable;
|
|
30
|
+
revisionSequence;
|
|
27
31
|
activityLoad;
|
|
28
32
|
activityLoadedAt = 0;
|
|
29
33
|
pool = null;
|
|
@@ -46,6 +50,9 @@ export class PostgresStorage extends BaseStorage {
|
|
|
46
50
|
this.deliveriesTable = quotePgIdent(`${this.tableName}_webhook_deliveries`);
|
|
47
51
|
this.outboxTable = quotePgIdent(`${this.tableName}_outbox`);
|
|
48
52
|
this.activityTable = quotePgIdent(`${this.tableName}_activity`);
|
|
53
|
+
this.entitiesTable = quotePgIdent(`${this.tableName}_entities`);
|
|
54
|
+
this.referencesTable = quotePgIdent(`${this.tableName}_references`);
|
|
55
|
+
this.revisionSequence = quotePgIdent(`${this.tableName}_entity_revision`);
|
|
49
56
|
}
|
|
50
57
|
async initialize(paths = {}) {
|
|
51
58
|
this.legacyPaths = paths;
|
|
@@ -63,7 +70,7 @@ export class PostgresStorage extends BaseStorage {
|
|
|
63
70
|
this.configLoad = null;
|
|
64
71
|
return this.loadConfig();
|
|
65
72
|
}
|
|
66
|
-
this.cachedConfig =
|
|
73
|
+
this.cachedConfig = loaded;
|
|
67
74
|
return this.cloneSnapshot(this.cachedConfig);
|
|
68
75
|
}
|
|
69
76
|
finally {
|
|
@@ -73,63 +80,125 @@ export class PostgresStorage extends BaseStorage {
|
|
|
73
80
|
}
|
|
74
81
|
async saveConfig(config, options) {
|
|
75
82
|
await this.ensureSchema();
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
const baseline = this.snapshots.get(config);
|
|
84
|
+
if (!baseline)
|
|
85
|
+
throw new Error("Config must be loaded from this store before saving; detached whole-platform replacements are not supported");
|
|
86
|
+
const proposed = splitConfig(this.parseConfig(config));
|
|
87
|
+
// Cached manifests belong to their concrete service registration. Deleting
|
|
88
|
+
// a registration also deletes its known cache, whichever API initiated it.
|
|
89
|
+
for (const [key, entity] of proposed)
|
|
90
|
+
if (entity.kind === "manifestCache") {
|
|
91
|
+
if (entityReferences(entity, baseline.entities).some(ref => !proposed.has(ref)))
|
|
92
|
+
proposed.delete(key);
|
|
93
|
+
}
|
|
94
|
+
const changed = changedEntityKeys(baseline.entities, proposed);
|
|
79
95
|
const client = await this.getPool().connect();
|
|
80
96
|
try {
|
|
81
97
|
await client.query("begin");
|
|
82
98
|
if (options?.completeAction)
|
|
83
99
|
await this.finishAction(client, options.completeAction);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
// Shared dependency locks allow two apps in a tenant to save concurrently.
|
|
101
|
+
// Exclusive locks cover only changed entities and their uniqueness claims.
|
|
102
|
+
const locks = new Map();
|
|
103
|
+
const dependencies = new Set();
|
|
104
|
+
for (const key of changed) {
|
|
105
|
+
locks.set(key, true);
|
|
106
|
+
for (const entities of [baseline.entities, proposed]) {
|
|
107
|
+
const entity = entities.get(key);
|
|
108
|
+
if (!entity)
|
|
109
|
+
continue;
|
|
110
|
+
for (const claim of entityClaims(entity))
|
|
111
|
+
locks.set(claim, true);
|
|
112
|
+
for (const ref of entityReferences(entity, entities)) {
|
|
113
|
+
dependencies.add(ref);
|
|
114
|
+
if (!locks.has(ref))
|
|
115
|
+
locks.set(ref, false);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
91
118
|
}
|
|
92
|
-
|
|
93
|
-
|
|
119
|
+
for (const [key, exclusive] of [...locks].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
120
|
+
await client.query(`select ${exclusive ? "pg_advisory_xact_lock" : "pg_advisory_xact_lock_shared"}(hashtextextended($1, 0))`, [JSON.stringify([this.tableName, this.rowId, key])]);
|
|
121
|
+
}
|
|
122
|
+
const current = await this.readSnapshot(client);
|
|
123
|
+
for (const key of new Set([...changed, ...dependencies])) {
|
|
124
|
+
const expected = baseline.revisions.get(key) ?? 0;
|
|
125
|
+
const actual = current.revisions.get(key) ?? 0;
|
|
126
|
+
if (expected !== actual) {
|
|
127
|
+
this.invalidate();
|
|
128
|
+
throw new ConfigRevisionConflictError(expected, actual, key);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const merged = new Map(current.entities);
|
|
132
|
+
for (const key of changed) {
|
|
133
|
+
const entity = proposed.get(key);
|
|
134
|
+
if (entity)
|
|
135
|
+
merged.set(key, entity);
|
|
136
|
+
else
|
|
137
|
+
merged.delete(key);
|
|
138
|
+
}
|
|
139
|
+
// A sync may have created a cache after the deletion snapshot was loaded.
|
|
140
|
+
// Reject that stale deletion before reaching the FK; a fresh retry includes
|
|
141
|
+
// the cache in its deletion set and locks both records in canonical order.
|
|
142
|
+
for (const entity of merged.values())
|
|
143
|
+
if (entity.kind === "manifestCache") {
|
|
144
|
+
const removed = entityReferences(entity, current.entities).find(ref => !merged.has(ref));
|
|
145
|
+
if (removed) {
|
|
146
|
+
this.invalidate();
|
|
147
|
+
throw new ConfigRevisionConflictError(baseline.revisions.get(keyOf(entity)) ?? 0, current.revisions.get(keyOf(entity)) ?? 0, keyOf(entity));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const validated = this.parseConfig(assembleConfig(merged.values()));
|
|
151
|
+
this.validateConfigReferences(validated);
|
|
152
|
+
// Validate uniqueness against the current committed state, under claim locks.
|
|
153
|
+
const claims = new Set();
|
|
154
|
+
for (const entity of merged.values())
|
|
155
|
+
for (const claim of entityClaims(entity)) {
|
|
156
|
+
if (claims.has(claim))
|
|
157
|
+
throw new Error(`Configuration uniqueness conflict: ${claim}`);
|
|
158
|
+
claims.add(claim);
|
|
159
|
+
}
|
|
94
160
|
const resetActivity = [];
|
|
95
|
-
|
|
161
|
+
const services = new Map(validated.tenants.flatMap(tenant => tenant.services.map(service => [service.id, service])));
|
|
162
|
+
for (const previous of current.config.tenants.flatMap(tenant => tenant.services)) {
|
|
96
163
|
const next = services.get(previous.id);
|
|
97
|
-
if (!next || previous.apiKeyHash !== next.apiKeyHash || previous.hostname !== next.hostname
|
|
98
|
-
|| (previous.lastSeenAt && !next.lastSeenAt) || (previous.lastSyncAt && !next.lastSyncAt)) {
|
|
164
|
+
if (!next || previous.apiKeyHash !== next.apiKeyHash || previous.hostname !== next.hostname)
|
|
99
165
|
resetActivity.push(previous.id);
|
|
100
|
-
if (next) {
|
|
101
|
-
delete next.lastSeenAt;
|
|
102
|
-
delete next.lastSyncAt;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
166
|
}
|
|
106
167
|
if (resetActivity.length)
|
|
107
168
|
await client.query(`delete from ${this.activityTable} where scope_id = $1 and service_id = any($2::text[])`, [this.rowId, resetActivity]);
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
if (refreshedManifests.length) {
|
|
111
|
-
// Readiness compares manifest acceptance with delivery; both must use the DB clock.
|
|
169
|
+
const refreshed = changed.map(key => merged.get(key)).filter(entity => entity?.kind === "manifestCache");
|
|
170
|
+
if (refreshed.length) {
|
|
112
171
|
const clock = await client.query("select clock_timestamp() as now");
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
entry.fetchedAt = fetchedAt;
|
|
172
|
+
for (const entity of refreshed)
|
|
173
|
+
entity.value.fetchedAt = clock.rows[0].now.toISOString();
|
|
116
174
|
}
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
175
|
+
for (const key of changed) {
|
|
176
|
+
const entity = merged.get(key) ?? baseline.entities.get(key);
|
|
177
|
+
await client.query(`delete from ${this.referencesTable} where scope_id = $1 and kind = $2 and entity_id = $3`, [this.rowId, entity.kind, entity.id]);
|
|
178
|
+
if (merged.has(key)) {
|
|
179
|
+
await client.query(`insert into ${this.entitiesTable} (scope_id, kind, entity_id, value, revision)
|
|
180
|
+
values ($1, $2, $3, $4::jsonb, nextval('${this.revisionSequence}'))
|
|
181
|
+
on conflict (scope_id, kind, entity_id) do update set value = excluded.value, revision = excluded.revision`, [this.rowId, entity.kind, entity.id, JSON.stringify(entity.value)]);
|
|
182
|
+
}
|
|
183
|
+
else
|
|
184
|
+
await client.query(`delete from ${this.entitiesTable} where scope_id = $1 and kind = $2 and entity_id = $3`, [this.rowId, entity.kind, entity.id]);
|
|
185
|
+
}
|
|
186
|
+
for (const key of changed) {
|
|
187
|
+
const entity = merged.get(key);
|
|
188
|
+
if (entity)
|
|
189
|
+
await this.writeReferences(client, entity, merged);
|
|
126
190
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
191
|
+
if (changed.length) {
|
|
192
|
+
const result = await client.query(`select nextval('${this.revisionSequence}') as revision`);
|
|
193
|
+
await this.insertOutbox(client, "broadcast", "platform-config.changed", { revision: Number(result.rows[0].revision) });
|
|
194
|
+
}
|
|
195
|
+
const committed = await this.readSnapshot(client);
|
|
196
|
+
await client.query("commit");
|
|
197
|
+
// Update the caller's baseline as well as its value: subsequent saves must
|
|
198
|
+
// not mistake concurrent edits included in this commit for user deletions.
|
|
199
|
+
Object.assign(config, structuredClone(committed.config));
|
|
200
|
+
this.snapshots.set(config, committed);
|
|
201
|
+
this.invalidate();
|
|
133
202
|
}
|
|
134
203
|
catch (error) {
|
|
135
204
|
await client.query("rollback").catch(() => undefined);
|
|
@@ -139,6 +208,13 @@ export class PostgresStorage extends BaseStorage {
|
|
|
139
208
|
client.release();
|
|
140
209
|
}
|
|
141
210
|
}
|
|
211
|
+
async writeReferences(client, entity, entities) {
|
|
212
|
+
for (const ref of entityReferences(entity, entities)) {
|
|
213
|
+
const [kind, id] = JSON.parse(ref);
|
|
214
|
+
await client.query(`insert into ${this.referencesTable} (scope_id, kind, entity_id, target_kind, target_id)
|
|
215
|
+
values ($1, $2, $3, $4, $5)`, [this.rowId, entity.kind, entity.id, kind, id]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
142
218
|
invalidate() {
|
|
143
219
|
this.configGeneration++;
|
|
144
220
|
this.cachedConfig = null;
|
|
@@ -167,19 +243,25 @@ export class PostgresStorage extends BaseStorage {
|
|
|
167
243
|
}
|
|
168
244
|
return structuredClone(this.credentialIndex.entries.get(hashApiKey(apiKey)) ?? null);
|
|
169
245
|
}
|
|
246
|
+
async readSnapshot(connection) {
|
|
247
|
+
const result = await connection.query(`select kind, entity_id, value, revision from ${this.entitiesTable} where scope_id = $1 order by ordinal`, [this.rowId]);
|
|
248
|
+
if (!result.rows.some(row => row.kind === "settings"))
|
|
249
|
+
throw new Error(`Config scope ${this.rowId} was not initialized`);
|
|
250
|
+
const rows = result.rows.map(row => ({ ...row, id: row.entity_id }));
|
|
251
|
+
const previewConfigs = new Set(rows.filter(row => row.kind === "previewConfigs").map(row => row.id));
|
|
252
|
+
for (const row of rows)
|
|
253
|
+
if (row.kind === "previewEnvironmentDeployments" && !previewConfigs.has(row.id)) {
|
|
254
|
+
throw new Error(`Persisted preview ${row.id} is missing its effective configuration`);
|
|
255
|
+
}
|
|
256
|
+
const config = this.parseConfig(assembleConfig(rows));
|
|
257
|
+
return { config, entities: splitConfig(config), revisions: new Map(rows.map(row => [keyOf(row), Number(row.revision)])) };
|
|
258
|
+
}
|
|
170
259
|
async readConfig(generation) {
|
|
171
|
-
|
|
172
|
-
if (!result.rows[0])
|
|
173
|
-
throw new Error(`Platform config row ${this.rowId} was not initialized`);
|
|
174
|
-
return {
|
|
175
|
-
config: this.parseConfig(result.rows[0].config),
|
|
176
|
-
revision: Number(result.rows[0].revision),
|
|
177
|
-
generation
|
|
178
|
-
};
|
|
260
|
+
return { ...await this.readSnapshot(this.getPool()), generation };
|
|
179
261
|
}
|
|
180
262
|
async cloneSnapshot(snapshot) {
|
|
181
263
|
const config = structuredClone(snapshot.config);
|
|
182
|
-
this.snapshots.set(config, snapshot
|
|
264
|
+
this.snapshots.set(config, snapshot);
|
|
183
265
|
const activity = await this.loadServiceActivity();
|
|
184
266
|
for (const tenant of config.tenants)
|
|
185
267
|
for (const service of tenant.services) {
|
|
@@ -187,6 +269,7 @@ export class PostgresStorage extends BaseStorage {
|
|
|
187
269
|
}
|
|
188
270
|
return config;
|
|
189
271
|
}
|
|
272
|
+
async close() { await this.pool?.end(); }
|
|
190
273
|
async touchServiceActivity(serviceId, field) {
|
|
191
274
|
await this.ensureSchema();
|
|
192
275
|
const column = field === "lastSeenAt" ? "last_seen_at" : "last_sync_at";
|
|
@@ -430,7 +513,7 @@ export class PostgresStorage extends BaseStorage {
|
|
|
430
513
|
}
|
|
431
514
|
async migrate() {
|
|
432
515
|
const client = await this.getPool().connect();
|
|
433
|
-
const lockName = `${this.tableName}
|
|
516
|
+
const lockName = `${this.tableName}:migrations`;
|
|
434
517
|
try {
|
|
435
518
|
await client.query("select pg_advisory_lock(hashtext($1))", [lockName]);
|
|
436
519
|
await client.query("begin");
|
|
@@ -464,11 +547,12 @@ export class PostgresStorage extends BaseStorage {
|
|
|
464
547
|
payload jsonb not null, attempts integer not null default 0, available_at timestamptz not null default now(),
|
|
465
548
|
lease_owner text, lease_until timestamptz, created_at timestamptz not null default now(),
|
|
466
549
|
primary key (scope_id, id))`);
|
|
467
|
-
const applied = await client.query(`select 1 from ${this.migrationsTable} where scope_id = $1 and version = $2`, [this.rowId,
|
|
550
|
+
const applied = await client.query(`select 1 from ${this.migrationsTable} where scope_id = $1 and version = $2`, [this.rowId, 1]);
|
|
468
551
|
if (applied.rows.length === 0) {
|
|
469
552
|
await this.importLegacyFiles(client);
|
|
470
|
-
await client.query(`insert into ${this.migrationsTable} (scope_id, version) values ($1, $2)`, [this.rowId,
|
|
553
|
+
await client.query(`insert into ${this.migrationsTable} (scope_id, version) values ($1, $2)`, [this.rowId, 1]);
|
|
471
554
|
}
|
|
555
|
+
await this.migrateEntities(client);
|
|
472
556
|
await client.query("commit");
|
|
473
557
|
}
|
|
474
558
|
catch (error) {
|
|
@@ -480,6 +564,67 @@ export class PostgresStorage extends BaseStorage {
|
|
|
480
564
|
client.release();
|
|
481
565
|
}
|
|
482
566
|
}
|
|
567
|
+
async migrateEntities(client) {
|
|
568
|
+
await client.query(`create sequence if not exists ${this.revisionSequence}`);
|
|
569
|
+
await client.query(`create table if not exists ${this.entitiesTable} (
|
|
570
|
+
scope_id text not null, kind text not null, entity_id text not null, value jsonb not null,
|
|
571
|
+
revision bigint not null, ordinal bigint generated always as identity,
|
|
572
|
+
primary key (scope_id, kind, entity_id), check (jsonb_typeof(value) = 'object'))`);
|
|
573
|
+
await client.query(`create table if not exists ${this.referencesTable} (
|
|
574
|
+
scope_id text not null, kind text not null, entity_id text not null, target_kind text not null, target_id text not null,
|
|
575
|
+
primary key (scope_id, kind, entity_id, target_kind, target_id),
|
|
576
|
+
foreign key (scope_id, kind, entity_id) references ${this.entitiesTable} (scope_id, kind, entity_id) deferrable initially deferred,
|
|
577
|
+
foreign key (scope_id, target_kind, target_id) references ${this.entitiesTable} (scope_id, kind, entity_id) deferrable initially deferred)`);
|
|
578
|
+
await client.query(`create index if not exists ${quotePgIdent(`${this.tableName}_ref_target_idx`)}
|
|
579
|
+
on ${this.referencesTable} (scope_id, target_kind, target_id)`);
|
|
580
|
+
const applied = await client.query(`select 1 from ${this.migrationsTable} where scope_id = $1 and version = $2`, [this.rowId, SCHEMA_VERSION]);
|
|
581
|
+
if (applied.rows.length)
|
|
582
|
+
return;
|
|
583
|
+
// Row lock waits for in-flight legacy saves. The trigger below fences later
|
|
584
|
+
// old-binary writes; the frozen row is retained as a migration backup.
|
|
585
|
+
const source = await client.query(`select config from ${this.quotedTableName} where id = $1 for update`, [this.rowId]);
|
|
586
|
+
if (!source.rows[0])
|
|
587
|
+
throw new Error("Legacy configuration is missing");
|
|
588
|
+
const config = this.parseConfig(source.rows[0].config);
|
|
589
|
+
this.validateConfigReferences(config);
|
|
590
|
+
const entities = splitConfig(config);
|
|
591
|
+
const claims = new Set();
|
|
592
|
+
for (const entity of entities.values())
|
|
593
|
+
for (const claim of entityClaims(entity)) {
|
|
594
|
+
if (claims.has(claim))
|
|
595
|
+
throw new Error(`Cannot migrate duplicate configuration claim: ${claim}`);
|
|
596
|
+
claims.add(claim);
|
|
597
|
+
}
|
|
598
|
+
for (const tenant of config.tenants)
|
|
599
|
+
for (const service of tenant.services) {
|
|
600
|
+
if (!service.lastSeenAt && !service.lastSyncAt)
|
|
601
|
+
continue;
|
|
602
|
+
await client.query(`insert into ${this.activityTable} (scope_id, service_id, last_seen_at, last_sync_at)
|
|
603
|
+
values ($1, $2, $3, $4) on conflict (scope_id, service_id) do update set
|
|
604
|
+
last_seen_at = greatest(${this.activityTable}.last_seen_at, excluded.last_seen_at),
|
|
605
|
+
last_sync_at = greatest(${this.activityTable}.last_sync_at, excluded.last_sync_at)`, [this.rowId, service.id, service.lastSeenAt ?? null, service.lastSyncAt ?? null]);
|
|
606
|
+
}
|
|
607
|
+
for (const entity of entities.values())
|
|
608
|
+
await client.query(`insert into ${this.entitiesTable} (scope_id, kind, entity_id, value, revision) values ($1, $2, $3, $4::jsonb, nextval('${this.revisionSequence}'))`, [this.rowId, entity.kind, entity.id, JSON.stringify(entity.value)]);
|
|
609
|
+
for (const entity of entities.values())
|
|
610
|
+
await this.writeReferences(client, entity, entities);
|
|
611
|
+
const restored = await this.readSnapshot(client);
|
|
612
|
+
if (changedEntityKeys(entities, restored.entities).length)
|
|
613
|
+
throw new Error("Config entity migration verification failed");
|
|
614
|
+
const fence = quotePgIdent(`${this.tableName}_legacy_write_fence`);
|
|
615
|
+
await client.query(`create or replace function ${fence}() returns trigger language plpgsql as $body$
|
|
616
|
+
begin
|
|
617
|
+
if exists (select 1 from ${this.migrationsTable} where scope_id = OLD.id and version = 2) then
|
|
618
|
+
raise exception 'Config storage migrated to independent entities; upgrade all config-manager replicas';
|
|
619
|
+
end if;
|
|
620
|
+
if TG_OP = 'DELETE' then return OLD; end if;
|
|
621
|
+
return NEW;
|
|
622
|
+
end $body$`);
|
|
623
|
+
await client.query(`drop trigger if exists legacy_write_fence on ${this.quotedTableName}`);
|
|
624
|
+
await client.query(`create trigger legacy_write_fence before update or delete on ${this.quotedTableName}
|
|
625
|
+
for each row execute function ${fence}()`);
|
|
626
|
+
await client.query(`insert into ${this.migrationsTable} (scope_id, version) values ($1, $2)`, [this.rowId, SCHEMA_VERSION]);
|
|
627
|
+
}
|
|
483
628
|
async importLegacyFiles(client) {
|
|
484
629
|
const existingConfig = await client.query(`select 1 from ${this.quotedTableName} where id = $1`, [this.rowId]);
|
|
485
630
|
if (existingConfig.rows.length === 0) {
|