@wtfalch/foundry 0.58.0 → 0.60.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.
@@ -71,7 +71,8 @@ export interface WorkerAppDatabasePort {
71
71
  applyMigration(owner: HyperdriveOrigin, migration: DatabaseMigration): Promise<void>;
72
72
  /**
73
73
  * Creates or updates the `runtime.user` role with `runtime.password`, grants it DML on the
74
- * tables and revokes UPDATE, DELETE and TRUNCATE from it on each `appendOnly` table.
74
+ * tables, revokes UPDATE, DELETE and TRUNCATE from it on each `appendOnly` table, and grants
75
+ * it EXECUTE on each `grants` function.
75
76
  */
76
77
  ensureRuntimeRole(input: {
77
78
  owner: HyperdriveOrigin;
@@ -80,6 +81,7 @@ export interface WorkerAppDatabasePort {
80
81
  password: string;
81
82
  };
82
83
  appendOnly: string[];
84
+ grants: string[];
83
85
  }): Promise<void>;
84
86
  }
85
87
  export declare const WORKER_APP_MIGRATION_TIMEOUT_MS = 300000;
@@ -149,6 +151,7 @@ export declare function applyWorkerAppDatabase(deps: WorkerAppDatabaseDeps, org:
149
151
  artifactUrl: string;
150
152
  database: {
151
153
  appendOnly: string[];
154
+ grants?: string[];
152
155
  migrationsSha256?: string;
153
156
  };
154
157
  }, context: StepExecutionContext, zoneApex: string): Promise<{
@@ -264,6 +264,7 @@ export async function applyWorkerAppDatabase(deps, org, entry, scriptName, want,
264
264
  owner: ownerOrigin,
265
265
  runtime: { user: runtimeUser, password: runtimePassword },
266
266
  appendOnly: [...want.database.appendOnly],
267
+ grants: [...(want.database.grants ?? [])],
267
268
  }));
268
269
  // 6. Hyperdrive, with the runtime credentials only.
269
270
  const name = hyperdriveName(scriptName);
@@ -107,6 +107,11 @@ export interface WorkerAppUpload {
107
107
  migrations?: WorkerAppMigrations;
108
108
  /** Durable Object classes the upload marks container-enabled; absent when the entry declares no container. */
109
109
  containers?: string[];
110
+ /** Workers Logs: always on, every request sampled. The adapter sends it as the upload metadata's `observability`. */
111
+ observability: {
112
+ enabled: true;
113
+ head_sampling_rate: 1;
114
+ };
110
115
  }
