@wtfalch/foundry 0.16.7 → 0.16.8
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/bootstrap/bootstrap.d.ts +2 -0
- package/dist/bootstrap/bootstrap.js +9 -1
- package/dist/bootstrap/declare.d.ts +1 -1
- package/dist/bootstrap/declare.js +7 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/migrations/0036_applied_versions.sql +45 -0
- package/dist/migrations/0037_platform_server_ipv4.sql +8 -0
- package/dist/org.d.ts +39 -0
- package/dist/org.js +46 -1
- package/dist/plan/boule.d.ts +8 -2
- package/dist/plan/boule.js +10 -1
- package/dist/plan/diff.d.ts +2 -0
- package/dist/plan/diff.js +40 -5
- package/dist/plan/executor.d.ts +30 -6
- package/dist/plan/executor.js +86 -21
- package/dist/plan/plan-apply.d.ts +21 -1
- package/dist/plan/plan-apply.js +38 -3
- package/dist/plan/platform-diff.d.ts +5 -1
- package/dist/plan/platform-diff.js +17 -5
- package/dist/plan/platform-providers.d.ts +51 -1
- package/dist/plan/platform-providers.js +327 -52
- package/dist/platform.d.ts +11 -0
- package/dist/platform.js +5 -0
- package/dist/store/postgres.d.ts +9 -1
- package/dist/store/postgres.js +36 -2
- package/dist/store/schema.d.ts +134 -0
- package/dist/store/schema.js +15 -0
- package/dist/store.d.ts +37 -0
- package/package.json +1 -1
package/dist/platform.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isIPv4 } from 'node:net';
|
|
1
2
|
import { validateDomain } from './org.js';
|
|
2
3
|
export const PLATFORM_SERVICE_KEYS = [
|
|
3
4
|
'auth',
|
|
@@ -21,6 +22,7 @@ function serviceOf(platform, key) {
|
|
|
21
22
|
* shape as `org.ts`'s `validateBuyerIdentity`.
|
|
22
23
|
*
|
|
23
24
|
* - `apex` is a valid domain (`org.ts`'s `validateDomain`).
|
|
25
|
+
* - `serverIpv4`, when declared, is a dotted-quad IPv4 address.
|
|
24
26
|
* - Every service's `hostname` is the apex itself or a subdomain of it -- a
|
|
25
27
|
* platform service does not live outside the platform's own apex.
|
|
26
28
|
* - Every service's `gitRef` is non-empty once that service is `enabled` --
|
|
@@ -30,6 +32,9 @@ export function validatePlatform(platform) {
|
|
|
30
32
|
const apexReason = validateDomain(platform.apex);
|
|
31
33
|
if (apexReason)
|
|
32
34
|
return `apex invalid: ${apexReason}`;
|
|
35
|
+
if (platform.serverIpv4 != null && !isIPv4(platform.serverIpv4)) {
|
|
36
|
+
return `serverIpv4 '${platform.serverIpv4}' must be a dotted-quad IPv4 address`;
|
|
37
|
+
}
|
|
33
38
|
for (const key of PLATFORM_SERVICE_KEYS) {
|
|
34
39
|
const service = serviceOf(platform, key);
|
|
35
40
|
if (!service.hostname.trim()) {
|
package/dist/store/postgres.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
|
|
2
|
-
import type { DeclaredOrgStore, PlatformStore, RequestedChangeStore } from '../store.js';
|
|
2
|
+
import type { AppliedVersionsStore, DeclaredOrgStore, PlatformStore, RequestedChangeStore } from '../store.js';
|
|
3
3
|
/**
|
|
4
4
|
* The host's own drizzle handle or a transaction open on it, same shape
|
|
5
5
|
* `@wtfalch/keys`'s `HeldKeysDb` uses: postgres-js in a real host, PGlite in
|
|
@@ -90,3 +90,11 @@ export declare function createPostgresPlatformStore(db: FoundryStoreDb): Platfor
|
|
|
90
90
|
* argument makes -- and stores the result as the request's initial `state`.
|
|
91
91
|
*/
|
|
92
92
|
export declare function createPostgresRequestedChangeStore(db: FoundryStoreDb): RequestedChangeStore;
|
|
93
|
+
/**
|
|
94
|
+
* `AppliedVersionsStore` (`../store.ts`) on Postgres, for #253: an appended
|
|
95
|
+
* history of recorded applies, beside `createPostgresDeclaredOrgStore`'s own
|
|
96
|
+
* single-snapshot `putAppliedState` above. `record` always inserts a new
|
|
97
|
+
* row -- never an upsert -- so the row it replaces by being newer is never
|
|
98
|
+
* lost the way `foundry_applied_state`'s one row per service would lose it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function createPostgresAppliedVersionsStore(db: FoundryStoreDb): AppliedVersionsStore;
|
package/dist/store/postgres.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { and, asc, eq, sql } from 'drizzle-orm';
|
|
1
|
+
import { and, asc, desc, eq, sql } from 'drizzle-orm';
|
|
2
2
|
import { APP_ENTRY_KEY, DEFAULT_MAIL_APPS, DEFAULT_NO_REPLY_LOCAL_PART, validateBuyerIdentity, validateDomain, validateHosting, validateMail, validateOwners, validateSite, } from '../org.js';
|
|
3
3
|
import { lockedHostnames } from '../plan/locked-hostnames.js';
|
|
4
4
|
import { validatePlatform } from '../platform.js';
|
|
5
5
|
import { RequestedChangeError, classifyChange } from '../requested-changes.js';
|
|
6
6
|
import { validateSlug } from '../slug.js';
|
|
7
|
-
import { declaredOrgs, foundryAppliedState, foundryDeclaredPlatform, foundryRequestedChanges, } from './schema.js';
|
|
7
|
+
import { declaredOrgs, foundryAppliedState, foundryAppliedVersions, foundryDeclaredPlatform, foundryRequestedChanges, } from './schema.js';
|
|
8
8
|
const UNIQUE_VIOLATION = '23505';
|
|
9
9
|
/** `migrations/0004_slug_unique.sql` / `migrations/0015_domain_unique.sql`'s index names, read back off a caught unique-violation to tell which one fired. */
|
|
10
10
|
const SLUG_UNIQUE_INDEX = 'foundry_declared_orgs_slug_idx';
|
|
@@ -466,6 +466,7 @@ function rowToPlatform(row) {
|
|
|
466
466
|
return {
|
|
467
467
|
ownerOrgId: row.ownerOrgId,
|
|
468
468
|
apex: row.apex,
|
|
469
|
+
...(row.serverIpv4 ? { serverIpv4: row.serverIpv4 } : {}),
|
|
469
470
|
auth: { enabled: row.authEnabled, hostname: row.authHostname, gitRef: row.authGitRef },
|
|
470
471
|
mail: { enabled: row.mailEnabled, hostname: row.mailHostname, gitRef: row.mailGitRef },
|
|
471
472
|
sfu: { enabled: row.sfuEnabled, hostname: row.sfuHostname, gitRef: row.sfuGitRef },
|
|
@@ -489,6 +490,7 @@ function platformToRow(platform) {
|
|
|
489
490
|
id: PLATFORM_ROW_ID,
|
|
490
491
|
ownerOrgId: platform.ownerOrgId,
|
|
491
492
|
apex: platform.apex,
|
|
493
|
+
serverIpv4: platform.serverIpv4 ?? null,
|
|
492
494
|
authEnabled: platform.auth.enabled,
|
|
493
495
|
authHostname: platform.auth.hostname,
|
|
494
496
|
authGitRef: platform.auth.gitRef,
|
|
@@ -617,3 +619,35 @@ export function createPostgresRequestedChangeStore(db) {
|
|
|
617
619
|
},
|
|
618
620
|
};
|
|
619
621
|
}
|
|
622
|
+
/**
|
|
623
|
+
* `AppliedVersionsStore` (`../store.ts`) on Postgres, for #253: an appended
|
|
624
|
+
* history of recorded applies, beside `createPostgresDeclaredOrgStore`'s own
|
|
625
|
+
* single-snapshot `putAppliedState` above. `record` always inserts a new
|
|
626
|
+
* row -- never an upsert -- so the row it replaces by being newer is never
|
|
627
|
+
* lost the way `foundry_applied_state`'s one row per service would lose it.
|
|
628
|
+
*/
|
|
629
|
+
export function createPostgresAppliedVersionsStore(db) {
|
|
630
|
+
return {
|
|
631
|
+
async record({ service, ref, appliedAt, runId, backupId }) {
|
|
632
|
+
await db.insert(foundryAppliedVersions).values({
|
|
633
|
+
service,
|
|
634
|
+
ref,
|
|
635
|
+
appliedAt: new Date(appliedAt),
|
|
636
|
+
runId: runId ?? null,
|
|
637
|
+
backupId: backupId ?? null,
|
|
638
|
+
});
|
|
639
|
+
},
|
|
640
|
+
async previousAppliedRef(service) {
|
|
641
|
+
// The two most recent rows for this service, newest first: index 0 is
|
|
642
|
+
// the current one, index 1 -- if it exists -- is what a rollback would
|
|
643
|
+
// go back to.
|
|
644
|
+
const rows = await db
|
|
645
|
+
.select({ ref: foundryAppliedVersions.ref })
|
|
646
|
+
.from(foundryAppliedVersions)
|
|
647
|
+
.where(eq(foundryAppliedVersions.service, service))
|
|
648
|
+
.orderBy(desc(foundryAppliedVersions.appliedAt), desc(foundryAppliedVersions.id))
|
|
649
|
+
.limit(2);
|
|
650
|
+
return rows[1]?.ref ?? null;
|
|
651
|
+
},
|
|
652
|
+
};
|
|
653
|
+
}
|
package/dist/store/schema.d.ts
CHANGED
|
@@ -948,6 +948,121 @@ export declare const foundryAppliedState: import("drizzle-orm/pg-core").PgTableW
|
|
|
948
948
|
};
|
|
949
949
|
dialect: "pg";
|
|
950
950
|
}>;
|
|
951
|
+
/**
|
|
952
|
+
* Mirrors `migrations/0036_applied_versions.sql`, the source of truth: an
|
|
953
|
+
* appended history row beside `foundryAppliedState`'s single current
|
|
954
|
+
* snapshot (#253). See that migration for what `service`, `ref`, `runId`
|
|
955
|
+
* and `backupId` mean and why the last two are nullable.
|
|
956
|
+
*/
|
|
957
|
+
export declare const foundryAppliedVersions: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
958
|
+
name: "foundry_applied_versions";
|
|
959
|
+
schema: undefined;
|
|
960
|
+
columns: {
|
|
961
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
962
|
+
name: "id";
|
|
963
|
+
tableName: "foundry_applied_versions";
|
|
964
|
+
dataType: "number";
|
|
965
|
+
columnType: "PgBigSerial53";
|
|
966
|
+
data: number;
|
|
967
|
+
driverParam: number;
|
|
968
|
+
notNull: true;
|
|
969
|
+
hasDefault: true;
|
|
970
|
+
isPrimaryKey: true;
|
|
971
|
+
isAutoincrement: false;
|
|
972
|
+
hasRuntimeDefault: false;
|
|
973
|
+
enumValues: undefined;
|
|
974
|
+
baseColumn: never;
|
|
975
|
+
identity: undefined;
|
|
976
|
+
generated: undefined;
|
|
977
|
+
}, {}, {}>;
|
|
978
|
+
service: import("drizzle-orm/pg-core").PgColumn<{
|
|
979
|
+
name: "service";
|
|
980
|
+
tableName: "foundry_applied_versions";
|
|
981
|
+
dataType: "string";
|
|
982
|
+
columnType: "PgText";
|
|
983
|
+
data: string;
|
|
984
|
+
driverParam: string;
|
|
985
|
+
notNull: true;
|
|
986
|
+
hasDefault: false;
|
|
987
|
+
isPrimaryKey: false;
|
|
988
|
+
isAutoincrement: false;
|
|
989
|
+
hasRuntimeDefault: false;
|
|
990
|
+
enumValues: [string, ...string[]];
|
|
991
|
+
baseColumn: never;
|
|
992
|
+
identity: undefined;
|
|
993
|
+
generated: undefined;
|
|
994
|
+
}, {}, {}>;
|
|
995
|
+
ref: import("drizzle-orm/pg-core").PgColumn<{
|
|
996
|
+
name: "ref";
|
|
997
|
+
tableName: "foundry_applied_versions";
|
|
998
|
+
dataType: "string";
|
|
999
|
+
columnType: "PgText";
|
|
1000
|
+
data: string;
|
|
1001
|
+
driverParam: string;
|
|
1002
|
+
notNull: true;
|
|
1003
|
+
hasDefault: false;
|
|
1004
|
+
isPrimaryKey: false;
|
|
1005
|
+
isAutoincrement: false;
|
|
1006
|
+
hasRuntimeDefault: false;
|
|
1007
|
+
enumValues: [string, ...string[]];
|
|
1008
|
+
baseColumn: never;
|
|
1009
|
+
identity: undefined;
|
|
1010
|
+
generated: undefined;
|
|
1011
|
+
}, {}, {}>;
|
|
1012
|
+
appliedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1013
|
+
name: "applied_at";
|
|
1014
|
+
tableName: "foundry_applied_versions";
|
|
1015
|
+
dataType: "date";
|
|
1016
|
+
columnType: "PgTimestamp";
|
|
1017
|
+
data: Date;
|
|
1018
|
+
driverParam: string;
|
|
1019
|
+
notNull: true;
|
|
1020
|
+
hasDefault: true;
|
|
1021
|
+
isPrimaryKey: false;
|
|
1022
|
+
isAutoincrement: false;
|
|
1023
|
+
hasRuntimeDefault: false;
|
|
1024
|
+
enumValues: undefined;
|
|
1025
|
+
baseColumn: never;
|
|
1026
|
+
identity: undefined;
|
|
1027
|
+
generated: undefined;
|
|
1028
|
+
}, {}, {}>;
|
|
1029
|
+
runId: import("drizzle-orm/pg-core").PgColumn<{
|
|
1030
|
+
name: "run_id";
|
|
1031
|
+
tableName: "foundry_applied_versions";
|
|
1032
|
+
dataType: "string";
|
|
1033
|
+
columnType: "PgText";
|
|
1034
|
+
data: string;
|
|
1035
|
+
driverParam: string;
|
|
1036
|
+
notNull: false;
|
|
1037
|
+
hasDefault: false;
|
|
1038
|
+
isPrimaryKey: false;
|
|
1039
|
+
isAutoincrement: false;
|
|
1040
|
+
hasRuntimeDefault: false;
|
|
1041
|
+
enumValues: [string, ...string[]];
|
|
1042
|
+
baseColumn: never;
|
|
1043
|
+
identity: undefined;
|
|
1044
|
+
generated: undefined;
|
|
1045
|
+
}, {}, {}>;
|
|
1046
|
+
backupId: import("drizzle-orm/pg-core").PgColumn<{
|
|
1047
|
+
name: "backup_id";
|
|
1048
|
+
tableName: "foundry_applied_versions";
|
|
1049
|
+
dataType: "string";
|
|
1050
|
+
columnType: "PgText";
|
|
1051
|
+
data: string;
|
|
1052
|
+
driverParam: string;
|
|
1053
|
+
notNull: false;
|
|
1054
|
+
hasDefault: false;
|
|
1055
|
+
isPrimaryKey: false;
|
|
1056
|
+
isAutoincrement: false;
|
|
1057
|
+
hasRuntimeDefault: false;
|
|
1058
|
+
enumValues: [string, ...string[]];
|
|
1059
|
+
baseColumn: never;
|
|
1060
|
+
identity: undefined;
|
|
1061
|
+
generated: undefined;
|
|
1062
|
+
}, {}, {}>;
|
|
1063
|
+
};
|
|
1064
|
+
dialect: "pg";
|
|
1065
|
+
}>;
|
|
951
1066
|
/** Mirrors `migrations/0012_audit.sql`, the source of truth (#85). */
|
|
952
1067
|
export declare const foundryAuditEvents: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
953
1068
|
name: "foundry_audit_events";
|
|
@@ -1195,6 +1310,23 @@ export declare const foundryDeclaredPlatform: import("drizzle-orm/pg-core").PgTa
|
|
|
1195
1310
|
identity: undefined;
|
|
1196
1311
|
generated: undefined;
|
|
1197
1312
|
}, {}, {}>;
|
|
1313
|
+
serverIpv4: import("drizzle-orm/pg-core").PgColumn<{
|
|
1314
|
+
name: "server_ipv4";
|
|
1315
|
+
tableName: "foundry_declared_platform";
|
|
1316
|
+
dataType: "string";
|
|
1317
|
+
columnType: "PgText";
|
|
1318
|
+
data: string;
|
|
1319
|
+
driverParam: string;
|
|
1320
|
+
notNull: false;
|
|
1321
|
+
hasDefault: false;
|
|
1322
|
+
isPrimaryKey: false;
|
|
1323
|
+
isAutoincrement: false;
|
|
1324
|
+
hasRuntimeDefault: false;
|
|
1325
|
+
enumValues: [string, ...string[]];
|
|
1326
|
+
baseColumn: never;
|
|
1327
|
+
identity: undefined;
|
|
1328
|
+
generated: undefined;
|
|
1329
|
+
}, {}, {}>;
|
|
1198
1330
|
authEnabled: import("drizzle-orm/pg-core").PgColumn<{
|
|
1199
1331
|
name: "auth_enabled";
|
|
1200
1332
|
tableName: "foundry_declared_platform";
|
|
@@ -1803,3 +1935,5 @@ export type DeclaredPlatformRow = typeof foundryDeclaredPlatform.$inferSelect;
|
|
|
1803
1935
|
export type NewDeclaredPlatformRow = typeof foundryDeclaredPlatform.$inferInsert;
|
|
1804
1936
|
export type RequestedChangeRow = typeof foundryRequestedChanges.$inferSelect;
|
|
1805
1937
|
export type NewRequestedChangeRow = typeof foundryRequestedChanges.$inferInsert;
|
|
1938
|
+
export type AppliedVersionRow = typeof foundryAppliedVersions.$inferSelect;
|
|
1939
|
+
export type NewAppliedVersionRow = typeof foundryAppliedVersions.$inferInsert;
|
package/dist/store/schema.js
CHANGED
|
@@ -102,6 +102,20 @@ export const foundryAppliedState = pgTable('foundry_applied_state', {
|
|
|
102
102
|
lastAppliedAt: timestamp('last_applied_at', { withTimezone: true }),
|
|
103
103
|
lastAppliedDeclaredOrgVersion: text('last_applied_declared_org_version'),
|
|
104
104
|
});
|
|
105
|
+
/**
|
|
106
|
+
* Mirrors `migrations/0036_applied_versions.sql`, the source of truth: an
|
|
107
|
+
* appended history row beside `foundryAppliedState`'s single current
|
|
108
|
+
* snapshot (#253). See that migration for what `service`, `ref`, `runId`
|
|
109
|
+
* and `backupId` mean and why the last two are nullable.
|
|
110
|
+
*/
|
|
111
|
+
export const foundryAppliedVersions = pgTable('foundry_applied_versions', {
|
|
112
|
+
id: bigserial('id', { mode: 'number' }).primaryKey(),
|
|
113
|
+
service: text('service').notNull(),
|
|
114
|
+
ref: text('ref').notNull(),
|
|
115
|
+
appliedAt: timestamp('applied_at', { withTimezone: true }).notNull().defaultNow(),
|
|
116
|
+
runId: text('run_id'),
|
|
117
|
+
backupId: text('backup_id'),
|
|
118
|
+
});
|
|
105
119
|
/** Mirrors `migrations/0012_audit.sql`, the source of truth (#85). */
|
|
106
120
|
export const foundryAuditEvents = pgTable('foundry_audit_events', {
|
|
107
121
|
id: bigserial('id', { mode: 'number' }).primaryKey(),
|
|
@@ -126,6 +140,7 @@ export const foundryDeclaredPlatform = pgTable('foundry_declared_platform', {
|
|
|
126
140
|
id: boolean('id').primaryKey().default(true),
|
|
127
141
|
ownerOrgId: text('owner_org_id').notNull(),
|
|
128
142
|
apex: text('apex').notNull(),
|
|
143
|
+
serverIpv4: text('server_ipv4'),
|
|
129
144
|
authEnabled: boolean('auth_enabled').notNull().default(false),
|
|
130
145
|
authHostname: text('auth_hostname').notNull(),
|
|
131
146
|
authGitRef: text('auth_git_ref').notNull().default(''),
|
package/dist/store.d.ts
CHANGED
|
@@ -90,6 +90,43 @@ export interface PlatformStore {
|
|
|
90
90
|
/** Replaces the recorded ids. A no-op until the platform is declared. */
|
|
91
91
|
putServiceIds(serviceIds: Record<string, string>): Promise<void>;
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* One recorded apply, appended to the history #253 keeps beside
|
|
95
|
+
* {@link AppliedState}'s single current snapshot. See
|
|
96
|
+
* `migrations/0036_applied_versions.sql` for what each field means and why
|
|
97
|
+
* `runId`/`backupId` are nullable.
|
|
98
|
+
*/
|
|
99
|
+
export interface AppliedVersion {
|
|
100
|
+
service: string;
|
|
101
|
+
ref: string;
|
|
102
|
+
appliedAt: string;
|
|
103
|
+
runId: string | null;
|
|
104
|
+
backupId: string | null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The persistence seam for #253's history of recorded applies: a row
|
|
108
|
+
* appended every time a version is recorded as applied, never replaced in
|
|
109
|
+
* place the way {@link DeclaredOrgStore.putAppliedState} and
|
|
110
|
+
* {@link PlatformStore.putServiceIds} replace their own single snapshot.
|
|
111
|
+
* `service` is whatever was just recorded -- today, an org's id -- and
|
|
112
|
+
* `previousAppliedRef` is what a rollback reads to find what to roll back
|
|
113
|
+
* to.
|
|
114
|
+
*/
|
|
115
|
+
export interface AppliedVersionsStore {
|
|
116
|
+
/** Appends a row. Never replaces or deduplicates an existing one. */
|
|
117
|
+
record(entry: {
|
|
118
|
+
service: string;
|
|
119
|
+
ref: string;
|
|
120
|
+
appliedAt: string;
|
|
121
|
+
runId?: string | null;
|
|
122
|
+
backupId?: string | null;
|
|
123
|
+
}): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* The `ref` of the second-most-recent row recorded for `service` -- the
|
|
126
|
+
* one before whatever is current -- or `null` with fewer than two rows.
|
|
127
|
+
*/
|
|
128
|
+
previousAppliedRef(service: string): Promise<string | null>;
|
|
129
|
+
}
|
|
93
130
|
/**
|
|
94
131
|
* The persistence seam for #178's {@link RequestedChange}: a company's own
|
|
95
132
|
* Boule requests a setting change on its declared org, foundry classifies
|