@sanoma-ai/workflows 0.11.12 → 0.11.14
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/CLOUD-IDENTITY.md +12 -4
- package/KMS-SECRETS.md +3 -1
- package/README.md +4 -2
- package/dist/bundled.d.ts +2 -2
- package/dist/chats.d.ts +3 -3
- package/dist/chats.js +34 -20
- package/dist/chats.js.map +1 -1
- package/dist/client.d.ts +17 -4
- package/dist/client.js +74 -63
- package/dist/client.js.map +1 -1
- package/dist/cloud-identity.js +14 -12
- package/dist/cloud-identity.js.map +1 -1
- package/dist/collection.d.ts +2 -2
- package/dist/collection.js +53 -42
- package/dist/collection.js.map +1 -1
- package/dist/config.d.ts +11 -6
- package/dist/config.js +15 -13
- package/dist/config.js.map +1 -1
- package/dist/connection.d.ts +2 -0
- package/dist/connection.js +112 -40
- package/dist/connection.js.map +1 -1
- package/dist/credentials.d.ts +1 -1
- package/dist/credentials.js +33 -29
- package/dist/credentials.js.map +1 -1
- package/dist/db/company.d.ts +1152 -0
- package/dist/db/company.js +118 -0
- package/dist/db/company.js.map +1 -0
- package/dist/db/dbos.d.ts +111 -0
- package/dist/db/dbos.js +15 -0
- package/dist/db/dbos.js.map +1 -0
- package/dist/db/identity.d.ts +367 -0
- package/dist/db/identity.js +48 -0
- package/dist/db/identity.js.map +1 -0
- package/dist/db/index.d.ts +7 -0
- package/dist/db/index.js +12 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/kms.d.ts +224 -0
- package/dist/db/kms.js +29 -0
- package/dist/db/kms.js.map +1 -0
- package/dist/db/migrate.d.ts +66 -0
- package/dist/db/migrate.js +39 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/wakeup.d.ts +271 -0
- package/dist/db/wakeup.js +34 -0
- package/dist/db/wakeup.js.map +1 -0
- package/dist/db.d.ts +42 -39
- package/dist/db.js +129 -169
- package/dist/db.js.map +1 -1
- package/dist/deadlines.js +8 -4
- package/dist/deadlines.js.map +1 -1
- package/dist/deployment-key.d.ts +4 -1
- package/dist/deployment-key.js +10 -3
- package/dist/deployment-key.js.map +1 -1
- package/dist/host-derive.d.ts +12 -3
- package/dist/host-derive.js +21 -8
- package/dist/host-derive.js.map +1 -1
- package/dist/identity-provisioning.d.ts +2 -2
- package/dist/identity-provisioning.js +75 -42
- package/dist/identity-provisioning.js.map +1 -1
- package/dist/identity-service.js +18 -15
- package/dist/identity-service.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kms-secrets.d.ts +9 -0
- package/dist/kms-secrets.js +59 -27
- package/dist/kms-secrets.js.map +1 -1
- package/dist/ledger.d.ts +1 -1
- package/dist/ledger.js +22 -14
- package/dist/ledger.js.map +1 -1
- package/dist/op.d.ts +27 -6
- package/dist/op.js.map +1 -1
- package/dist/run-status.js +7 -3
- package/dist/run-status.js.map +1 -1
- package/dist/schedules.d.ts +1 -1
- package/dist/schedules.js +5 -7
- package/dist/schedules.js.map +1 -1
- package/dist/schema.d.ts +6 -5
- package/dist/schema.js +11 -7
- package/dist/schema.js.map +1 -1
- package/dist/shared.d.ts +6 -23
- package/dist/shared.js +6 -5
- package/dist/shared.js.map +1 -1
- package/dist/storage-provisioning.d.ts +2 -0
- package/dist/storage-provisioning.js +51 -50
- package/dist/storage-provisioning.js.map +1 -1
- package/dist/tokens.d.ts +2 -2
- package/dist/tokens.js +31 -20
- package/dist/tokens.js.map +1 -1
- package/dist/version.d.ts +4 -4
- package/dist/version.js +5 -5
- package/dist/version.js.map +1 -1
- package/dist/wakeup-service.d.ts +0 -4
- package/dist/wakeup-service.js +102 -93
- package/dist/wakeup-service.js.map +1 -1
- package/dist/worker.d.ts +7 -0
- package/dist/worker.js +26 -21
- package/dist/worker.js.map +1 -1
- package/drizzle/company/0000_tables.sql +104 -0
- package/drizzle/company/0001_wakeup_row.sql +2 -0
- package/drizzle/company/meta/0000_snapshot.json +652 -0
- package/drizzle/company/meta/0001_snapshot.json +652 -0
- package/drizzle/company/meta/_journal.json +20 -0
- package/drizzle/wakeup/0000_tables.sql +26 -0
- package/drizzle/wakeup/meta/0000_snapshot.json +200 -0
- package/drizzle/wakeup/meta/_journal.json +13 -0
- package/oxlint.json +2 -1
- package/package.json +11 -2
- package/src/bundled.ts +2 -2
- package/src/chats.ts +41 -42
- package/src/client.ts +81 -64
- package/src/cloud-identity.ts +23 -24
- package/src/collection.ts +58 -50
- package/src/config.ts +29 -19
- package/src/connection.ts +132 -102
- package/src/credentials.ts +37 -33
- package/src/db/company.ts +173 -0
- package/src/db/dbos.ts +17 -0
- package/src/db/identity.ts +61 -0
- package/src/db/index.ts +12 -0
- package/src/db/kms.ts +41 -0
- package/src/db/migrate.ts +71 -0
- package/src/db/wakeup.ts +41 -0
- package/src/db.ts +151 -182
- package/src/deadlines.ts +10 -4
- package/src/deployment-key.ts +11 -3
- package/src/host-derive.ts +26 -9
- package/src/identity-provisioning.ts +85 -57
- package/src/identity-service.ts +19 -19
- package/src/index.ts +3 -0
- package/src/kms-secrets.ts +64 -56
- package/src/ledger.ts +23 -18
- package/src/op.ts +28 -6
- package/src/run-status.ts +7 -6
- package/src/schedules.ts +6 -8
- package/src/schema.ts +11 -8
- package/src/shared.ts +6 -17
- package/src/storage-provisioning.ts +47 -52
- package/src/tokens.ts +43 -33
- package/src/version.ts +5 -5
- package/src/wakeup-service.ts +117 -131
- package/src/worker.ts +38 -21
package/src/cloud-identity.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { Compute, OAuth2Client } from "google-auth-library";
|
|
3
|
+
import { and, eq, gt, lte, sql } from "drizzle-orm";
|
|
3
4
|
import type { Db } from "./db.ts";
|
|
5
|
+
import { identityCredentials, identityDeployments } from "./db/identity.ts";
|
|
4
6
|
import { enrollmentBroker } from "./identity-broker.ts";
|
|
5
7
|
export { provisioningIdentityBroker, attachedGoogleIdTokenIssuer } from "./provisioning-identity.ts";
|
|
6
8
|
export type { ProvisioningIdentity, ProvisioningIdToken } from "./provisioning-identity.ts";
|
|
@@ -64,31 +66,28 @@ REVOKE ALL ON ALL TABLES IN SCHEMA sanoma_identity FROM PUBLIC;
|
|
|
64
66
|
export function postgresIdentityEnrollments(database: () => Promise<Db>): IdentityEnrollments {
|
|
65
67
|
return {
|
|
66
68
|
async active(hash) {
|
|
67
|
-
const
|
|
69
|
+
const [row] = await (
|
|
68
70
|
await database()
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
generation: Number(row.generation),
|
|
90
|
-
}
|
|
91
|
-
);
|
|
71
|
+
)
|
|
72
|
+
.select({
|
|
73
|
+
deployment: identityDeployments.id,
|
|
74
|
+
company: identityDeployments.company,
|
|
75
|
+
environment: identityDeployments.environment,
|
|
76
|
+
serviceAccount: identityDeployments.serviceAccount,
|
|
77
|
+
generation: identityCredentials.generation,
|
|
78
|
+
})
|
|
79
|
+
.from(identityCredentials)
|
|
80
|
+
.innerJoin(identityDeployments, eq(identityDeployments.id, identityCredentials.deployment))
|
|
81
|
+
.where(
|
|
82
|
+
and(
|
|
83
|
+
eq(identityCredentials.hash, hash),
|
|
84
|
+
eq(identityCredentials.revoked, false),
|
|
85
|
+
eq(identityDeployments.enabled, true),
|
|
86
|
+
lte(identityCredentials.validFrom, sql`clock_timestamp()`),
|
|
87
|
+
gt(identityCredentials.validUntil, sql`clock_timestamp()`),
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
return row;
|
|
92
91
|
},
|
|
93
92
|
};
|
|
94
93
|
}
|
package/src/collection.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import
|
|
3
|
+
import * as drizzle from "drizzle-orm";
|
|
4
|
+
import { and, eq, or, sql, type SQL } from "drizzle-orm";
|
|
5
|
+
import type { Db } from "./db.ts";
|
|
6
|
+
import { data } from "./db/company.ts";
|
|
4
7
|
import { defineFake, type Fake } from "./fake.ts";
|
|
5
8
|
import { isObject } from "./resource.ts";
|
|
6
9
|
import {
|
|
@@ -29,7 +32,7 @@ import {
|
|
|
29
32
|
// A company's own records: a collection is declared with plain values (a name, a key and typed
|
|
30
33
|
// fields), and its `get`, `list`, `save` and `delete` are operations of the vendor `data`, under
|
|
31
34
|
// the policy and in the ledger like any connector's. The records are JSON rows of one table the
|
|
32
|
-
// runtime owns (`
|
|
35
|
+
// runtime owns (`data`), in the config's database; a sandbox run keeps them in memory.
|
|
33
36
|
|
|
34
37
|
export type FieldType = (typeof FIELD_TYPES)[number];
|
|
35
38
|
|
|
@@ -266,7 +269,7 @@ export function collectionsConnector(list: readonly Collection[]): Connector<typ
|
|
|
266
269
|
return { ...defineConnector(DATA_VENDOR, {}), ...Object.fromEntries(ops) } as Connector<typeof DATA_VENDOR, DataOps>;
|
|
267
270
|
}
|
|
268
271
|
|
|
269
|
-
const COLUMNS =
|
|
272
|
+
const COLUMNS = { key: data.key, record: data.record, updatedAt: data.updatedAt, updatedBy: data.updatedBy };
|
|
270
273
|
const recordExists = () =>
|
|
271
274
|
new DriverError("Collection record already exists", { retryable: false, status: 409, vendorCode: "record_exists" });
|
|
272
275
|
const recordChanged = () =>
|
|
@@ -278,14 +281,10 @@ const recordChanged = () =>
|
|
|
278
281
|
const sameJson = (a: Record<string, unknown>, b: Record<string, unknown>) =>
|
|
279
282
|
isDeepStrictEqual(JSON.parse(JSON.stringify(a)), JSON.parse(JSON.stringify(b)));
|
|
280
283
|
type SaveInput = { record: Record<string, unknown>; ifAbsent?: boolean; ifRecord?: Record<string, unknown> };
|
|
281
|
-
|
|
284
|
+
// By namespace: the fake names a range's bounds `gt`, `gte`, `lt` and `lte`.
|
|
285
|
+
const RANGE = { gt: drizzle.gt, gte: drizzle.gte, lt: drizzle.lt, lte: drizzle.lte };
|
|
282
286
|
|
|
283
|
-
|
|
284
|
-
key: string;
|
|
285
|
-
record: Record<string, unknown>;
|
|
286
|
-
updated_at: Date;
|
|
287
|
-
updated_by: string;
|
|
288
|
-
}
|
|
287
|
+
type Row = Pick<typeof data.$inferSelect, keyof typeof COLUMNS>;
|
|
289
288
|
|
|
290
289
|
/** A `list` input as its schema parses it. */
|
|
291
290
|
interface Listing {
|
|
@@ -295,58 +294,70 @@ interface Listing {
|
|
|
295
294
|
}
|
|
296
295
|
|
|
297
296
|
/**
|
|
298
|
-
* The collections' driver: their records in `
|
|
297
|
+
* The collections' driver: their records in the `data` table of the database `store` is on, through
|
|
299
298
|
* the pool a run's calls share, or one connection in a transaction (a direct write, with its
|
|
300
299
|
* ledger record). Every value reaches Postgres as a parameter, field names included; the field
|
|
301
300
|
* names and operators are the ones the input's schema accepts.
|
|
302
301
|
*/
|
|
303
302
|
export function collectionsDriver(list: readonly Collection[], store: { db(): Promise<Db> }): Driver {
|
|
304
|
-
const query = async (sql: string, params: unknown[]) => (await store.db()).query<Row>(sql, params);
|
|
305
303
|
const impl = list.map((c) => {
|
|
306
304
|
const saved = (row: Row) =>
|
|
307
305
|
answer(c, {
|
|
308
306
|
key: row.key,
|
|
309
307
|
record: row.record,
|
|
310
|
-
updatedAt: row.
|
|
311
|
-
updatedBy: row.
|
|
308
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
309
|
+
updatedBy: row.updatedBy,
|
|
312
310
|
});
|
|
311
|
+
const here = (key: string) => and(eq(data.collection, c.name), eq(data.key, key));
|
|
313
312
|
const ops: Record<string, (input: any, call: CallContext) => Promise<unknown>> = {
|
|
314
313
|
get: async ({ key }: { key: string }) => {
|
|
315
|
-
const
|
|
316
|
-
const [row] = (await query(sql, [c.name, key])).rows;
|
|
314
|
+
const [row] = await (await store.db()).select(COLUMNS).from(data).where(here(key));
|
|
317
315
|
return row ? saved(row) : null;
|
|
318
316
|
},
|
|
319
|
-
list: async (listing: Listing) =>
|
|
317
|
+
list: async (listing: Listing) => {
|
|
318
|
+
const { where, order } = listClauses(c, listing);
|
|
319
|
+
const rows = await (
|
|
320
|
+
await store.db()
|
|
321
|
+
)
|
|
322
|
+
.select(COLUMNS)
|
|
323
|
+
.from(data)
|
|
324
|
+
.where(where)
|
|
325
|
+
.orderBy(...order)
|
|
326
|
+
.limit(listing.limit);
|
|
327
|
+
return rows.map(saved);
|
|
328
|
+
},
|
|
320
329
|
save: async ({ record, ifAbsent, ifRecord }: SaveInput, call) => {
|
|
330
|
+
const db = await store.db();
|
|
331
|
+
const key = record[c.key] as string;
|
|
332
|
+
const by = updatedBy(call);
|
|
321
333
|
if (ifRecord !== undefined) {
|
|
322
334
|
// PostgreSQL locks and rechecks the predicate on a concurrent update. A lost-response
|
|
323
335
|
// retry may recover its own identical result, but never overwrite another actor's state.
|
|
324
|
-
const retry =
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const row = (await query(sql, params)).rows[0];
|
|
336
|
+
const retry = and(eq(data.record, record), eq(data.updatedBy, by))!;
|
|
337
|
+
const [row] = await db
|
|
338
|
+
.update(data)
|
|
339
|
+
.set({ record, updatedAt: sql`CASE WHEN ${retry} THEN ${data.updatedAt} ELSE now() END`, updatedBy: by })
|
|
340
|
+
.where(and(here(key), or(eq(data.record, ifRecord), retry)))
|
|
341
|
+
.returning(COLUMNS);
|
|
331
342
|
if (!row) throw recordChanged();
|
|
332
343
|
return saved(row);
|
|
333
344
|
}
|
|
334
345
|
// The conflict row is locked by Postgres: never implement an insert-only claim as get + save.
|
|
335
346
|
// An identical retry by the same actor keeps the first receipt and never overwrites later progress.
|
|
336
347
|
const conflict = ifAbsent
|
|
337
|
-
?
|
|
338
|
-
:
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
348
|
+
? { set: { record: sql`${data.record}` }, setWhere: and(eq(data.record, record), eq(data.updatedBy, by)) }
|
|
349
|
+
: { set: { record, updatedAt: sql`now()`, updatedBy: by } };
|
|
350
|
+
const [row] = await db
|
|
351
|
+
.insert(data)
|
|
352
|
+
.values({ collection: c.name, key, record, updatedBy: by })
|
|
353
|
+
.onConflictDoUpdate({ target: [data.collection, data.key], ...conflict })
|
|
354
|
+
.returning(COLUMNS);
|
|
344
355
|
if (!row) throw recordExists();
|
|
345
356
|
return saved(row);
|
|
346
357
|
},
|
|
347
358
|
delete: async ({ key }: { key: string }) => {
|
|
348
|
-
const
|
|
349
|
-
return { deleted:
|
|
359
|
+
const gone = await (await store.db()).delete(data).where(here(key)).returning({ key: data.key });
|
|
360
|
+
return { deleted: gone.length > 0 };
|
|
350
361
|
},
|
|
351
362
|
};
|
|
352
363
|
return [c.name, ops];
|
|
@@ -369,40 +380,37 @@ const jsonTypesOf = (c: Collection): Record<string, JsonType> =>
|
|
|
369
380
|
const SQL_TYPE = { string: "text", number: "numeric", boolean: "boolean" } as const;
|
|
370
381
|
|
|
371
382
|
/**
|
|
372
|
-
* The `list` query
|
|
383
|
+
* The `list` query's filter and order on Postgres. A field's value is compared as its declared
|
|
373
384
|
* type, text byte by byte (`COLLATE "C"`, whatever the database's collation, as the fake
|
|
374
385
|
* compares it); a missing value, or one of another type, is SQL's null: it matches no filter and
|
|
375
386
|
* sorts last.
|
|
376
387
|
*/
|
|
377
|
-
function
|
|
388
|
+
function listClauses(c: Collection, { where = {}, order }: Listing): { where: SQL | undefined; order: SQL[] } {
|
|
378
389
|
const types = jsonTypesOf(c);
|
|
379
|
-
const params: unknown[] = [c.name];
|
|
380
|
-
const param = (value: unknown) => `$${params.push(value)}`;
|
|
381
390
|
const field = (name: string) => {
|
|
382
391
|
const type = types[name]!;
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
392
|
+
const sqlType = sql.raw(SQL_TYPE[type]);
|
|
393
|
+
const json = sql`${data.record} -> ${name}::text`;
|
|
394
|
+
const typed = sql`CASE WHEN jsonb_typeof(${json}) = ${type} THEN (${json} #>> '{}')::${sqlType} END`;
|
|
395
|
+
return type === "string" ? sql`(${typed}) COLLATE "C"` : typed;
|
|
386
396
|
};
|
|
387
|
-
const value = (name: string, v: unknown) => `${
|
|
388
|
-
const conditions = [
|
|
397
|
+
const value = (name: string, v: unknown) => sql`${v}::${sql.raw(SQL_TYPE[types[name]!])}`;
|
|
398
|
+
const conditions: SQL[] = [eq(data.collection, c.name)];
|
|
389
399
|
for (const [name, condition] of Object.entries(where)) {
|
|
390
400
|
if (condition === undefined) continue;
|
|
391
|
-
if (!isObject(condition)) conditions.push(
|
|
401
|
+
if (!isObject(condition)) conditions.push(eq(field(name), value(name, condition)));
|
|
392
402
|
else {
|
|
393
403
|
for (const [op, v] of Object.entries(condition)) {
|
|
394
|
-
|
|
395
|
-
if (v !== undefined) conditions.push(`${field(name)} ${sign} ${value(name, v)}`);
|
|
404
|
+
if (v !== undefined) conditions.push(RANGE[op as keyof typeof RANGE](field(name), value(name, v)));
|
|
396
405
|
}
|
|
397
406
|
}
|
|
398
407
|
}
|
|
399
|
-
const byKey =
|
|
408
|
+
const byKey = sql`${data.key} COLLATE "C"`;
|
|
400
409
|
const by =
|
|
401
410
|
order === undefined
|
|
402
|
-
? byKey
|
|
403
|
-
: `${field(order.replace(/^-/, ""))} ${order.startsWith("-") ? "DESC" : "ASC"} NULLS LAST
|
|
404
|
-
|
|
405
|
-
return [sql, params];
|
|
411
|
+
? [byKey]
|
|
412
|
+
: [sql`${field(order.replace(/^-/, ""))} ${sql.raw(order.startsWith("-") ? "DESC" : "ASC")} NULLS LAST`, byKey];
|
|
413
|
+
return { where: and(...conditions), order: by };
|
|
406
414
|
}
|
|
407
415
|
|
|
408
416
|
/** Orders two values of one type as Postgres does under `COLLATE "C"`: strings by their UTF-8 bytes. */
|
package/src/config.ts
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
collectionsFake,
|
|
36
36
|
isCollection,
|
|
37
37
|
} from "./collection.ts";
|
|
38
|
-
import {
|
|
38
|
+
import { lazyDb, type LazyDb } from "./db.ts";
|
|
39
39
|
import { isDirectory, kindDirs, SCENARIOS_DIR } from "./paths.ts";
|
|
40
40
|
import { callerFile, type Use, type WorkflowDefinition } from "./define.ts";
|
|
41
41
|
import { DRIFT_WORKFLOW, driftWorkflow } from "./drift.ts";
|
|
@@ -77,7 +77,7 @@ import { runtimeDriver, sandboxRuntimeDriver, sanoma } from "./runtime-ops.ts";
|
|
|
77
77
|
import { tokenStore } from "./tokens.ts";
|
|
78
78
|
import type { Steps } from "./steps.ts";
|
|
79
79
|
import { checkSchedule } from "./schedules.ts";
|
|
80
|
-
import {
|
|
80
|
+
import { builtInCodeOf, computeVersion } from "./version.ts";
|
|
81
81
|
import { type DeclaredResource, readPeople, type ResourceProblem } from "./resources.ts";
|
|
82
82
|
import { checkWakeupOps, type DeclaredWakeup, type EnvWakeup, HOST_URL } from "./wakeup.ts";
|
|
83
83
|
import { instanceUrl, WAKEUP_TOKEN } from "./wakeup-service.ts";
|
|
@@ -174,7 +174,7 @@ export interface Login {
|
|
|
174
174
|
export interface ResolvedConfig {
|
|
175
175
|
secrets: SecretConfiguration;
|
|
176
176
|
connections: ConnectionRegistry;
|
|
177
|
-
|
|
177
|
+
connectionStore: LazyDb;
|
|
178
178
|
identityProvisioning: ReturnType<typeof configuredIdentityProvisioning>;
|
|
179
179
|
/**
|
|
180
180
|
* Scopes workflows, queues and versions in the system database: the `name` of
|
|
@@ -184,8 +184,11 @@ export interface ResolvedConfig {
|
|
|
184
184
|
databaseUrl: string;
|
|
185
185
|
/** The DBOS application version: `<appName>@<DBOS__APPVERSION, or a hash of the workflows>`. */
|
|
186
186
|
version: string;
|
|
187
|
-
/**
|
|
188
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Each built-in workflow's code hash (`builtInCodeOf`), by its name, which each of its runs carries:
|
|
189
|
+
* a worker on another version of the app takes over a run whose code is its own (`carryBuiltInRuns`).
|
|
190
|
+
*/
|
|
191
|
+
builtInCodes: Readonly<Record<string, string>>;
|
|
189
192
|
/** The DBOS queue runs are started on: `sanoma:<appName>`. */
|
|
190
193
|
queueName: string;
|
|
191
194
|
/** The config's `login`, checked (`checkLogin`); the built-in `login-code` workflow sends through its `sender`, from its `from`. */
|
|
@@ -206,6 +209,8 @@ export interface ResolvedConfig {
|
|
|
206
209
|
driversByVendor: Map<string, Driver>;
|
|
207
210
|
/** The makers of the fakes the connectors attach, by vendor id in `connectors` order: each sandbox run makes its own. */
|
|
208
211
|
fakes: Map<string, () => Fake<any, any>>;
|
|
212
|
+
/** The preparations the connectors attach (`ConnectorSetup.prepare`), by vendor id in `connectors` order: the worker runs each at its start. */
|
|
213
|
+
preparations: ReadonlyMap<string, NonNullable<ConnectorSetup["prepare"]>>;
|
|
209
214
|
/** The makers of the login halves the connectors attach, by vendor id in `connectors` order: the app makes one per public site that names it. */
|
|
210
215
|
logins: ReadonlyMap<string, NonNullable<ConnectorSetup["login"]>>;
|
|
211
216
|
/** The makers of the model halves the connectors attach, by vendor id in `connectors` order: the app makes each once for its model registry. */
|
|
@@ -235,11 +240,11 @@ export interface ResolvedConfig {
|
|
|
235
240
|
/** The config's collections, by name: their operations are in `ops` and `drivers`, their fake in `fakes`, under `DATA_VENDOR`. */
|
|
236
241
|
collections: ReadonlyMap<string, Collection>;
|
|
237
242
|
/**
|
|
238
|
-
* The
|
|
243
|
+
* The database the collections' driver keeps their records in, when there are any: opened on
|
|
239
244
|
* its first call, and closed by whoever calls the driver (the worker, and the client, which
|
|
240
245
|
* calls it directly, when each stops).
|
|
241
246
|
*/
|
|
242
|
-
dataStore?:
|
|
247
|
+
dataStore?: LazyDb;
|
|
243
248
|
/**
|
|
244
249
|
* By name; each checked against `ops` and `drivers`. The config's, the built-ins `drift`
|
|
245
250
|
* (`DRIFT_WORKFLOW`) and `apply` (`APPLY_WORKFLOW`) when its connectors declare resource types,
|
|
@@ -347,15 +352,15 @@ export function resolveConfig(config: SanomaConfig): ResolvedConfig {
|
|
|
347
352
|
const hostTyped = [...types.values()].some((type) => type.host);
|
|
348
353
|
// Built-in: the collections' operations, `data.<name>.<op>`, on the config's database.
|
|
349
354
|
const databaseUrl = resolveDatabaseUrl(config);
|
|
350
|
-
const
|
|
355
|
+
const connectionStore = lazyDb(databaseUrl, "connections");
|
|
351
356
|
const identityProvisioning = configuredIdentityProvisioning(config.identityProvisioning);
|
|
352
357
|
const connections = new ConnectionRegistry(
|
|
353
358
|
connectionCatalog(config.connectors, config.connections, config.connectionRules),
|
|
354
|
-
() =>
|
|
355
|
-
configuredSecrets(config.secrets ?? {}, () =>
|
|
359
|
+
() => connectionStore.db(),
|
|
360
|
+
configuredSecrets(config.secrets ?? {}, () => connectionStore.db()),
|
|
356
361
|
);
|
|
357
362
|
const declared = [...collections.values()];
|
|
358
|
-
const dataStore = declared.length ?
|
|
363
|
+
const dataStore = declared.length ? lazyDb(databaseUrl, "data") : undefined;
|
|
359
364
|
// Built-in, in every config: a change's operations, which the template's policy names
|
|
360
365
|
// (`sanoma.change.merge`), the listing of apps a page may call (`sanoma.apps.list`), and what a
|
|
361
366
|
// workflow asks the runtime to do for it (`sanoma`, runtime-ops.ts).
|
|
@@ -380,9 +385,9 @@ export function resolveConfig(config: SanomaConfig): ResolvedConfig {
|
|
|
380
385
|
connectionDriver(connections),
|
|
381
386
|
appsListDriver,
|
|
382
387
|
runtimeDriver({
|
|
383
|
-
tokens: tokenStore(() =>
|
|
388
|
+
tokens: tokenStore(() => connectionStore.db()),
|
|
384
389
|
identities: identityProvisioning.store,
|
|
385
|
-
inspect: inspectRun(() =>
|
|
390
|
+
inspect: inspectRun(() => connectionStore.db(), appName),
|
|
386
391
|
}),
|
|
387
392
|
...(hostTyped ? [sourceDriver] : []),
|
|
388
393
|
...(dataStore ? [collectionsDriver(declared, dataStore)] : []),
|
|
@@ -452,20 +457,25 @@ export function resolveConfig(config: SanomaConfig): ResolvedConfig {
|
|
|
452
457
|
return {
|
|
453
458
|
secrets: config.secrets ?? {},
|
|
454
459
|
connections,
|
|
455
|
-
|
|
460
|
+
connectionStore,
|
|
456
461
|
identityProvisioning,
|
|
457
462
|
appName,
|
|
458
463
|
databaseUrl,
|
|
459
464
|
// The built-in's code and operations are part of the app's version, like the config's own. A
|
|
460
465
|
// bundle's code is not the source's: it carries the hashes of the root it was read from.
|
|
461
466
|
version: bundled?.version ?? computeVersion({ appName, workflows: [...names.values()] }),
|
|
462
|
-
|
|
467
|
+
builtInCodes:
|
|
468
|
+
bundled?.builtInCodes ??
|
|
469
|
+
Object.fromEntries(
|
|
470
|
+
[...names.values()].filter((wf) => BUILT_IN_WORKFLOWS.has(wf.name)).map((wf) => [wf.name, builtInCodeOf(wf)]),
|
|
471
|
+
),
|
|
463
472
|
queueName: `sanoma:${appName}`,
|
|
464
473
|
...(login && { login }),
|
|
465
474
|
ops,
|
|
466
475
|
drivers,
|
|
467
476
|
driversByVendor,
|
|
468
477
|
fakes,
|
|
478
|
+
preparations: makersOf(config.connectors, "prepare"),
|
|
469
479
|
logins: makersOf(config.connectors, "login"),
|
|
470
480
|
models: makersOf(config.connectors, "model"),
|
|
471
481
|
remotes,
|
|
@@ -626,8 +636,8 @@ function makersOf<K extends keyof ConnectorSetup>(
|
|
|
626
636
|
export function readRootSnapshot(config: SanomaConfig, resolved: ResolvedConfig = resolveConfig(config)): RootSnapshot {
|
|
627
637
|
const root = rootOf(config);
|
|
628
638
|
if (root === undefined) throw new Error("A config with no root has no files to carry");
|
|
629
|
-
const { appName, version,
|
|
630
|
-
return { appName, version,
|
|
639
|
+
const { appName, version, builtInCodes } = resolved;
|
|
640
|
+
return { appName, version, builtInCodes, policies: readPolicyFiles(root), people: readPeople(root) };
|
|
631
641
|
}
|
|
632
642
|
|
|
633
643
|
/**
|
|
@@ -762,9 +772,9 @@ async function readDeclared(
|
|
|
762
772
|
const keep = new Set<string>();
|
|
763
773
|
const leftOut = new Set<string>();
|
|
764
774
|
if (root !== undefined) {
|
|
765
|
-
const { deriveHosts, hostFacts } = await import("./host-derive.ts");
|
|
775
|
+
const { deriveHosts, hostFacts, hostRootFacts } = await import("./host-derive.ts");
|
|
766
776
|
const facts = await hostFacts({
|
|
767
|
-
root,
|
|
777
|
+
rootFacts: () => hostRootFacts(root),
|
|
768
778
|
resources,
|
|
769
779
|
types,
|
|
770
780
|
drivers: options.drivers,
|