@wtfalch/foundry 0.34.0 → 0.35.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.
Files changed (88) hide show
  1. package/dist/bootstrap/bootstrap.d.ts +1 -1
  2. package/dist/bootstrap/bootstrap.js +49 -11
  3. package/dist/bootstrap/declare.d.ts +12 -5
  4. package/dist/bootstrap/declare.js +33 -18
  5. package/dist/bootstrap/versions.d.ts +20 -0
  6. package/dist/bootstrap/versions.js +46 -0
  7. package/dist/calls-sfu.js +3 -3
  8. package/dist/index.d.ts +9 -4
  9. package/dist/index.js +5 -2
  10. package/dist/migrations/0053_heartbeats_provider.sql +9 -0
  11. package/dist/migrations/0053_org_version_pin.sql +6 -0
  12. package/dist/org.d.ts +60 -14
  13. package/dist/org.js +118 -28
  14. package/dist/plan/backup-gate.d.ts +93 -0
  15. package/dist/plan/backup-gate.js +261 -0
  16. package/dist/plan/broker-entries.d.ts +9 -1
  17. package/dist/plan/broker-entries.js +20 -3
  18. package/dist/plan/build-record.d.ts +56 -0
  19. package/dist/plan/build-record.js +109 -0
  20. package/dist/plan/calls-matrix.js +3 -3
  21. package/dist/plan/cloudflared-connector.js +2 -2
  22. package/dist/plan/company-backups.d.ts +31 -0
  23. package/dist/plan/company-backups.js +101 -0
  24. package/dist/plan/deploy-health.d.ts +12 -2
  25. package/dist/plan/deploy-health.js +3 -1
  26. package/dist/plan/destinations.d.ts +73 -0
  27. package/dist/plan/destinations.js +217 -0
  28. package/dist/plan/diff.d.ts +17 -8
  29. package/dist/plan/diff.js +131 -131
  30. package/dist/plan/edge-access.d.ts +15 -0
  31. package/dist/plan/edge-access.js +21 -2
  32. package/dist/plan/executor.d.ts +166 -7
  33. package/dist/plan/executor.js +547 -106
  34. package/dist/plan/health-targets.d.ts +1 -3
  35. package/dist/plan/health-targets.js +36 -36
  36. package/dist/plan/held-key-refs.d.ts +5 -0
  37. package/dist/plan/held-key-refs.js +5 -0
  38. package/dist/plan/hostnames.d.ts +10 -0
  39. package/dist/plan/hostnames.js +14 -0
  40. package/dist/plan/index.d.ts +5 -0
  41. package/dist/plan/index.js +3 -0
  42. package/dist/plan/live.d.ts +6 -0
  43. package/dist/plan/live.js +3 -2
  44. package/dist/plan/locked-hostnames.d.ts +2 -1
  45. package/dist/plan/locked-hostnames.js +16 -3
  46. package/dist/plan/login-host.d.ts +9 -20
  47. package/dist/plan/login-host.js +10 -25
  48. package/dist/plan/mail-relay.d.ts +48 -29
  49. package/dist/plan/mail-relay.js +145 -68
  50. package/dist/plan/network-move.d.ts +20 -0
  51. package/dist/plan/network-move.js +41 -0
  52. package/dist/plan/oikos-ingress.d.ts +111 -0
  53. package/dist/plan/oikos-ingress.js +231 -0
  54. package/dist/plan/org-names.d.ts +8 -2
  55. package/dist/plan/org-names.js +57 -1
  56. package/dist/plan/own-diff.d.ts +9 -8
  57. package/dist/plan/own-diff.js +42 -34
  58. package/dist/plan/own-hostnames.d.ts +27 -0
  59. package/dist/plan/own-hostnames.js +24 -0
  60. package/dist/plan/own-limits.d.ts +198 -7
  61. package/dist/plan/own-limits.js +163 -32
  62. package/dist/plan/own-services.js +3 -1
  63. package/dist/plan/own-sfu.js +3 -2
  64. package/dist/plan/plan-apply.d.ts +47 -7
  65. package/dist/plan/plan-apply.js +184 -38
  66. package/dist/plan/platform-diff.d.ts +133 -17
  67. package/dist/plan/platform-diff.js +231 -68
  68. package/dist/plan/platform-org.d.ts +79 -10
  69. package/dist/plan/platform-org.js +93 -13
  70. package/dist/plan/platform-providers.d.ts +118 -73
  71. package/dist/plan/platform-providers.js +770 -231
  72. package/dist/plan/proxy-labels.d.ts +57 -11
  73. package/dist/plan/proxy-labels.js +94 -14
  74. package/dist/plan/run-options.d.ts +19 -0
  75. package/dist/plan/run-options.js +46 -0
  76. package/dist/plan/zitadel-login-policy.d.ts +1 -1
  77. package/dist/plan/zitadel-login-policy.js +76 -8
  78. package/dist/platform.d.ts +32 -0
  79. package/dist/platform.js +16 -0
  80. package/dist/provision.d.ts +2 -0
  81. package/dist/provision.js +3 -1
  82. package/dist/slug.d.ts +3 -1
  83. package/dist/slug.js +25 -3
  84. package/dist/store/postgres.js +43 -1
  85. package/dist/store/schema.d.ts +36 -0
  86. package/dist/store/schema.js +4 -0
  87. package/package.json +3 -3
  88. package/versions.digests.json +11 -0
