@wtfalch/foundry 0.14.0 → 0.16.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/bootstrap/declare.d.ts +2 -2
- package/dist/bootstrap/declare.js +6 -6
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/migrations/0034_owners.sql +42 -0
- package/dist/migrations/0035_chat_on_by_default.sql +10 -0
- package/dist/org.d.ts +80 -17
- package/dist/org.js +221 -18
- package/dist/plan/agora.d.ts +19 -1
- package/dist/plan/agora.js +37 -2
- package/dist/plan/chat-matrix.d.ts +99 -0
- package/dist/plan/chat-matrix.js +194 -0
- package/dist/plan/diff.d.ts +5 -3
- package/dist/plan/diff.js +143 -83
- package/dist/plan/entry-ai-key.d.ts +58 -0
- package/dist/plan/entry-ai-key.js +209 -0
- package/dist/plan/entry-files-key.d.ts +82 -0
- package/dist/plan/entry-files-key.js +327 -0
- package/dist/plan/executor.d.ts +244 -16
- package/dist/plan/executor.js +860 -78
- package/dist/plan/hostnames.d.ts +8 -1
- package/dist/plan/hostnames.js +18 -1
- package/dist/plan/index.d.ts +2 -1
- package/dist/plan/index.js +2 -1
- package/dist/plan/plan-apply.d.ts +2 -0
- package/dist/plan/plan-apply.js +42 -2
- package/dist/plan/platform-diff.d.ts +8 -0
- package/dist/plan/platform-diff.js +8 -0
- package/dist/plan/platform-org.d.ts +17 -5
- package/dist/plan/platform-org.js +15 -7
- package/dist/plan/platform-providers.d.ts +38 -12
- package/dist/plan/platform-providers.js +99 -16
- package/dist/plan/proxy-labels.d.ts +42 -0
- package/dist/plan/proxy-labels.js +73 -0
- package/dist/plan/zitadel-machine-user.d.ts +5 -1
- package/dist/plan/zitadel-machine-user.js +5 -1
- package/dist/plan/zitadel-project-roles.d.ts +25 -0
- package/dist/plan/zitadel-project-roles.js +104 -0
- package/dist/store/postgres.js +4 -17
- package/dist/store/schema.d.ts +13 -45
- package/dist/store/schema.js +5 -8
- package/package.json +2 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { DeclaredOrg, HostingEntry } from '../org.js';
|
|
2
|
+
/**
|
|
3
|
+
* What an entry's Traefik label set is built from. `wildcardDomain` is the
|
|
4
|
+
* org's domain when the entry declares `'*'`, else `null`.
|
|
5
|
+
*/
|
|
6
|
+
export interface EntryLabelSpec {
|
|
7
|
+
names: string[];
|
|
8
|
+
wildcardDomain: string | null;
|
|
9
|
+
port: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The scope key holding the hash of the label set last patched onto an
|
|
13
|
+
* entry's app. Present only for an entry that has had a custom label set.
|
|
14
|
+
*/
|
|
15
|
+
export declare function appLabelsAppliedScopeKey(entryKey: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* The label set an entry's app needs, or `null` when it needs none. Coolify
|
|
18
|
+
* replaces the labels it generates from `domains` once `custom_labels` is
|
|
19
|
+
* set, so an entry with `'*'` carries its exact `Host` rules as well. An entry
|
|
20
|
+
* that already has a custom set (`hadLabels`) keeps one, without the wildcard
|
|
21
|
+
* rule, so a removed `'*'` stops matching and a later name change still
|
|
22
|
+
* reaches the app: an empty `custom_labels` is refused by Coolify's API.
|
|
23
|
+
* Coolify's default port for an app is 80.
|
|
24
|
+
*/
|
|
25
|
+
export declare function entryLabelSpec(org: DeclaredOrg, entry: HostingEntry, hadLabels: boolean): EntryLabelSpec | null;
|
|
26
|
+
/** A short digest of the spec; the same spec always gives the same hash. */
|
|
27
|
+
export declare function entryLabelHash(spec: EntryLabelSpec): string;
|
|
28
|
+
/**
|
|
29
|
+
* The Traefik labels for an entry's app, one per line. Coolify reaches an app
|
|
30
|
+
* through its http entrypoint here (the tunnel dials `coolify-proxy:80`), so
|
|
31
|
+
* each name gets the same http router Coolify generates for a plain domain:
|
|
32
|
+
* `Host(name) && PathPrefix(/)`, default priority. The wildcard is one
|
|
33
|
+
* `HostRegexp` router at `priority=1`, so any exact name, this app's or
|
|
34
|
+
* another's, wins over it. The regex matches one label (`[a-z0-9-]+`), the
|
|
35
|
+
* same depth Cloudflare's Universal certificate covers.
|
|
36
|
+
*/
|
|
37
|
+
export declare function entryProxyLabels(appUuid: string, spec: EntryLabelSpec): string;
|
|
38
|
+
/**
|
|
39
|
+
* Whether two label sets hold the same labels. Order and the `coolify.*`
|
|
40
|
+
* lines Coolify adds and strips itself do not count.
|
|
41
|
+
*/
|
|
42
|
+
export declare function sameLabelSets(a: string, b: string): boolean;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { entryHostnames, wildcardHostname } from './hostnames.js';
|
|
3
|
+
/**
|
|
4
|
+
* The scope key holding the hash of the label set last patched onto an
|
|
5
|
+
* entry's app. Present only for an entry that has had a custom label set.
|
|
6
|
+
*/
|
|
7
|
+
export function appLabelsAppliedScopeKey(entryKey) {
|
|
8
|
+
return `app_labels_applied.${entryKey}`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The label set an entry's app needs, or `null` when it needs none. Coolify
|
|
12
|
+
* replaces the labels it generates from `domains` once `custom_labels` is
|
|
13
|
+
* set, so an entry with `'*'` carries its exact `Host` rules as well. An entry
|
|
14
|
+
* that already has a custom set (`hadLabels`) keeps one, without the wildcard
|
|
15
|
+
* rule, so a removed `'*'` stops matching and a later name change still
|
|
16
|
+
* reaches the app: an empty `custom_labels` is refused by Coolify's API.
|
|
17
|
+
* Coolify's default port for an app is 80.
|
|
18
|
+
*/
|
|
19
|
+
export function entryLabelSpec(org, entry, hadLabels) {
|
|
20
|
+
const wildcard = entry.hostnames.includes('*') ? wildcardHostname(org) : null;
|
|
21
|
+
if (!wildcard && !hadLabels)
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
names: entryHostnames(org, entry),
|
|
25
|
+
wildcardDomain: wildcard ? wildcard.slice(2) : null,
|
|
26
|
+
port: entry.buildPack === 'static' ? 80 : (entry.port ?? 80),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** A short digest of the spec; the same spec always gives the same hash. */
|
|
30
|
+
export function entryLabelHash(spec) {
|
|
31
|
+
return createHash('sha256').update(JSON.stringify(spec)).digest('hex').slice(0, 16);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The Traefik labels for an entry's app, one per line. Coolify reaches an app
|
|
35
|
+
* through its http entrypoint here (the tunnel dials `coolify-proxy:80`), so
|
|
36
|
+
* each name gets the same http router Coolify generates for a plain domain:
|
|
37
|
+
* `Host(name) && PathPrefix(/)`, default priority. The wildcard is one
|
|
38
|
+
* `HostRegexp` router at `priority=1`, so any exact name, this app's or
|
|
39
|
+
* another's, wins over it. The regex matches one label (`[a-z0-9-]+`), the
|
|
40
|
+
* same depth Cloudflare's Universal certificate covers.
|
|
41
|
+
*/
|
|
42
|
+
export function entryProxyLabels(appUuid, spec) {
|
|
43
|
+
const labels = [];
|
|
44
|
+
const route = (id, rule, priority) => {
|
|
45
|
+
const router = `traefik.http.routers.${id}`;
|
|
46
|
+
labels.push(`${router}.rule=${rule}`, `${router}.entryPoints=http`, `${router}.service=${id}`, `${router}.middlewares=gzip`, `traefik.http.services.${id}.loadbalancer.server.port=${spec.port}`);
|
|
47
|
+
if (priority !== undefined)
|
|
48
|
+
labels.push(`${router}.priority=${priority}`);
|
|
49
|
+
};
|
|
50
|
+
spec.names.forEach((name, i) => {
|
|
51
|
+
route(`http-${i}-${appUuid}`, `Host(\`${name}\`) && PathPrefix(\`/\`)`);
|
|
52
|
+
});
|
|
53
|
+
if (spec.wildcardDomain) {
|
|
54
|
+
const domain = spec.wildcardDomain.replace(/\./g, '[.]');
|
|
55
|
+
route(`http-wildcard-${appUuid}`, `HostRegexp(\`^[a-z0-9-]+[.]${domain}$\`)`, 1);
|
|
56
|
+
}
|
|
57
|
+
if (labels.length === 0)
|
|
58
|
+
return 'traefik.enable=false';
|
|
59
|
+
labels.push('traefik.enable=true', 'traefik.http.middlewares.gzip.compress=true');
|
|
60
|
+
return labels.sort().join('\n');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Whether two label sets hold the same labels. Order and the `coolify.*`
|
|
64
|
+
* lines Coolify adds and strips itself do not count.
|
|
65
|
+
*/
|
|
66
|
+
export function sameLabelSets(a, b) {
|
|
67
|
+
const lines = (set) => set
|
|
68
|
+
.split(/\r\n|\n|\r/)
|
|
69
|
+
.filter((line) => line !== '' && !line.startsWith('coolify.'))
|
|
70
|
+
.sort()
|
|
71
|
+
.join('\n');
|
|
72
|
+
return lines(a) === lines(b);
|
|
73
|
+
}
|
|
@@ -49,7 +49,11 @@ import type { ZitadelPort } from './executor.js';
|
|
|
49
49
|
*/
|
|
50
50
|
export declare class ZitadelHttpError extends Error {
|
|
51
51
|
readonly status: number;
|
|
52
|
-
|
|
52
|
+
/** ZITADEL's own gRPC-style error code from the body (6 is AlreadyExists), when it sent one. */
|
|
53
|
+
readonly code?: number | undefined;
|
|
54
|
+
constructor(status: number, message: string,
|
|
55
|
+
/** ZITADEL's own gRPC-style error code from the body (6 is AlreadyExists), when it sent one. */
|
|
56
|
+
code?: number | undefined);
|
|
53
57
|
}
|
|
54
58
|
/**
|
|
55
59
|
* #216: thrown by {@link createZitadelAdminMinter}'s `mintLoginClientMachineUser`
|
|
@@ -48,9 +48,13 @@
|
|
|
48
48
|
*/
|
|
49
49
|
export class ZitadelHttpError extends Error {
|
|
50
50
|
status;
|
|
51
|
-
|
|
51
|
+
code;
|
|
52
|
+
constructor(status, message,
|
|
53
|
+
/** ZITADEL's own gRPC-style error code from the body (6 is AlreadyExists), when it sent one. */
|
|
54
|
+
code) {
|
|
52
55
|
super(message);
|
|
53
56
|
this.status = status;
|
|
57
|
+
this.code = code;
|
|
54
58
|
this.name = 'ZitadelHttpError';
|
|
55
59
|
}
|
|
56
60
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ZitadelPort } from './executor.js';
|
|
2
|
+
export interface ZitadelProjectRolesOptions {
|
|
3
|
+
/** The ZITADEL instance's base URL. */
|
|
4
|
+
url: string;
|
|
5
|
+
/** Bearer token allowed to manage projects and user grants. */
|
|
6
|
+
token: string;
|
|
7
|
+
fetchImpl?: typeof fetch;
|
|
8
|
+
instanceHost?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The real {@link ZitadelPort.ensureProjectRole} and
|
|
12
|
+
* {@link ZitadelPort.grantProjectRoles}. `@wtfalch/providers` has no
|
|
13
|
+
* project-role API, so this speaks ZITADEL's v1 management API directly, with
|
|
14
|
+
* the request shape `createZitadelAdminMinter` uses. The calls follow
|
|
15
|
+
* ZITADEL's published API reference and have not been run against a live
|
|
16
|
+
* instance.
|
|
17
|
+
*
|
|
18
|
+
* 1. `POST /management/v1/projects/{id}/roles` adds the role; a 409 means it
|
|
19
|
+
* exists already.
|
|
20
|
+
* 2. `POST /management/v1/users/grants/_search` finds the user's grant on the
|
|
21
|
+
* project; `PUT /management/v1/users/{id}/grants/{grantId}` adds the
|
|
22
|
+
* missing roles to it, or `POST /management/v1/users/{id}/grants` creates
|
|
23
|
+
* the grant.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createZitadelProjectRoles(options: ZitadelProjectRolesOptions): Pick<ZitadelPort, 'ensureProjectRole' | 'grantProjectRoles'>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ZitadelHttpError } from './zitadel-machine-user.js';
|
|
2
|
+
/**
|
|
3
|
+
* The real {@link ZitadelPort.ensureProjectRole} and
|
|
4
|
+
* {@link ZitadelPort.grantProjectRoles}. `@wtfalch/providers` has no
|
|
5
|
+
* project-role API, so this speaks ZITADEL's v1 management API directly, with
|
|
6
|
+
* the request shape `createZitadelAdminMinter` uses. The calls follow
|
|
7
|
+
* ZITADEL's published API reference and have not been run against a live
|
|
8
|
+
* instance.
|
|
9
|
+
*
|
|
10
|
+
* 1. `POST /management/v1/projects/{id}/roles` adds the role; a 409 means it
|
|
11
|
+
* exists already.
|
|
12
|
+
* 2. `POST /management/v1/users/grants/_search` finds the user's grant on the
|
|
13
|
+
* project; `PUT /management/v1/users/{id}/grants/{grantId}` adds the
|
|
14
|
+
* missing roles to it, or `POST /management/v1/users/{id}/grants` creates
|
|
15
|
+
* the grant.
|
|
16
|
+
*/
|
|
17
|
+
export function createZitadelProjectRoles(options) {
|
|
18
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
19
|
+
async function call(method, path, orgId, body) {
|
|
20
|
+
const response = await fetchImpl(`${options.url}${path}`, {
|
|
21
|
+
method,
|
|
22
|
+
headers: {
|
|
23
|
+
authorization: `Bearer ${options.token}`,
|
|
24
|
+
'content-type': 'application/json',
|
|
25
|
+
'x-zitadel-orgid': orgId,
|
|
26
|
+
...(options.instanceHost
|
|
27
|
+
? {
|
|
28
|
+
'x-zitadel-instance-host': options.instanceHost,
|
|
29
|
+
'x-forwarded-host': options.instanceHost,
|
|
30
|
+
'x-forwarded-proto': 'https',
|
|
31
|
+
}
|
|
32
|
+
: {}),
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify(body),
|
|
35
|
+
});
|
|
36
|
+
const text = await response.text();
|
|
37
|
+
let data = {};
|
|
38
|
+
try {
|
|
39
|
+
data = text ? JSON.parse(text) : {};
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new ZitadelHttpError(response.status, `non-JSON response from ${path}`);
|
|
43
|
+
}
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new ZitadelHttpError(response.status, String(data.message ?? response.statusText), typeof data.code === 'number' ? data.code : undefined);
|
|
46
|
+
}
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
async ensureProjectRole(orgId, projectId, role) {
|
|
51
|
+
try {
|
|
52
|
+
await call('POST', `/management/v1/projects/${projectId}/roles`, orgId, {
|
|
53
|
+
roleKey: role.key,
|
|
54
|
+
displayName: role.displayName,
|
|
55
|
+
});
|
|
56
|
+
return { created: true };
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
// Only AlreadyExists (gRPC code 6, or its name in the message) means the
|
|
60
|
+
// role is there; any other 409 is a real failure.
|
|
61
|
+
if (e instanceof ZitadelHttpError &&
|
|
62
|
+
e.status === 409 &&
|
|
63
|
+
(e.code === 6 || /already.?exists/i.test(e.message))) {
|
|
64
|
+
return { created: false };
|
|
65
|
+
}
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
async grantProjectRoles(orgId, userId, projectId, roleKeys) {
|
|
70
|
+
// This project's own grant for this user: not one inherited through a
|
|
71
|
+
// project grant from another org. Paged, so a user with many grants
|
|
72
|
+
// cannot hide it.
|
|
73
|
+
const pageSize = 100;
|
|
74
|
+
let existing;
|
|
75
|
+
for (let offset = 0; !existing; offset += pageSize) {
|
|
76
|
+
const found = await call('POST', '/management/v1/users/grants/_search', orgId, {
|
|
77
|
+
query: { offset: String(offset), limit: pageSize },
|
|
78
|
+
queries: [{ userIdQuery: { userId } }, { projectIdQuery: { projectId } }],
|
|
79
|
+
});
|
|
80
|
+
const page = found.result ?? [];
|
|
81
|
+
existing = page.find((g) => (g.userId === undefined || g.userId === userId) &&
|
|
82
|
+
(g.projectId === undefined || g.projectId === projectId) &&
|
|
83
|
+
!g.projectGrantId);
|
|
84
|
+
if (page.length < pageSize)
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
if (!existing) {
|
|
88
|
+
await call('POST', `/management/v1/users/${userId}/grants`, orgId, {
|
|
89
|
+
projectId,
|
|
90
|
+
roleKeys,
|
|
91
|
+
});
|
|
92
|
+
return { changed: true };
|
|
93
|
+
}
|
|
94
|
+
const held = existing.roleKeys ?? [];
|
|
95
|
+
const missing = roleKeys.filter((k) => !held.includes(k));
|
|
96
|
+
if (missing.length === 0)
|
|
97
|
+
return { changed: false };
|
|
98
|
+
await call('PUT', `/management/v1/users/${userId}/grants/${existing.id}`, orgId, {
|
|
99
|
+
roleKeys: [...held, ...missing],
|
|
100
|
+
});
|
|
101
|
+
return { changed: true };
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
package/dist/store/postgres.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { and, asc, eq, sql } from 'drizzle-orm';
|
|
2
|
-
import { APP_ENTRY_KEY, DEFAULT_MAIL_APPS, DEFAULT_NO_REPLY_LOCAL_PART, validateBuyerIdentity, validateDomain, validateHosting, validateMail,
|
|
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';
|
|
@@ -99,7 +99,7 @@ function rowToOrg(row) {
|
|
|
99
99
|
adoptedApp: row.adoptedAppForm
|
|
100
100
|
? { form: row.adoptedAppForm }
|
|
101
101
|
: null,
|
|
102
|
-
|
|
102
|
+
owners: row.owners ?? [],
|
|
103
103
|
test: row.isTest,
|
|
104
104
|
};
|
|
105
105
|
}
|
|
@@ -142,9 +142,7 @@ function orgToRow(org) {
|
|
|
142
142
|
vatRegistered: org.buyer.vatRegistered,
|
|
143
143
|
bespokeAppRepo: org.bespokeAppRepo,
|
|
144
144
|
adoptedAppForm: org.adoptedApp?.form ?? null,
|
|
145
|
-
|
|
146
|
-
ownerGivenName: org.owner?.givenName ?? null,
|
|
147
|
-
ownerFamilyName: org.owner?.familyName ?? null,
|
|
145
|
+
owners: org.owners,
|
|
148
146
|
isTest: org.test,
|
|
149
147
|
};
|
|
150
148
|
}
|
|
@@ -173,17 +171,6 @@ function rowToBuyer(row) {
|
|
|
173
171
|
vatRegistered: row.vatRegistered,
|
|
174
172
|
};
|
|
175
173
|
}
|
|
176
|
-
/**
|
|
177
|
-
* Reassembles `DeclaredOrg.owner` from the row's three separate nullable
|
|
178
|
-
* columns -- present only when all three are, same "all parts or none"
|
|
179
|
-
* pattern {@link rowToBuyer} uses for the buyer's address.
|
|
180
|
-
*/
|
|
181
|
-
function rowToOwner(row) {
|
|
182
|
-
const { ownerEmail, ownerGivenName, ownerFamilyName } = row;
|
|
183
|
-
if (ownerEmail === null || ownerGivenName === null || ownerFamilyName === null)
|
|
184
|
-
return null;
|
|
185
|
-
return { email: ownerEmail, givenName: ownerGivenName, familyName: ownerFamilyName };
|
|
186
|
-
}
|
|
187
174
|
/**
|
|
188
175
|
* `DeclaredOrgStore` (`../store.ts`) on Postgres, for #18: create, read and
|
|
189
176
|
* edit a {@link DeclaredOrg}, and read and write the {@link AppliedState}
|
|
@@ -272,7 +259,7 @@ export function createPostgresDeclaredOrgStore(db) {
|
|
|
272
259
|
// Same place `requireValidHosting` checks hosting, on both `create` and
|
|
273
260
|
// the merged result of `update`.
|
|
274
261
|
function requireValidOwner(org) {
|
|
275
|
-
const reason =
|
|
262
|
+
const reason = validateOwners(org.owners);
|
|
276
263
|
if (reason)
|
|
277
264
|
throw new DeclaredOrgError(`owner invalid: ${reason}`);
|
|
278
265
|
}
|
package/dist/store/schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppEnvSource, HostingEntry, IssuerApp } from '../org.js';
|
|
1
|
+
import type { AppEnvSource, HostingEntry, IssuerApp, OwnerIdentity } from '../org.js';
|
|
2
2
|
import type { Plan } from '../plan.js';
|
|
3
3
|
import type { Checkpoint } from '../state.js';
|
|
4
4
|
/**
|
|
@@ -754,57 +754,25 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
|
|
|
754
754
|
identity: undefined;
|
|
755
755
|
generated: undefined;
|
|
756
756
|
}, {}, {}>;
|
|
757
|
-
|
|
758
|
-
name: "
|
|
757
|
+
owners: import("drizzle-orm/pg-core").PgColumn<{
|
|
758
|
+
name: "owners";
|
|
759
759
|
tableName: "foundry_declared_orgs";
|
|
760
|
-
dataType: "
|
|
761
|
-
columnType: "
|
|
762
|
-
data:
|
|
763
|
-
driverParam:
|
|
764
|
-
notNull:
|
|
765
|
-
hasDefault:
|
|
766
|
-
isPrimaryKey: false;
|
|
767
|
-
isAutoincrement: false;
|
|
768
|
-
hasRuntimeDefault: false;
|
|
769
|
-
enumValues: [string, ...string[]];
|
|
770
|
-
baseColumn: never;
|
|
771
|
-
identity: undefined;
|
|
772
|
-
generated: undefined;
|
|
773
|
-
}, {}, {}>;
|
|
774
|
-
ownerGivenName: import("drizzle-orm/pg-core").PgColumn<{
|
|
775
|
-
name: "owner_given_name";
|
|
776
|
-
tableName: "foundry_declared_orgs";
|
|
777
|
-
dataType: "string";
|
|
778
|
-
columnType: "PgText";
|
|
779
|
-
data: string;
|
|
780
|
-
driverParam: string;
|
|
781
|
-
notNull: false;
|
|
782
|
-
hasDefault: false;
|
|
783
|
-
isPrimaryKey: false;
|
|
784
|
-
isAutoincrement: false;
|
|
785
|
-
hasRuntimeDefault: false;
|
|
786
|
-
enumValues: [string, ...string[]];
|
|
787
|
-
baseColumn: never;
|
|
788
|
-
identity: undefined;
|
|
789
|
-
generated: undefined;
|
|
790
|
-
}, {}, {}>;
|
|
791
|
-
ownerFamilyName: import("drizzle-orm/pg-core").PgColumn<{
|
|
792
|
-
name: "owner_family_name";
|
|
793
|
-
tableName: "foundry_declared_orgs";
|
|
794
|
-
dataType: "string";
|
|
795
|
-
columnType: "PgText";
|
|
796
|
-
data: string;
|
|
797
|
-
driverParam: string;
|
|
798
|
-
notNull: false;
|
|
799
|
-
hasDefault: false;
|
|
760
|
+
dataType: "json";
|
|
761
|
+
columnType: "PgJsonb";
|
|
762
|
+
data: OwnerIdentity[];
|
|
763
|
+
driverParam: unknown;
|
|
764
|
+
notNull: true;
|
|
765
|
+
hasDefault: true;
|
|
800
766
|
isPrimaryKey: false;
|
|
801
767
|
isAutoincrement: false;
|
|
802
768
|
hasRuntimeDefault: false;
|
|
803
|
-
enumValues:
|
|
769
|
+
enumValues: undefined;
|
|
804
770
|
baseColumn: never;
|
|
805
771
|
identity: undefined;
|
|
806
772
|
generated: undefined;
|
|
807
|
-
}, {}, {
|
|
773
|
+
}, {}, {
|
|
774
|
+
$type: OwnerIdentity[];
|
|
775
|
+
}>;
|
|
808
776
|
isTest: import("drizzle-orm/pg-core").PgColumn<{
|
|
809
777
|
name: "is_test";
|
|
810
778
|
tableName: "foundry_declared_orgs";
|
package/dist/store/schema.js
CHANGED
|
@@ -27,7 +27,7 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
|
|
|
27
27
|
// MailConfig.roleMailboxes (org.ts, #230). Local parts of the role mailboxes
|
|
28
28
|
// pre-created on the org's mail domain; empty by default.
|
|
29
29
|
roleMailboxes: jsonb('role_mailboxes').notNull().default([]).$type(),
|
|
30
|
-
chatEnabled: boolean('chat_enabled').notNull().default(
|
|
30
|
+
chatEnabled: boolean('chat_enabled').notNull().default(true),
|
|
31
31
|
chatUsesDomain: boolean('chat_uses_domain').notNull().default(false),
|
|
32
32
|
callsEnabled: boolean('calls_enabled').notNull().default(false),
|
|
33
33
|
callsUsesDomain: boolean('calls_uses_domain').notNull().default(false),
|
|
@@ -78,13 +78,10 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
|
|
|
78
78
|
vatRegistered: boolean('vat_registered'),
|
|
79
79
|
bespokeAppRepo: text('bespoke_app_repo'),
|
|
80
80
|
adoptedAppForm: text('adopted_app_form'),
|
|
81
|
-
// OwnerIdentity (org.ts): the org's
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
ownerEmail: text('owner_email'),
|
|
86
|
-
ownerGivenName: text('owner_given_name'),
|
|
87
|
-
ownerFamilyName: text('owner_family_name'),
|
|
81
|
+
// OwnerIdentity[] (org.ts): the org's current owners, in order (the first
|
|
82
|
+
// is the primary). jsonb, like `hosting_entries`: an ordered list read and
|
|
83
|
+
// written whole with the org, never queried by owner.
|
|
84
|
+
owners: jsonb('owners').notNull().default([]).$type(),
|
|
88
85
|
// `test: boolean` (org.ts): only a test org can be destroyed. A brand new
|
|
89
86
|
// org defaults to real (false).
|
|
90
87
|
isTest: boolean('is_test').notNull().default(false),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtfalch/foundry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@wtfalch/audit": "0.6.0",
|
|
44
44
|
"@wtfalch/auth": "0.16.0",
|
|
45
|
-
"@wtfalch/email": "0.
|
|
45
|
+
"@wtfalch/email": "^0.17.0",
|
|
46
46
|
"@wtfalch/providers": "0.6.0",
|
|
47
47
|
"ssh2": "1.17.0",
|
|
48
48
|
"zod": "4.6.5"
|