@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/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()) {
@@ -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;
@@ -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
+ }
@@ -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;
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/foundry",
3
- "version": "0.16.7",
3
+ "version": "0.16.8",
4
4
  "description": "Foundry's SDK: the declared org's types, the plan and apply contracts, and the state store's interface.",
5
5
  "repository": {
6
6
  "type": "git",