@@ -1,5 +1,5 @@
1
1
  import { and, asc, desc, eq, ne, sql } from 'drizzle-orm';
2
- import { APP_ENTRY_KEY, DEFAULT_MAIL_APPS, DEFAULT_NO_REPLY_LOCAL_PART, validateBuyerIdentity, validateCalls, validateDomain, validateHosting, validateMail, validateOwners, validateServices, validateSite, } from '../org.js';
2
+ import { APP_ENTRY_KEY, DEFAULT_MAIL_APPS, DEFAULT_NO_REPLY_LOCAL_PART, validateBuyerIdentity, validateCalls, validateDomain, validateDomainClaim, validateHosting, validateMail, validateOwners, validateServices, validateSite, validateVersionPin, } from '../org.js';
3
3
  import { lockedHostnames } from '../plan/locked-hostnames.js';
4
4
  import { findNameClashes, nameClashMessage } from '../plan/org-names.js';
5
5
  import { validatePlatform } from '../platform.js';
@@ -146,6 +146,8 @@ function rowToOrg(row) {
146
146
  },
147
147
  }
148
148
  : {}),
149
+ // Omitted while unset. Validated on read: a stored value is not trusted.
150
+ ...(row.versionPin ? { versionPin: readVersionPin(row) } : {}),
149
151
  site: { enabled: row.siteEnabled, usesDomain: row.siteUsesDomain },
150
152
  hosting: {
151
153
  enabled: row.hostingEnabled,
@@ -169,6 +171,14 @@ function rowToOrg(row) {
169
171
  test: row.isTest,
170
172
  };
171
173
  }