111
116
  export interface WorkerAppContainer {
112
117
  className: string;
@@ -203,6 +208,7 @@ export interface WorkerAppWant {
203
208
  /** `workerApp.database`: the Hyperdrive binding and the tables the runtime role may only append to, sorted. */
204
209
  database?: {
205
210
  appendOnly: string[];
211
+ grants?: string[];
206
212
  migrationsSha256?: string;
207
213
  };
208
214
  /** Secret names and where each comes from, sorted. */
@@ -186,6 +186,7 @@ export function wantedWorkerApp(org, entry, recorded, input) {
186
186
  ? {
187
187
  database: {
188
188
  appendOnly: [...(app.database.appendOnly ?? [])].sort(),
189
+ ...(app.database.grants?.length ? { grants: [...app.database.grants].sort() } : {}),
189
190
  ...(app.database.migrationsSha256
190
191
  ? { migrationsSha256: app.database.migrationsSha256 }
191
192
  : {}),
@@ -526,6 +527,7 @@ export async function applyWorkerApp(deps, org, action, context) {
526
527
  ? [{ type: 'hyperdrive', name: WORKER_APP_HYPERDRIVE_BINDING, id: hyperdriveId }]
527
528
  : []),
528
529
  ],
530
+ observability: { enabled: true, head_sampling_rate: 1 },
529
531
  ...(migrations ? { migrations } : {}),
530
532
  ...(want.containers.length > 0
531
533
  ? { containers: want.containers.map((c) => c.className) }
@@ -60,10 +60,14 @@ export interface WorkerAppConfig {
60
60
  * or truncate. `migrationsSha256` (64 lowercase hex) pins the release's
61
61
  * `migrations.json`: required when the release has one, and foundry refuses
62
62
  * to parse bytes whose sha256 differs. Omit it only for a release with no
63
- * `migrations.json`; one that has the file is then refused.
63
+ * `migrations.json`; one that has the file is then refused. `grants` names
64
+ * functions the runtime role may execute, as `name(type, ...)` or
65
+ * `schema.name(type, ...)`: the security definer functions a migration
66
+ * revoked from public, such as `@wtfalch/audit`'s ledger functions.
64
67
  */
65
68
  database?: {
66
69
  appendOnly?: string[];
70
+ grants?: string[];
67
71
  migrationsSha256?: string;
68
72
  };
69
73
  /** Plain vars. Never secrets. */
@@ -95,6 +99,8 @@ export declare const entryHasDatabase: (entry: {
95
99
  export declare const WORKER_APP_HYPERDRIVE_BINDING = "HYPERDRIVE";
96
100
  /** The most tables `database.appendOnly` may name, and the shape of one name (`table` or `schema.table`). */
97
101
  export declare const WORKER_APP_APPEND_ONLY_MAX = 100;
102
+ /** The most functions `database.grants` may name, and the shape of one signature. It is spliced into SQL, so nothing else passes. */
103
+ export declare const WORKER_APP_GRANTS_MAX = 100;
98
104
  /** Hex characters of the org id's sha256 in a host id. */
99
105
  export declare const WORKER_APP_HOST_ID_HASH_LENGTH = 12;
100
106
  /**
@@ -27,6 +27,9 @@ export const WORKER_APP_HYPERDRIVE_BINDING = 'HYPERDRIVE';
27
27
  /** The most tables `database.appendOnly` may name, and the shape of one name (`table` or `schema.table`). */
28
28
  export const WORKER_APP_APPEND_ONLY_MAX = 100;
29
29
  const TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,62}(\.[A-Za-z_][A-Za-z0-9_]{0,62})?$/;
30
+ /** The most functions `database.grants` may name, and the shape of one signature. It is spliced into SQL, so nothing else passes. */
31
+ export const WORKER_APP_GRANTS_MAX = 100;
32
+ const FUNCTION_SIGNATURE = /^[A-Za-z_][A-Za-z0-9_]{0,62}(\.[A-Za-z_][A-Za-z0-9_]{0,62})?\(([a-z][a-z0-9_]*( [a-z][a-z0-9_]*)?(, [a-z][a-z0-9_]*( [a-z][a-z0-9_]*)?)*)?\)$/;
30
33
  /** The custody Worker's host id shape for a derived (app) host, and its limit. */
31
34
  const HOST_ID = /^app-[a-z0-9]+(-[a-z0-9]+)*$/;
32
35
  const HOST_ID_MAX_LENGTH = 64;
@@ -342,11 +345,26 @@ function validateWorkerAppDatabase(at, app) {
342
345
  if (database === undefined)
343
346
  return null;
344
347
  if (typeof database !== 'object' || database === null || Array.isArray(database)) {
345
- return `${at} database must be an object: { appendOnly?: string[]; migrationsSha256?: string }`;
348
+ return `${at} database must be an object: { appendOnly?: string[]; grants?: string[]; migrationsSha256?: string }`;
346
349
  }
347
- const unknown = Object.keys(database).find((key) => key !== 'appendOnly' && key !== 'migrationsSha256');
350
+ const unknown = Object.keys(database).find((key) => key !== 'appendOnly' && key !== 'grants' && key !== 'migrationsSha256');
348
351
  if (unknown) {
349
- return `${at} database.${unknown} is not a setting: only appendOnly and migrationsSha256`;
352
+ return `${at} database.${unknown} is not a setting: only appendOnly, grants and migrationsSha256`;
353
+ }
354
+ const grants = database.grants;
355
+ if (grants !== undefined) {
356
+ if (!Array.isArray(grants) || grants.length > WORKER_APP_GRANTS_MAX) {
357
+ return `${at} database.grants must be a list of at most ${WORKER_APP_GRANTS_MAX} function signatures`;
358
+ }
359
+ const seenGrants = new Set();
360
+ for (const signature of grants) {
361
+ if (typeof signature !== 'string' || !FUNCTION_SIGNATURE.test(signature)) {
362
+ return `${at} database.grants names '${String(signature).slice(0, 80)}', which is not a function signature (name(type, ...) or schema.name(type, ...))`;
363
+ }
364
+ if (seenGrants.has(signature))
365
+ return `${at} database.grants names '${signature}' twice`;
366
+ seenGrants.add(signature);
367
+ }
350
368
  }
351
369
  const sha = database.migrationsSha256;
352
370
  if (sha !== undefined && (typeof sha !== 'string' || !SHA256_HEX.test(sha))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/foundry",
3
- "version": "0.58.0",
3
+ "version": "0.60.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",