@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.
- package/dist/calls-sfu-deploy-support.d.ts +57 -0
- package/dist/calls-sfu-deploy-support.js +82 -0
- package/dist/calls-sfu.d.ts +175 -0
- package/dist/calls-sfu.js +239 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +6 -4
- package/dist/migrate.d.ts +38 -0
- package/dist/migrate.js +47 -0
- package/dist/migrations/0012_audit.sql +21 -0
- package/dist/migrations/0013_access_emails.sql +6 -0
- package/dist/org.d.ts +23 -0
- package/dist/org.js +37 -0
- package/dist/plan/diff.js +136 -5
- package/dist/plan/executor.d.ts +363 -14
- package/dist/plan/executor.js +759 -24
- package/dist/plan/index.d.ts +2 -2
- package/dist/plan/index.js +2 -2
- package/dist/plan/plan-apply.js +17 -2
- package/dist/plan/providers-readers.d.ts +54 -1
- package/dist/plan/providers-readers.js +153 -1
- package/dist/provision.d.ts +56 -0
- package/dist/provision.js +69 -0
- package/dist/slug.d.ts +12 -18
- package/dist/slug.js +66 -0
- package/dist/store/audit.d.ts +8 -0
- package/dist/store/audit.js +43 -0
- package/dist/store/postgres.js +20 -1
- package/dist/store/schema.d.ts +188 -0
- package/dist/store/schema.js +14 -1
- package/dist/store.d.ts +2 -0
- package/package.json +9 -3
package/dist/store/postgres.js
CHANGED
|
@@ -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
|
package/dist/store/schema.d.ts
CHANGED
|
@@ -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;
|
package/dist/store/schema.js
CHANGED
|
@@ -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.
|
|
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/
|
|
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",
|