174
+ function readVersionPin(row) {
175
+ const pin = row.versionPin;
176
+ const reason = validateVersionPin({ versionPin: pin });
177
+ if (reason) {
178
+ throw new DeclaredOrgError(`stored org ${row.slug} has an invalid versionPin: ${reason}`);
179
+ }
180
+ return pin;
181
+ }
172
182
  function orgToRow(org) {
173
183
  return {
174
184
  id: org.id,
@@ -190,6 +200,7 @@ function orgToRow(org) {
190
200
  issuerUsesDomain: org.issuer.usesDomain,
191
201
  servicesOwn: org.services?.own ?? false,
192
202
  servicesSfuSlot: org.services?.sfuSlot ?? null,
203
+ versionPin: org.versionPin && Object.keys(org.versionPin).length > 0 ? org.versionPin : null,
193
204
  siteEnabled: org.site.enabled,
194
205
  siteUsesDomain: org.site.usesDomain,
195
206
  hostingEnabled: org.hosting.enabled,
@@ -288,6 +299,29 @@ export function createPostgresDeclaredOrgStore(db) {
288
299
  if (reason)
289
300
  throw new DeclaredOrgError(`domain invalid: ${reason} (#131)`);
290
301
  }
302
+ // The platform's apex and every other company's domain are read here, so a claim is checked against what is declared now.
303
+ async function requireDomainClaim(org) {
304
+ if (org.domain === null)
305
+ return;
306
+ const [platformRow] = await db
307
+ .select({
308
+ apex: foundryDeclaredPlatform.apex,
309
+ ownerOrgId: foundryDeclaredPlatform.ownerOrgId,
310
+ })
311
+ .from(foundryDeclaredPlatform)
312
+ .limit(1);
313
+ const others = await db
314
+ .select({ id: declaredOrgs.id, domain: declaredOrgs.domain })
315
+ .from(declaredOrgs)
316
+ .where(ne(declaredOrgs.id, org.id));
317
+ const reason = validateDomainClaim(org.domain, {
318
+ orgId: org.id,
319
+ platform: platformRow ?? null,
320
+ others,
321
+ });
322
+ if (reason)
323
+ throw new DeclaredOrgError(`domain invalid: ${reason}`);
324
+ }
291
325
  function requireValidLimit(fieldName, value) {
292
326
  if (value !== null && !NON_NEGATIVE_INTEGER.test(value)) {
293
327
  throw new DeclaredOrgError(`${fieldName} '${value}' must be a bigint-safe decimal string of a non-negative integer, or null for uncapped (#27)`);
@@ -357,6 +391,9 @@ export function createPostgresDeclaredOrgStore(db) {
357
391
  const reason = validateCalls(org);
358
392
  if (reason)
359
393
  throw new DeclaredOrgError(`calls invalid: ${reason} (#259)`);
394
+ const pinReason = validateVersionPin(org);
395
+ if (pinReason)
396
+ throw new DeclaredOrgError(`versionPin invalid: ${pinReason}`);
360
397
  }
361
398
  // Own-service rules, on `create` and on the merged result of `update`; no
362
399
  // two orgs share an `sfuSlot` (the unique index catches the race this misses).
@@ -396,6 +433,7 @@ export function createPostgresDeclaredOrgStore(db) {
396
433
  requireValidCalls(org);
397
434
  await requireValidServices(org);
398
435
  await requireNoNameClash(org);
436
+ await requireDomainClaim(org);
399
437
  try {
400
438
  await db.insert(declaredOrgs).values(orgToRow(org));
401
439
  }
@@ -505,6 +543,8 @@ export function createPostgresDeclaredOrgStore(db) {
505
543
  requireValidCalls(next);
506
544
  await requireValidServices(next);
507
545
  await requireNoNameClash(next);
546
+ if (next.domain !== current.domain)
547
+ await requireDomainClaim(next);
508
548
  try {
509
549
  // #351: with `expectedVersion` the version check is part of this one
510
550
  // statement, so an edit that lands after the read above makes it match
@@ -689,6 +729,7 @@ function rowToPlatform(row) {
689
729
  gitRef: row.coolifyGitRef,
690
730
  },
691
731
  uptime: { enabled: row.uptimeEnabled, tag: row.uptimeTag, sha256: row.uptimeSha256 },
732
+ heartbeatsProvider: row.heartbeatsProvider,
692
733
  };
693
734
  }
694
735
  function platformToRow(platform) {
@@ -736,6 +777,7 @@ function platformToRow(platform) {
736
777
  uptimeEnabled: platform.uptime.enabled,
737
778
  uptimeTag: platform.uptime.tag,
738
779
  uptimeSha256: platform.uptime.sha256,
780
+ heartbeatsProvider: platform.heartbeatsProvider,
739
781
  };
740
782
  }
741
783
  /**
@@ -355,6 +355,25 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
355
355
  identity: undefined;
356
356
  generated: undefined;
357
357
  }, {}, {}>;
358
+ versionPin: import("drizzle-orm/pg-core").PgColumn<{
359
+ name: "version_pin";
360
+ tableName: "foundry_declared_orgs";
361
+ dataType: "json";
362
+ columnType: "PgJsonb";
363
+ data: Record<string, string>;
364
+ driverParam: unknown;
365
+ notNull: false;
366
+ hasDefault: false;
367
+ isPrimaryKey: false;
368
+ isAutoincrement: false;
369
+ hasRuntimeDefault: false;
370
+ enumValues: undefined;
371
+ baseColumn: never;
372
+ identity: undefined;
373
+ generated: undefined;
374
+ }, {}, {
375
+ $type: Record<string, string>;
376
+ }>;
358
377
  siteEnabled: import("drizzle-orm/pg-core").PgColumn<{
359
378
  name: "site_enabled";
360
379
  tableName: "foundry_declared_orgs";
@@ -2170,6 +2189,23 @@ export declare const foundryDeclaredPlatform: import("drizzle-orm/pg-core").PgTa
2170
2189
  identity: undefined;
2171
2190
  generated: undefined;
2172
2191
  }, {}, {}>;
2192
+ heartbeatsProvider: import("drizzle-orm/pg-core").PgColumn<{
2193
+ name: "heartbeats_provider";
2194
+ tableName: "foundry_declared_platform";
2195
+ dataType: "string";
2196
+ columnType: "PgText";
2197
+ data: string;
2198
+ driverParam: string;
2199
+ notNull: true;
2200
+ hasDefault: true;
2201
+ isPrimaryKey: false;
2202
+ isAutoincrement: false;
2203
+ hasRuntimeDefault: false;
2204
+ enumValues: [string, ...string[]];
2205
+ baseColumn: never;
2206
+ identity: undefined;
2207
+ generated: undefined;
2208
+ }, {}, {}>;
2173
2209
  serviceIds: import("drizzle-orm/pg-core").PgColumn<{
2174
2210
  name: "service_ids";
2175
2211
  tableName: "foundry_declared_platform";
@@ -38,6 +38,8 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
38
38
  // ServicesConfig (org.ts): false and null are the platform's shared services.
39
39
  servicesOwn: boolean('services_own').notNull().default(false),
40
40
  servicesSfuSlot: integer('services_sfu_slot'),
41
+ // VersionPin (org.ts): null follows the platform. `migrations/0053_org_version_pin.sql`.
42
+ versionPin: jsonb('version_pin').$type(),
41
43
  // SiteConfig (org.ts, T10). A brand new org has no cms site.
42
44
  siteEnabled: boolean('site_enabled').notNull().default(false),
43
45
  siteUsesDomain: boolean('site_uses_domain').notNull().default(false),
@@ -203,6 +205,8 @@ export const foundryDeclaredPlatform = pgTable('foundry_declared_platform', {
203
205
  uptimeEnabled: boolean('uptime_enabled').notNull().default(true),
204
206
  uptimeTag: text('uptime_tag').notNull().default(''),
205
207
  uptimeSha256: text('uptime_sha256').notNull().default(''),
208
+ /** foundry#544, `migrations/0053_heartbeats_provider.sql`: which Healthchecks instance manages the heartbeat checks, decoupled from `hcEnabled` above. */
209
+ heartbeatsProvider: text('heartbeats_provider').notNull().default('self-hosted'),
206
210
  /** `migrations/0018_platform_apply.sql`: the platform's applied state, `system:kind:key` to provider id. */
207
211
  serviceIds: jsonb('service_ids').$type().notNull().default({}),
208
212
  createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/foundry",
3
- "version": "0.34.0",
3
+ "version": "0.35.1",
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",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "license": "MIT",
11
11
  "type": "module",
12
- "files": ["dist", "versions.json"],
12
+ "files": ["dist", "versions.json", "versions.digests.json"],
13
13
  "exports": {
14
14
  ".": {
15
15
  "types": "./dist/index.d.ts",
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@wtfalch/audit": "0.6.0",
46
46
  "@wtfalch/auth": "0.16.0",
47
- "@wtfalch/edge": "0.12.0",
47
+ "@wtfalch/edge": "0.13.0",
48
48
  "@wtfalch/email": "^0.20.0",
49
49
  "@wtfalch/providers": "0.7.5",
50
50
  "ssh2": "1.17.0",
@@ -0,0 +1,11 @@
1
+ {
2
+ "zitadel": "sha256:2ec2a42551862ca59dc752c321c7041358dea8b33b63ea5e021ec499ad5e2d9f",
3
+ "synapse": "sha256:6b95dd129e35e97e8032cb759e5db4a516a5f7ef6c98d34d716fea1ae239b62a",
4
+ "mas": "sha256:52c18ffcc940220a3b6aa5985b7e09d24ac27f5ed10a4d660c312e48f73ff105",
5
+ "livekit": "sha256:6fd3b7088874c4d119160dd688798dfec852bc014786d392caad15f6f63912a3",
6
+ "cloudflared": "sha256:072c067d25ccbe61d46e18f0d0723255f2bb5304f7317caa95b27031520ff92c",
7
+ "stalwart": "sha256:ec011be228596e37e65f41aab17deed573859614430472f7eeb42178c50d87b7",
8
+ "hc": "sha256:aa08a61b0dcfd83f1332eab195d5cec1b595ae076eb8388cadf8a3d597114f9b",
9
+ "alpine": "sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1",
10
+ "busybox": "sha256:5cec3fc171c87218698e85a52af7087de727372aae264a787b8112901a5b0092"
11
+ }