@wtfalch/foundry 0.2.0 → 0.3.0

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.
@@ -1,5 +1,5 @@
1
1
  import { eq, sql } from 'drizzle-orm';
2
- import { validateBuyerIdentity, validateHosting, validateOwner } from '../org.js';
2
+ import { validateBuyerIdentity, validateHosting, validateOwner, validateSite } from '../org.js';
3
3
  import { validateSlug } from '../slug.js';
4
4
  import { declaredOrgs, foundryAppliedState, } from './schema.js';
5
5
  const UNIQUE_VIOLATION = '23505';
@@ -58,6 +58,7 @@ function rowToOrg(row) {
58
58
  gitBranch: row.hostingGitBranch,
59
59
  buildPack: row.hostingBuildPack,
60
60
  issuerApps: row.hostingIssuerApps,
61
+ accessEmails: row.hostingAccessEmails,
61
62
  },
62
63
  sso: { enabled: row.ssoEnabled },
63
64
  guests: { enabled: row.guestsEnabled },
@@ -93,6 +94,7 @@ function orgToRow(org) {
93
94
  hostingGitBranch: org.hosting.gitBranch,
94
95
  hostingBuildPack: org.hosting.buildPack,
95
96
  hostingIssuerApps: org.hosting.issuerApps,
97
+ hostingAccessEmails: org.hosting.accessEmails,
96
98
  ssoEnabled: org.sso.enabled,
97
99
  guestsEnabled: org.guests.enabled,
98
100
  soloEnabled: org.solo.enabled,
@@ -211,6 +213,13 @@ export function createPostgresDeclaredOrgStore(db) {
211
213
  if (reason)
212
214
  throw new DeclaredOrgError(`hosting invalid: ${reason} (#71)`);
213
215
  }
216
+ // #101: same place `requireValidHosting` checks hosting, on both `create`
217
+ // and the merged result of `update`.
218
+ function requireValidSite(org) {
219
+ const reason = validateSite(org);
220
+ if (reason)
221
+ throw new DeclaredOrgError(`site invalid: ${reason} (#101)`);
222
+ }
214
223
  // Same place `requireValidHosting` checks hosting, on both `create` and
215
224
  // the merged result of `update`.
216
225
  function requireValidOwner(org) {
@@ -224,6 +233,7 @@ export function createPostgresDeclaredOrgStore(db) {
224
233
  requireValidLimits(org);
225
234
  requireValidBuyer(org);
226
235
  requireValidHosting(org);
236
+ requireValidSite(org);
227
237
  requireValidOwner(org);
228
238
  try {
229
239
  await db.insert(declaredOrgs).values(orgToRow(org));
@@ -243,6 +253,14 @@ export function createPostgresDeclaredOrgStore(db) {
243
253
  const [row] = await db.select().from(declaredOrgs).where(eq(declaredOrgs.id, id)).limit(1);
244
254
  return row ? rowToOrg(row) : null;
245
255
  },
256
+ async slugTaken(slug) {
257
+ const [row] = await db
258
+ .select({ id: declaredOrgs.id })
259
+ .from(declaredOrgs)
260
+ .where(eq(declaredOrgs.slug, slug))
261
+ .limit(1);
262
+ return row !== undefined;
263
+ },
246
264
  async update(id, patch) {
247
265
  const current = rowToOrg(await requireRow(id));
248
266
  // The slug freeze (#7 corrections 3): once mail exists, the mail
@@ -264,6 +282,7 @@ export function createPostgresDeclaredOrgStore(db) {
264
282
  requireValidLimits(next);
265
283
  requireValidBuyer(next);
266
284
  requireValidHosting(next);
285
+ requireValidSite(next);
267
286
  requireValidOwner(next);
268
287
  try {
269
288
  await db
@@ -1,4 +1,5 @@
1
1
  import type { IssuerApp } from '../org.js';
2
+ import type { Plan } from '../plan.js';
2
3
  import type { Checkpoint } from '../state.js';
3
4
  /**
4
5
  * Mirrors `migrations/0001_declared_org.sql`, which is the source of truth:
@@ -318,6 +319,25 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
318
319
  }, {}, {
319
320
  $type: IssuerApp[];
320
321
  }>;
322
+ hostingAccessEmails: import("drizzle-orm/pg-core").PgColumn<{
323
+ name: "hosting_access_emails";
324
+ tableName: "foundry_declared_orgs";
325
+ dataType: "json";
326
+ columnType: "PgJsonb";
327
+ data: string[];
328
+ driverParam: unknown;
329
+ notNull: true;
330
+ hasDefault: true;
331
+ isPrimaryKey: false;
332
+ isAutoincrement: false;
333
+ hasRuntimeDefault: false;
334
+ enumValues: undefined;
335
+ baseColumn: never;
336
+ identity: undefined;
337
+ generated: undefined;
338
+ }, {}, {
339
+ $type: string[];
340
+ }>;
321
341
  ssoEnabled: import("drizzle-orm/pg-core").PgColumn<{
322
342
  name: "sso_enabled";
323
343
  tableName: "foundry_declared_orgs";
@@ -799,7 +819,175 @@ export declare const foundryAppliedState: import("drizzle-orm/pg-core").PgTableW
799
819
  };
800
820
  dialect: "pg";
801
821
  }>;
822
+ /** Mirrors `migrations/0012_audit.sql`, the source of truth (#85). */
823
+ export declare const foundryAuditEvents: import("drizzle-orm/pg-core").PgTableWithColumns<{
824
+ name: "foundry_audit_events";
825
+ schema: undefined;
826
+ columns: {
827
+ id: import("drizzle-orm/pg-core").PgColumn<{
828
+ name: "id";
829
+ tableName: "foundry_audit_events";
830
+ dataType: "number";
831
+ columnType: "PgBigSerial53";
832
+ data: number;
833
+ driverParam: number;
834
+ notNull: true;
835
+ hasDefault: true;
836
+ isPrimaryKey: true;
837
+ isAutoincrement: false;
838
+ hasRuntimeDefault: false;
839
+ enumValues: undefined;
840
+ baseColumn: never;
841
+ identity: undefined;
842
+ generated: undefined;
843
+ }, {}, {}>;
844
+ at: import("drizzle-orm/pg-core").PgColumn<{
845
+ name: "at";
846
+ tableName: "foundry_audit_events";
847
+ dataType: "date";
848
+ columnType: "PgTimestamp";
849
+ data: Date;
850
+ driverParam: string;
851
+ notNull: true;
852
+ hasDefault: false;
853
+ isPrimaryKey: false;
854
+ isAutoincrement: false;
855
+ hasRuntimeDefault: false;
856
+ enumValues: undefined;
857
+ baseColumn: never;
858
+ identity: undefined;
859
+ generated: undefined;
860
+ }, {}, {}>;
861
+ kind: import("drizzle-orm/pg-core").PgColumn<{
862
+ name: "kind";
863
+ tableName: "foundry_audit_events";
864
+ dataType: "string";
865
+ columnType: "PgText";
866
+ data: "plan" | "apply" | "destroy";
867
+ driverParam: string;
868
+ notNull: true;
869
+ hasDefault: false;
870
+ isPrimaryKey: false;
871
+ isAutoincrement: false;
872
+ hasRuntimeDefault: false;
873
+ enumValues: [string, ...string[]];
874
+ baseColumn: never;
875
+ identity: undefined;
876
+ generated: undefined;
877
+ }, {}, {
878
+ $type: "plan" | "apply" | "destroy";
879
+ }>;
880
+ actorId: import("drizzle-orm/pg-core").PgColumn<{
881
+ name: "actor_id";
882
+ tableName: "foundry_audit_events";
883
+ dataType: "string";
884
+ columnType: "PgText";
885
+ data: string;
886
+ driverParam: string;
887
+ notNull: true;
888
+ hasDefault: false;
889
+ isPrimaryKey: false;
890
+ isAutoincrement: false;
891
+ hasRuntimeDefault: false;
892
+ enumValues: [string, ...string[]];
893
+ baseColumn: never;
894
+ identity: undefined;
895
+ generated: undefined;
896
+ }, {}, {}>;
897
+ actorDisplay: import("drizzle-orm/pg-core").PgColumn<{
898
+ name: "actor_display";
899
+ tableName: "foundry_audit_events";
900
+ dataType: "string";
901
+ columnType: "PgText";
902
+ data: string;
903
+ driverParam: string;
904
+ notNull: true;
905
+ hasDefault: false;
906
+ isPrimaryKey: false;
907
+ isAutoincrement: false;
908
+ hasRuntimeDefault: false;
909
+ enumValues: [string, ...string[]];
910
+ baseColumn: never;
911
+ identity: undefined;
912
+ generated: undefined;
913
+ }, {}, {}>;
914
+ orgId: import("drizzle-orm/pg-core").PgColumn<{
915
+ name: "org_id";
916
+ tableName: "foundry_audit_events";
917
+ dataType: "string";
918
+ columnType: "PgText";
919
+ data: string;
920
+ driverParam: string;
921
+ notNull: true;
922
+ hasDefault: false;
923
+ isPrimaryKey: false;
924
+ isAutoincrement: false;
925
+ hasRuntimeDefault: false;
926
+ enumValues: [string, ...string[]];
927
+ baseColumn: never;
928
+ identity: undefined;
929
+ generated: undefined;
930
+ }, {}, {}>;
931
+ outcome: import("drizzle-orm/pg-core").PgColumn<{
932
+ name: "outcome";
933
+ tableName: "foundry_audit_events";
934
+ dataType: "string";
935
+ columnType: "PgText";
936
+ data: "applied" | "refused" | "failed" | "destroyed";
937
+ driverParam: string;
938
+ notNull: false;
939
+ hasDefault: false;
940
+ isPrimaryKey: false;
941
+ isAutoincrement: false;
942
+ hasRuntimeDefault: false;
943
+ enumValues: [string, ...string[]];
944
+ baseColumn: never;
945
+ identity: undefined;
946
+ generated: undefined;
947
+ }, {}, {
948
+ $type: "applied" | "refused" | "failed" | "destroyed";
949
+ }>;
950
+ reason: import("drizzle-orm/pg-core").PgColumn<{
951
+ name: "reason";
952
+ tableName: "foundry_audit_events";
953
+ dataType: "string";
954
+ columnType: "PgText";
955
+ data: string;
956
+ driverParam: string;
957
+ notNull: false;
958
+ hasDefault: false;
959
+ isPrimaryKey: false;
960
+ isAutoincrement: false;
961
+ hasRuntimeDefault: false;
962
+ enumValues: [string, ...string[]];
963
+ baseColumn: never;
964
+ identity: undefined;
965
+ generated: undefined;
966
+ }, {}, {}>;
967
+ plan: import("drizzle-orm/pg-core").PgColumn<{
968
+ name: "plan";
969
+ tableName: "foundry_audit_events";
970
+ dataType: "json";
971
+ columnType: "PgJsonb";
972
+ data: Plan;
973
+ driverParam: unknown;
974
+ notNull: false;
975
+ hasDefault: false;
976
+ isPrimaryKey: false;
977
+ isAutoincrement: false;
978
+ hasRuntimeDefault: false;
979
+ enumValues: undefined;
980
+ baseColumn: never;
981
+ identity: undefined;
982
+ generated: undefined;
983
+ }, {}, {
984
+ $type: Plan;
985
+ }>;
986
+ };
987
+ dialect: "pg";
988
+ }>;
802
989
  export type DeclaredOrgRow = typeof declaredOrgs.$inferSelect;
803
990
  export type NewDeclaredOrgRow = typeof declaredOrgs.$inferInsert;
804
991
  export type AppliedStateRow = typeof foundryAppliedState.$inferSelect;
805
992
  export type NewAppliedStateRow = typeof foundryAppliedState.$inferInsert;
993
+ export type AuditEventRow = typeof foundryAuditEvents.$inferSelect;
@@ -1,4 +1,4 @@
1
- import { boolean, jsonb, pgTable, text, timestamp } from 'drizzle-orm/pg-core';
1
+ import { bigserial, boolean, jsonb, pgTable, text, timestamp } from 'drizzle-orm/pg-core';
2
2
  /**
3
3
  * Mirrors `migrations/0001_declared_org.sql`, which is the source of truth:
4
4
  * the SQL carries the CHECKs drizzle-kit does not generate here (this
@@ -26,6 +26,7 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
26
26
  hostingGitBranch: text('hosting_git_branch').notNull().default('main'),
27
27
  hostingBuildPack: text('hosting_build_pack').notNull().default('nixpacks'),
28
28
  hostingIssuerApps: jsonb('hosting_issuer_apps').notNull().default([]).$type(),
29
+ hostingAccessEmails: jsonb('hosting_access_emails').notNull().default([]).$type(),
29
30
  ssoEnabled: boolean('sso_enabled').notNull().default(false),
30
31
  guestsEnabled: boolean('guests_enabled').notNull().default(false),
31
32
  soloEnabled: boolean('solo_enabled').notNull().default(false),
@@ -75,3 +76,15 @@ export const foundryAppliedState = pgTable('foundry_applied_state', {
75
76
  lastAppliedAt: timestamp('last_applied_at', { withTimezone: true }),
76
77
  lastAppliedDeclaredOrgVersion: text('last_applied_declared_org_version'),
77
78
  });
79
+ /** Mirrors `migrations/0012_audit.sql`, the source of truth (#85). */
80
+ export const foundryAuditEvents = pgTable('foundry_audit_events', {
81
+ id: bigserial('id', { mode: 'number' }).primaryKey(),
82
+ at: timestamp('at', { withTimezone: true }).notNull(),
83
+ kind: text('kind').notNull().$type(),
84
+ actorId: text('actor_id').notNull(),
85
+ actorDisplay: text('actor_display').notNull(),
86
+ orgId: text('org_id').notNull(),
87
+ outcome: text('outcome').$type(),
88
+ reason: text('reason'),
89
+ plan: jsonb('plan').$type(),
90
+ });
package/dist/store.d.ts CHANGED
@@ -12,6 +12,8 @@ import type { AppliedState } from './state.js';
12
12
  export interface DeclaredOrgStore {
13
13
  create(org: DeclaredOrg): Promise<void>;
14
14
  get(id: OrgId): Promise<DeclaredOrg | null>;
15
+ /** Whether any org already holds this slug. `slugFromName` asks it. */
16
+ slugTaken(slug: string): Promise<boolean>;
15
17
  /**
16
18
  * Combinations foundry knows are impossible are rejected with a reason
17
19
  * (#18 "Done when"), so a caller must be ready for this to throw rather
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/foundry",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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",
@@ -28,13 +28,19 @@
28
28
  "scripts": {
29
29
  "build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/migrations && find src/migrations -maxdepth 1 -name '*.sql' -exec cp {} dist/migrations/ \\;",
30
30
  "typecheck": "tsc --noEmit",
31
- "test": "vitest run"
31
+ "test": "vitest run",
32
+ "db:provision": "node scripts/foundry-db-provision.mjs",
33
+ "db:migrate": "node scripts/foundry-db-migrate.mjs",
34
+ "calls-sfu:deploy": "node scripts/calls-sfu-deploy.mjs"
32
35
  },
33
36
  "peerDependencies": {
34
37
  "drizzle-orm": ">=0.45.2"
35
38
  },
36
39
  "dependencies": {
37
- "@wtfalch/providers": "0.5.0"
40
+ "@wtfalch/auth": "0.16.0",
41
+ "@wtfalch/email": "0.15.0",
42
+ "@wtfalch/providers": "0.5.0",
43
+ "ssh2": "1.17.0"
38
44
  },
39
45
  "devDependencies": {
40
46
  "@electric-sql/pglite": "^0.5.8",