@wtfalch/foundry 0.60.0 → 0.62.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.
Files changed (236) hide show
  1. package/dist/apps/cloudflare.d.ts +173 -0
  2. package/dist/apps/cloudflare.js +17 -0
  3. package/dist/apps/deploy.d.ts +116 -0
  4. package/dist/apps/deploy.js +791 -0
  5. package/dist/apps/github.d.ts +47 -0
  6. package/dist/apps/github.js +201 -0
  7. package/dist/apps/index.d.ts +9 -0
  8. package/dist/apps/index.js +9 -0
  9. package/dist/apps/migrations.d.ts +110 -0
  10. package/dist/apps/migrations.js +458 -0
  11. package/dist/audit/connector.d.ts +43 -0
  12. package/dist/audit/connector.js +22 -0
  13. package/dist/audit/evidence.d.ts +49 -0
  14. package/dist/audit/evidence.js +56 -0
  15. package/dist/audit/index.d.ts +9 -0
  16. package/dist/audit/index.js +9 -0
  17. package/dist/audit/migrations.d.ts +5 -0
  18. package/dist/audit/migrations.js +5 -0
  19. package/dist/audit/registry.d.ts +49 -0
  20. package/dist/audit/registry.js +44 -0
  21. package/dist/audit/seal.d.ts +49 -0
  22. package/dist/audit/seal.js +131 -0
  23. package/dist/audit/sealer-role.d.ts +33 -0
  24. package/dist/audit/sealer-role.js +56 -0
  25. package/dist/audit/signer.d.ts +40 -0
  26. package/dist/audit/signer.js +78 -0
  27. package/dist/audit/tick.d.ts +36 -0
  28. package/dist/audit/tick.js +87 -0
  29. package/dist/catalogue/copy-en.d.ts +1 -0
  30. package/dist/catalogue/copy-en.js +129 -0
  31. package/dist/catalogue/index.d.ts +9 -0
  32. package/dist/catalogue/index.js +21 -0
  33. package/dist/catalogue/integrations.d.ts +4 -0
  34. package/dist/catalogue/integrations.js +104 -0
  35. package/dist/catalogue/systems.d.ts +7 -0
  36. package/dist/catalogue/systems.js +31 -0
  37. package/dist/catalogue/types.d.ts +110 -0
  38. package/dist/catalogue/types.js +1 -0
  39. package/dist/entry-rules.d.ts +53 -0
  40. package/dist/entry-rules.js +71 -0
  41. package/dist/hosted-label.d.ts +13 -0
  42. package/dist/hosted-label.js +81 -0
  43. package/dist/index.d.ts +14 -5
  44. package/dist/index.js +7 -3
  45. package/dist/migrations/0062_hosting_github.sql +6 -0
  46. package/dist/migrations/0063_boule_apps.sql +7 -0
  47. package/dist/migrations/0064_requested_changes_boule_apps.sql +6 -0
  48. package/dist/migrations/0065_native_build_runs.sql +32 -0
  49. package/dist/migrations/0066_org_kosmos.sql +10 -0
  50. package/dist/modules/ai.d.ts +3 -0
  51. package/dist/modules/ai.js +64 -0
  52. package/dist/modules/assistants.d.ts +3 -0
  53. package/dist/modules/assistants.js +72 -0
  54. package/dist/modules/audit-ledger.d.ts +29 -0
  55. package/dist/modules/audit-ledger.js +49 -0
  56. package/dist/modules/audit.d.ts +7 -0
  57. package/dist/modules/audit.js +44 -0
  58. package/dist/modules/database.d.ts +23 -0
  59. package/dist/modules/database.js +189 -0
  60. package/dist/modules/email.d.ts +3 -0
  61. package/dist/modules/email.js +18 -0
  62. package/dist/modules/entry-parts.d.ts +23 -0
  63. package/dist/modules/entry-parts.js +63 -0
  64. package/dist/modules/files.d.ts +3 -0
  65. package/dist/modules/files.js +64 -0
  66. package/dist/modules/index.d.ts +74 -0
  67. package/dist/modules/index.js +774 -0
  68. package/dist/modules/jobs.d.ts +3 -0
  69. package/dist/modules/jobs.js +23 -0
  70. package/dist/modules/organisations.d.ts +3 -0
  71. package/dist/modules/organisations.js +20 -0
  72. package/dist/modules/outputs.d.ts +9 -0
  73. package/dist/modules/outputs.js +38 -0
  74. package/dist/modules/sign-in.d.ts +3 -0
  75. package/dist/modules/sign-in.js +197 -0
  76. package/dist/modules/signed-records.d.ts +3 -0
  77. package/dist/modules/signed-records.js +32 -0
  78. package/dist/modules/types.d.ts +194 -0
  79. package/dist/modules/types.js +1 -0
  80. package/dist/modules/workspaces.d.ts +3 -0
  81. package/dist/modules/workspaces.js +75 -0
  82. package/dist/native-build.d.ts +70 -0
  83. package/dist/native-build.js +92 -0
  84. package/dist/org.d.ts +184 -24
  85. package/dist/org.js +444 -49
  86. package/dist/plan/agora.d.ts +1 -1
  87. package/dist/plan/agora.js +4 -0
  88. package/dist/plan/apps-db-route.d.ts +90 -0
  89. package/dist/plan/apps-db-route.js +168 -0
  90. package/dist/plan/apps-zones.d.ts +56 -0
  91. package/dist/plan/apps-zones.js +118 -0
  92. package/dist/plan/archon-blue-green.js +11 -1
  93. package/dist/plan/blue-green-apply.d.ts +37 -1
  94. package/dist/plan/blue-green-apply.js +55 -15
  95. package/dist/plan/blue-green.d.ts +60 -8
  96. package/dist/plan/blue-green.js +115 -10
  97. package/dist/plan/boule.d.ts +23 -1
  98. package/dist/plan/boule.js +35 -2
  99. package/dist/plan/broker-entries.d.ts +28 -3
  100. package/dist/plan/broker-entries.js +74 -3
  101. package/dist/plan/broker-registrar.d.ts +168 -0
  102. package/dist/plan/broker-registrar.js +440 -0
  103. package/dist/plan/broker-workspaces-database.d.ts +48 -0
  104. package/dist/plan/broker-workspaces-database.js +87 -0
  105. package/dist/plan/deploy-health.js +9 -2
  106. package/dist/plan/desktop-callback.js +15 -7
  107. package/dist/plan/diff.d.ts +8 -2
  108. package/dist/plan/diff.js +552 -104
  109. package/dist/plan/edge-access.d.ts +6 -0
  110. package/dist/plan/edge-access.js +1 -0
  111. package/dist/plan/edge-dns.d.ts +6 -0
  112. package/dist/plan/edge-dns.js +9 -7
  113. package/dist/plan/entry-ai-key.d.ts +1 -1
  114. package/dist/plan/entry-ai-key.js +2 -2
  115. package/dist/plan/entry-audit.d.ts +58 -0
  116. package/dist/plan/entry-audit.js +122 -0
  117. package/dist/plan/entry-customer-grants.d.ts +75 -0
  118. package/dist/plan/entry-customer-grants.js +154 -0
  119. package/dist/plan/entry-edge-key.d.ts +26 -1
  120. package/dist/plan/entry-edge-key.js +42 -3
  121. package/dist/plan/entry-jobs.d.ts +64 -0
  122. package/dist/plan/entry-jobs.js +92 -0
  123. package/dist/plan/entry-repo-key-apply.d.ts +55 -0
  124. package/dist/plan/entry-repo-key-apply.js +185 -0
  125. package/dist/plan/entry-repo-key.d.ts +92 -0
  126. package/dist/plan/entry-repo-key.js +235 -0
  127. package/dist/plan/entry-workspaces.d.ts +55 -0
  128. package/dist/plan/entry-workspaces.js +90 -0
  129. package/dist/plan/executor.d.ts +231 -19
  130. package/dist/plan/executor.js +1648 -129
  131. package/dist/plan/gates.d.ts +1 -1
  132. package/dist/plan/gates.js +62 -9
  133. package/dist/plan/github-deploy-keys.d.ts +78 -0
  134. package/dist/plan/github-deploy-keys.js +305 -0
  135. package/dist/plan/github-source.d.ts +53 -0
  136. package/dist/plan/github-source.js +140 -0
  137. package/dist/plan/held-key-refs.d.ts +12 -0
  138. package/dist/plan/held-key-refs.js +12 -0
  139. package/dist/plan/hostnames.d.ts +9 -0
  140. package/dist/plan/hostnames.js +28 -1
  141. package/dist/plan/index.d.ts +9 -0
  142. package/dist/plan/index.js +6 -0
  143. package/dist/plan/jobs-scheduler-http.js +3 -0
  144. package/dist/plan/keys-registrar-http.d.ts +32 -0
  145. package/dist/plan/keys-registrar-http.js +105 -0
  146. package/dist/plan/keys-registrar.d.ts +67 -0
  147. package/dist/plan/keys-registrar.js +103 -0
  148. package/dist/plan/kosmos.d.ts +56 -0
  149. package/dist/plan/kosmos.js +84 -0
  150. package/dist/plan/live.d.ts +1 -1
  151. package/dist/plan/locked-hostnames.d.ts +2 -2
  152. package/dist/plan/locked-hostnames.js +7 -4
  153. package/dist/plan/login-host.js +11 -5
  154. package/dist/plan/machine-paths.d.ts +36 -0
  155. package/dist/plan/machine-paths.js +137 -0
  156. package/dist/plan/mcp.d.ts +1 -1
  157. package/dist/plan/mcp.js +49 -17
  158. package/dist/plan/org-apps-admin.d.ts +108 -0
  159. package/dist/plan/org-apps-admin.js +434 -0
  160. package/dist/plan/org-names.d.ts +3 -3
  161. package/dist/plan/org-names.js +15 -2
  162. package/dist/plan/org-postgres-migrate.js +1 -0
  163. package/dist/plan/own-diff.d.ts +23 -0
  164. package/dist/plan/own-diff.js +53 -0
  165. package/dist/plan/own-hostnames.d.ts +2 -0
  166. package/dist/plan/own-hostnames.js +1 -0
  167. package/dist/plan/own-jobs-route.d.ts +47 -0
  168. package/dist/plan/own-jobs-route.js +91 -0
  169. package/dist/plan/own-limits.d.ts +6 -0
  170. package/dist/plan/own-limits.js +1 -0
  171. package/dist/plan/own-services.d.ts +4 -0
  172. package/dist/plan/own-services.js +4 -0
  173. package/dist/plan/plan-apply.d.ts +39 -0
  174. package/dist/plan/plan-apply.js +163 -9
  175. package/dist/plan/platform-diff.d.ts +2 -2
  176. package/dist/plan/platform-diff.js +7 -2
  177. package/dist/plan/platform-org.d.ts +30 -0
  178. package/dist/plan/platform-org.js +12 -1
  179. package/dist/plan/platform-providers.d.ts +2 -0
  180. package/dist/plan/platform-providers.js +13 -2
  181. package/dist/plan/post-logout-uris.d.ts +1 -1
  182. package/dist/plan/post-logout-uris.js +12 -3
  183. package/dist/plan/private-pages.d.ts +26 -0
  184. package/dist/plan/private-pages.js +39 -0
  185. package/dist/plan/providers-readers.js +20 -1
  186. package/dist/plan/proxy-labels.d.ts +12 -1
  187. package/dist/plan/proxy-labels.js +32 -2
  188. package/dist/plan/release-host.d.ts +6 -2
  189. package/dist/plan/release-host.js +36 -8
  190. package/dist/plan/stage-env.d.ts +19 -1
  191. package/dist/plan/stage-env.js +25 -1
  192. package/dist/plan/stage-guards.d.ts +20 -0
  193. package/dist/plan/stage-guards.js +24 -0
  194. package/dist/plan/stage-isolation.d.ts +24 -0
  195. package/dist/plan/stage-isolation.js +29 -1
  196. package/dist/plan/worker-app-database.d.ts +46 -42
  197. package/dist/plan/worker-app-database.js +150 -153
  198. package/dist/plan/worker-app.d.ts +5 -65
  199. package/dist/plan/worker-app.js +28 -105
  200. package/dist/plan/worker.d.ts +2 -2
  201. package/dist/plan/worker.js +2 -1
  202. package/dist/plan/workspace-client.d.ts +25 -0
  203. package/dist/plan/workspace-client.js +48 -0
  204. package/dist/plan/workspace-sign-in.d.ts +55 -0
  205. package/dist/plan/workspace-sign-in.js +273 -0
  206. package/dist/plan/workspace-worker.d.ts +4 -0
  207. package/dist/plan/workspace-worker.js +13 -0
  208. package/dist/plan/workspace.d.ts +101 -0
  209. package/dist/plan/workspace.js +305 -0
  210. package/dist/release/decide.d.ts +30 -1
  211. package/dist/release/decide.js +73 -11
  212. package/dist/release/health.d.ts +8 -3
  213. package/dist/release/health.js +58 -5
  214. package/dist/release/migration-compatibility.d.ts +66 -0
  215. package/dist/release/migration-compatibility.js +340 -0
  216. package/dist/release/run.d.ts +2 -5
  217. package/dist/release/run.js +4 -0
  218. package/dist/release/state.d.ts +3 -1
  219. package/dist/release/state.js +6 -0
  220. package/dist/release/types.d.ts +20 -0
  221. package/dist/requested-changes.d.ts +11 -2
  222. package/dist/requested-changes.js +13 -1
  223. package/dist/service-labels.d.ts +4 -0
  224. package/dist/service-labels.js +11 -0
  225. package/dist/slug.d.ts +2 -0
  226. package/dist/slug.js +20 -7
  227. package/dist/store/postgres.js +25 -2
  228. package/dist/store/schema.d.ts +73 -1
  229. package/dist/store/schema.js +10 -0
  230. package/dist/worker-app-config.d.ts +1 -11
  231. package/dist/worker-app-config.js +4 -17
  232. package/dist/worker-app-migrations.d.ts +105 -0
  233. package/dist/worker-app-migrations.js +166 -0
  234. package/dist/worker-app-upload.d.ts +103 -0
  235. package/dist/worker-app-upload.js +128 -0
  236. package/package.json +19 -2
@@ -0,0 +1,189 @@
1
+ import { WORKER_APP_APPEND_ONLY_MAX, WORKER_APP_FUNCTION_SIGNATURE, WORKER_APP_GRANTS_MAX, WORKER_APP_TABLE_NAME, } from '../entry-rules.js';
2
+ import { clone, list, omit, problem, withWorkerApp } from './entry-parts.js';
3
+ const ADDRESS_HELP = 'From your Postgres provider. The owner can change tables; the app address is a separate user with less access. Use a different database for the workspace than for live.';
4
+ const ADDRESS_MAX = 2000;
5
+ const COPY_WORDS = { workspace: 'the workspace', live: 'live' };
6
+ const ADDRESS_KEYS = {
7
+ workspace: { owner: 'workspaceOwnerUrl', app: 'workspaceAppUrl' },
8
+ live: { owner: 'liveOwnerUrl', app: 'liveAppUrl' },
9
+ };
10
+ const addressLabel = (copy, role) => `Database address for ${COPY_WORDS[copy]} (${role})`;
11
+ const addressField = (copy, role) => ({
12
+ key: ADDRESS_KEYS[copy][role],
13
+ label: addressLabel(copy, role),
14
+ help: ADDRESS_HELP,
15
+ kind: 'secret',
16
+ required: true,
17
+ max: ADDRESS_MAX,
18
+ runtimes: ['worker'],
19
+ });
20
+ /** The user and the database (host, port, name) of a Postgres address, or null when it is not one. */
21
+ function parseAddress(value) {
22
+ let url;
23
+ try {
24
+ url = new URL(value);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ if (url.protocol !== 'postgres:' && url.protocol !== 'postgresql:')
30
+ return null;
31
+ const name = url.pathname.replace(/^\//, '');
32
+ if (url.username === '' || url.hostname === '' || name === '' || name.includes('/'))
33
+ return null;
34
+ let user;
35
+ try {
36
+ user = decodeURIComponent(url.username);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ return { user, place: `${url.hostname.toLowerCase()}:${url.port || '5432'}/${name}` };
42
+ }
43
+ /** One copy's addresses read and checked, with the database they reach; null when neither is given. */
44
+ function readCopy(values, copy) {
45
+ const keys = ADDRESS_KEYS[copy];
46
+ const owner = values[keys.owner];
47
+ const app = values[keys.app];
48
+ const given = (value) => typeof value === 'string' && value !== '';
49
+ if (!given(owner) && !given(app))
50
+ return null;
51
+ if (!given(owner)) {
52
+ return problem(keys.owner, `"${addressLabel(copy, 'owner')}" is needed with the app address.`);
53
+ }
54
+ if (!given(app)) {
55
+ return problem(keys.app, `"${addressLabel(copy, 'app')}" is needed with the owner address.`);
56
+ }
57
+ const shape = (role) => `"${addressLabel(copy, role)}" is not a Postgres address. It starts with postgres:// or postgresql:// and names a user, a server and a database. Copy it again from your Postgres provider.`;
58
+ const ownerParts = parseAddress(owner);
59
+ if (!ownerParts)
60
+ return problem(keys.owner, shape('owner'));
61
+ const appParts = parseAddress(app);
62
+ if (!appParts)
63
+ return problem(keys.app, shape('app'));
64
+ if (ownerParts.user === appParts.user) {
65
+ return problem(keys.app, `The owner and app addresses for ${COPY_WORDS[copy]} use the same user. The app needs a separate user with less access.`);
66
+ }
67
+ if (ownerParts.place !== appParts.place) {
68
+ return problem(keys.app, `The app address for ${COPY_WORDS[copy]} reaches another database than the owner address. Both must reach the same database.`);
69
+ }
70
+ return { addresses: { ownerUrl: owner, runtimeUrl: app }, place: ownerParts.place };
71
+ }
72
+ /**
73
+ * The owner and runtime addresses of one copy's Postgres, for `applyWorkerAppMigrations` and `deployWorkerApp`
74
+ * (`database.url = runtimeUrl`). Null when the values give neither address of that copy; a problem, in plain
75
+ * words that never repeat an address, when one is missing, is not a Postgres address, uses the owner's user,
76
+ * or reaches another database than the owner.
77
+ */
78
+ export function databaseAddressesOf(values, copy) {
79
+ const read = readCopy(values, copy);
80
+ if (read === null || 'message' in read)
81
+ return read;
82
+ return read.addresses;
83
+ }
84
+ /**
85
+ * A Postgres database for the app. A server app: `entry.database`. A Worker app: `workerApp.database`, reached
86
+ * through Hyperdrive, with its append-only tables and function grants. `migrationsSha256` is not a field: it
87
+ * comes from the build (`build.json`) and is kept as the entry holds it. A Worker app uses the company's own
88
+ * Postgres: an owner and an app address per copy, `secret` fields the caller holds and delivers itself. They
89
+ * never go into the entry, so `fromEntry` (and `modulesOf`) never gives them back.
90
+ */
91
+ export const database = {
92
+ view: {
93
+ id: 'database',
94
+ name: 'Database',
95
+ line: "A database for the app's own records, kept safe with daily backups.",
96
+ category: 'Data',
97
+ icon: 'database',
98
+ runtimes: ['server', 'worker'],
99
+ requires: [],
100
+ availability: 'ready',
101
+ fields: [
102
+ addressField('workspace', 'owner'),
103
+ addressField('workspace', 'app'),
104
+ addressField('live', 'owner'),
105
+ addressField('live', 'app'),
106
+ {
107
+ key: 'appendOnly',
108
+ label: 'Tables that keep every row',
109
+ help: 'The app can add and read rows in these tables but never change or delete them, as for an audit trail. Only for apps that run as a Worker app.',
110
+ kind: 'list',
111
+ advanced: true,
112
+ pattern: WORKER_APP_TABLE_NAME.source,
113
+ max: WORKER_APP_APPEND_ONLY_MAX,
114
+ },
115
+ {
116
+ key: 'grants',
117
+ label: 'Database functions the app may run',
118
+ help: 'Functions your database setup locks down, written as name(type, ...), such as audit_chain_tail(). Only for apps that run as a Worker app.',
119
+ kind: 'list',
120
+ advanced: true,
121
+ pattern: WORKER_APP_FUNCTION_SIGNATURE.source,
122
+ max: WORKER_APP_GRANTS_MAX,
123
+ },
124
+ ],
125
+ adds: [
126
+ {
127
+ name: 'A Postgres database',
128
+ hint: 'Only this app reaches it. The app gets its address itself.',
129
+ },
130
+ { name: 'Backups', hint: 'A copy is made every day.' },
131
+ ],
132
+ },
133
+ check(values, _modules, ctx) {
134
+ if (ctx.runtime === 'server') {
135
+ for (const key of ['appendOnly', 'grants']) {
136
+ if (list(values, key) !== undefined) {
137
+ return problem(key, 'Tables that keep every row and database functions can only be set for an app that runs as a Worker app.');
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+ const workspace = readCopy(values, 'workspace');
143
+ if (workspace && 'message' in workspace)
144
+ return workspace;
145
+ const live = readCopy(values, 'live');
146
+ if (live && 'message' in live)
147
+ return live;
148
+ if (workspace && live && workspace.place === live.place) {
149
+ return problem(ADDRESS_KEYS.workspace.owner, 'The workspace and live use the same database. Use a different database for the workspace than for live.');
150
+ }
151
+ return null;
152
+ },
153
+ toEntry(entry, values, ctx) {
154
+ if (ctx.runtime === 'server') {
155
+ const next = clone(entry);
156
+ next.database = values !== null;
157
+ return next;
158
+ }
159
+ return withWorkerApp(entry, (app) => {
160
+ if (values === null)
161
+ return omit(app, 'database');
162
+ const next = {};
163
+ const appendOnly = list(values, 'appendOnly');
164
+ const grants = list(values, 'grants');
165
+ if (appendOnly)
166
+ next.appendOnly = appendOnly;
167
+ if (grants)
168
+ next.grants = grants;
169
+ const pinned = app.database?.migrationsSha256;
170
+ if (pinned !== undefined)
171
+ next.migrationsSha256 = pinned;
172
+ return { ...app, database: next };
173
+ });
174
+ },
175
+ fromEntry(entry) {
176
+ if (entry.workerApp) {
177
+ const db = entry.workerApp.database;
178
+ if (!db)
179
+ return null;
180
+ const values = {};
181
+ if (db.appendOnly?.length)
182
+ values.appendOnly = [...db.appendOnly];
183
+ if (db.grants?.length)
184
+ values.grants = [...db.grants];
185
+ return values;
186
+ }
187
+ return entry.database ? {} : null;
188
+ },
189
+ };
@@ -0,0 +1,3 @@
1
+ import type { AppModule } from './types.js';
2
+ /** `uses.mail`: the app sends mail from the company's mail domain. */
3
+ export declare const email: AppModule;
@@ -0,0 +1,18 @@
1
+ import { setUse } from './entry-parts.js';
2
+ /** `uses.mail`: the app sends mail from the company's mail domain. */
3
+ export const email = {
4
+ view: {
5
+ id: 'email',
6
+ name: 'Email',
7
+ line: "The app sends email from your company's address, such as receipts and notices.",
8
+ category: 'Messages',
9
+ icon: 'mail',
10
+ runtimes: ['server'],
11
+ requires: [],
12
+ availability: 'ready',
13
+ fields: [],
14
+ adds: [{ name: 'Sending email', hint: "From your company's own mail address." }],
15
+ },
16
+ toEntry: (entry, values) => setUse(entry, 'mail', values === null ? undefined : true),
17
+ fromEntry: (entry) => (entry.uses?.mail === true ? {} : null),
18
+ };
@@ -0,0 +1,23 @@
1
+ import type { EntryAuth, HostingEntry, HostingEntryUses } from '../org.js';
2
+ import type { WorkerAppConfig } from '../worker-app-config.js';
3
+ import type { ModuleProblem, ModuleValue, ModuleValues } from './types.js';
4
+ /** Helpers the modules share to change one part of an entry without touching the caller's copy. */
5
+ export declare const clone: <T>(value: T) => T;
6
+ /** A copy of `value` without `keys`. */
7
+ export declare function omit<T extends object, K extends keyof T>(value: T, ...keys: K[]): Omit<T, K>;
8
+ export declare const isProblem: (value: HostingEntry | ModuleProblem) => value is ModuleProblem;
9
+ /** The entry with `uses` replaced; an empty `uses` is left out. */
10
+ export declare function withUses(entry: HostingEntry, uses: HostingEntryUses): HostingEntry;
11
+ /** The entry with `uses[key]` set, or removed when `value` is undefined. */
12
+ export declare function setUse<K extends keyof HostingEntryUses>(entry: HostingEntry, key: K, value: HostingEntryUses[K] | undefined): HostingEntry;
13
+ /** The entry with `uses.auth[key]` changed. Without `uses.auth` the entry comes back unchanged. */
14
+ export declare function setAuthPart<K extends keyof EntryAuth>(entry: HostingEntry, key: K, value: EntryAuth[K] | undefined): HostingEntry;
15
+ /** The entry with its `workerApp` replaced by `change`'s result. Without a `workerApp` the entry comes back unchanged. */
16
+ export declare function withWorkerApp(entry: HostingEntry, change: (app: WorkerAppConfig) => WorkerAppConfig): HostingEntry;
17
+ /** A text value, or undefined for an absent or empty one. */
18
+ export declare function text(values: ModuleValues, key: string): string | undefined;
19
+ /** A list value, or undefined for an absent or empty one. */
20
+ export declare function list(values: ModuleValues, key: string): string[] | undefined;
21
+ /** `values` without the keys whose value is undefined. */
22
+ export declare function defined(values: Record<string, ModuleValue | undefined>): ModuleValues;
23
+ export declare const problem: (field: string | null, message: string) => ModuleProblem;
@@ -0,0 +1,63 @@
1
+ /** Helpers the modules share to change one part of an entry without touching the caller's copy. */
2
+ export const clone = (value) => structuredClone(value);
3
+ /** A copy of `value` without `keys`. */
4
+ export function omit(value, ...keys) {
5
+ const drop = new Set(keys);
6
+ return Object.fromEntries(Object.entries(clone(value)).filter(([key]) => !drop.has(key)));
7
+ }
8
+ export const isProblem = (value) => 'message' in value && typeof value.message === 'string';
9
+ /** The entry with `uses` replaced; an empty `uses` is left out. */
10
+ export function withUses(entry, uses) {
11
+ if (Object.keys(uses).length === 0)
12
+ return omit(entry, 'uses');
13
+ return { ...clone(entry), uses };
14
+ }
15
+ /** The entry with `uses[key]` set, or removed when `value` is undefined. */
16
+ export function setUse(entry, key, value) {
17
+ const uses = clone(entry.uses ?? {});
18
+ if (value === undefined)
19
+ delete uses[key];
20
+ else
21
+ uses[key] = value;
22
+ return withUses(entry, uses);
23
+ }
24
+ /** The entry with `uses.auth[key]` changed. Without `uses.auth` the entry comes back unchanged. */
25
+ export function setAuthPart(entry, key, value) {
26
+ const auth = entry.uses?.auth;
27
+ if (!auth)
28
+ return clone(entry);
29
+ const next = clone(auth);
30
+ if (value === undefined)
31
+ delete next[key];
32
+ else
33
+ next[key] = value;
34
+ return setUse(entry, 'auth', next);
35
+ }
36
+ /** The entry with its `workerApp` replaced by `change`'s result. Without a `workerApp` the entry comes back unchanged. */
37
+ export function withWorkerApp(entry, change) {
38
+ if (!entry.workerApp)
39
+ return clone(entry);
40
+ return { ...clone(entry), workerApp: change(clone(entry.workerApp)) };
41
+ }
42
+ /** A text value, or undefined for an absent or empty one. */
43
+ export function text(values, key) {
44
+ const value = values[key];
45
+ return typeof value === 'string' && value !== '' ? value : undefined;
46
+ }
47
+ /** A list value, or undefined for an absent or empty one. */
48
+ export function list(values, key) {
49
+ const value = values[key];
50
+ return Array.isArray(value) && value.length > 0 ? [...value] : undefined;
51
+ }
52
+ /** `values` without the keys whose value is undefined. */
53
+ export function defined(values) {
54
+ const out = {};
55
+ for (const [key, value] of Object.entries(values))
56
+ if (value !== undefined)
57
+ out[key] = value;
58
+ return out;
59
+ }
60
+ export const problem = (field, message) => ({
61
+ field,
62
+ message,
63
+ });
@@ -0,0 +1,3 @@
1
+ import type { AppModule } from './types.js';
2
+ /** `uses.files`: the app keeps files (uploads, documents) in folders of its own. */
3
+ export declare const files: AppModule;
@@ -0,0 +1,64 @@
1
+ import { FILES_COLLECTION_NAME, FILES_ENTRY_PERMISSIONS } from '../entry-rules.js';
2
+ import { list, setUse } from './entry-parts.js';
3
+ /** `uses.files`: the app keeps files (uploads, documents) in folders of its own. */
4
+ export const files = {
5
+ view: {
6
+ id: 'files',
7
+ name: 'Files',
8
+ line: 'The app keeps files people upload, such as documents and pictures.',
9
+ category: 'Data',
10
+ icon: 'folder',
11
+ runtimes: ['server'],
12
+ requires: [],
13
+ availability: 'ready',
14
+ fields: [
15
+ {
16
+ key: 'collections',
17
+ label: 'Folders',
18
+ help: 'A name for each kind of file the app keeps, in capital letters, such as INVOICES or PHOTOS.',
19
+ kind: 'list',
20
+ required: true,
21
+ pattern: FILES_COLLECTION_NAME.source,
22
+ },
23
+ {
24
+ key: 'shareHost',
25
+ label: 'Share links on your own address',
26
+ help: 'Links to shared files start with share. and your domain. Needs your own domain.',
27
+ kind: 'switch',
28
+ advanced: true,
29
+ default: false,
30
+ },
31
+ {
32
+ key: 'permissions',
33
+ label: 'What the app may do with files',
34
+ help: 'Leave it empty to allow all of them.',
35
+ kind: 'list',
36
+ advanced: true,
37
+ choices: FILES_ENTRY_PERMISSIONS.map((value) => ({ value, label: value })),
38
+ },
39
+ ],
40
+ adds: [{ name: 'File storage', hint: 'Only this app reaches its folders.' }],
41
+ },
42
+ toEntry(entry, values) {
43
+ if (values === null)
44
+ return setUse(entry, 'files', undefined);
45
+ const next = { collections: list(values, 'collections') ?? [] };
46
+ const permissions = list(values, 'permissions');
47
+ if (permissions)
48
+ next.permissions = permissions;
49
+ if (values.shareHost === true)
50
+ next.shareHost = true;
51
+ return setUse(entry, 'files', next);
52
+ },
53
+ fromEntry(entry) {
54
+ const used = entry.uses?.files;
55
+ if (!used)
56
+ return null;
57
+ const values = { collections: [...used.collections] };
58
+ if (used.permissions?.length)
59
+ values.permissions = [...used.permissions];
60
+ if (used.shareHost !== undefined)
61
+ values.shareHost = used.shareHost;
62
+ return values;
63
+ },
64
+ };
@@ -0,0 +1,74 @@
1
+ import type { HostingEntry } from '../org.js';
2
+ import type { AppManifest, AppModule, AppModules, ManifestAnswer, ModuleContext, ModuleRefusal, ModuleView } from './types.js';
3
+ export type * from './types.js';
4
+ export { type DatabaseAddresses, type DatabaseCopy, databaseAddressesOf } from './database.js';
5
+ /**
6
+ * Every module, in the order they are applied: a module comes after the ones it needs, so sign-in sets
7
+ * `uses.auth` before help for AI assistants and workspaces add to it.
8
+ */
9
+ export declare const MODULES: readonly AppModule[];
10
+ /** The views of every module, JSON-safe, for Boule to render. */
11
+ export declare function modulesCatalogue(): ModuleView[];
12
+ /**
13
+ * Null when the modules can be turned on together for an app on `ctx.runtime`; else the first problem, in
14
+ * words a company person can act on.
15
+ */
16
+ export declare function checkModules(modules: AppModules, ctx: ModuleContext): ModuleRefusal | null;
17
+ export type ApplyModulesResult = {
18
+ ok: true;
19
+ entry: HostingEntry;
20
+ } | ({
21
+ ok: false;
22
+ } & ModuleRefusal);
23
+ /**
24
+ * `entry` with each module in `modules` set and every part an absent module owns removed. The rest of the
25
+ * entry (its build, address, env) is kept as it is. The caller still validates the whole company
26
+ * (`validateHosting`).
27
+ */
28
+ export declare function applyModules(entry: HostingEntry, modules: AppModules, ctx: ModuleContext): ApplyModulesResult;
29
+ /** The modules `entry` has, with their values: the inverse of {@link applyModules}. */
30
+ export declare function modulesOf(entry: HostingEntry): AppModules;
31
+ export type ParseAppManifestResult = {
32
+ ok: true;
33
+ /** Always v2: a v1 file is read as v2 with its fields mapped. */
34
+ manifest: AppManifest;
35
+ /** The version the file itself was written in. */
36
+ from: 1 | 2;
37
+ } | {
38
+ ok: false;
39
+ message: string;
40
+ };
41
+ /** `company-app.json` (v1 or v2) read and checked: what the app's code asks for. */
42
+ export declare function parseAppManifest(json: unknown): ParseAppManifestResult;
43
+ export interface ManifestProblem {
44
+ /** The value's key, or null for a problem between them. */
45
+ key: string | null;
46
+ message: string;
47
+ }
48
+ /** What an install of the app is made from. Asked secrets are in `secrets`; hold them, never log them. */
49
+ export interface ManifestInstall {
50
+ /** The modules with the answers that fill module fields applied. */
51
+ modules: AppModules;
52
+ /** Environment name -> plain text for the app: defaults and answers. A list is its items joined by ",". */
53
+ plain: Record<string, string>;
54
+ /** Environment name -> a secret the company typed. */
55
+ secrets: Record<string, string>;
56
+ /** Environment name -> how the platform makes it. */
57
+ generate: Record<string, 'token' | 'hex' | 'uuid'>;
58
+ /** Environment name -> the output it is read from. */
59
+ references: Record<string, {
60
+ to: string;
61
+ secret: boolean;
62
+ }>;
63
+ }
64
+ export type CheckManifestAnswersResult = ({
65
+ ok: true;
66
+ } & ManifestInstall) | {
67
+ ok: false;
68
+ problems: ManifestProblem[];
69
+ };
70
+ /**
71
+ * The company's answers checked against the manifest: a required question left empty blocks the install,
72
+ * and every answer must fit its question. Questions with a default may be left out of `answers`.
73
+ */
74
+ export declare function checkManifestAnswers(manifest: AppManifest, answers: Record<string, ManifestAnswer>): CheckManifestAnswersResult;