@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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { bigint, boolean, check, pgSchema, text, timestamp, unique, uuid } from "drizzle-orm/pg-core";
|
|
3
|
+
|
|
4
|
+
// The cloud identity broker's tables, in the schema `sanoma_identity` an administrator installs
|
|
5
|
+
// with `CLOUD_IDENTITY_SQL` (`cloud-identity.ts`) and `CLOUD_IDENTITY_AUDIT_SQL`
|
|
6
|
+
// (`identity-service.ts`), with their grants: the runtime never makes them, so no migration is
|
|
7
|
+
// generated from this model. `installed-schemas.test.ts` holds the install SQL to it.
|
|
8
|
+
|
|
9
|
+
export const identity = pgSchema("sanoma_identity");
|
|
10
|
+
|
|
11
|
+
const when = () => timestamp({ withTimezone: true });
|
|
12
|
+
|
|
13
|
+
/** Each enrolled deployment and the service account it may act as. */
|
|
14
|
+
export const identityDeployments = identity.table("deployments", {
|
|
15
|
+
id: text().primaryKey(),
|
|
16
|
+
company: text().notNull(),
|
|
17
|
+
environment: text().notNull(),
|
|
18
|
+
serviceAccount: text().notNull(),
|
|
19
|
+
enabled: boolean().notNull().default(true),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/** Each deployment credential's hash, never the credential, with its generation and validity. */
|
|
23
|
+
export const identityCredentials = identity.table(
|
|
24
|
+
"credentials",
|
|
25
|
+
{
|
|
26
|
+
hash: text().primaryKey(),
|
|
27
|
+
deployment: text()
|
|
28
|
+
.notNull()
|
|
29
|
+
.references(() => identityDeployments.id),
|
|
30
|
+
generation: bigint({ mode: "number" }).notNull(),
|
|
31
|
+
validFrom: when().notNull().defaultNow(),
|
|
32
|
+
validUntil: when().notNull(),
|
|
33
|
+
revoked: boolean().notNull().default(false),
|
|
34
|
+
},
|
|
35
|
+
(t) => [
|
|
36
|
+
unique("credentials_deployment_generation").on(t.deployment, t.generation),
|
|
37
|
+
check("credentials_hash", sql`${t.hash} ~ '^[0-9a-f]{64}$'`),
|
|
38
|
+
check("credentials_generation", sql`${t.generation} > 0`),
|
|
39
|
+
check("credentials_validity", sql`${t.validUntil} > ${t.validFrom}`),
|
|
40
|
+
check("credentials_finite", sql`isfinite(${t.validFrom}) AND isfinite(${t.validUntil})`),
|
|
41
|
+
],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
/** Each issuance decision, without token values. */
|
|
45
|
+
export const identityAuditEvents = identity.table(
|
|
46
|
+
"audit_events",
|
|
47
|
+
{
|
|
48
|
+
id: uuid().primaryKey().defaultRandom(),
|
|
49
|
+
recordedAt: when()
|
|
50
|
+
.notNull()
|
|
51
|
+
.default(sql`clock_timestamp()`),
|
|
52
|
+
result: text().$type<"issued" | "denied" | "unavailable">().notNull(),
|
|
53
|
+
deployment: text(),
|
|
54
|
+
company: text(),
|
|
55
|
+
environment: text(),
|
|
56
|
+
serviceAccount: text(),
|
|
57
|
+
generation: bigint({ mode: "number" }),
|
|
58
|
+
expiresAt: when(),
|
|
59
|
+
},
|
|
60
|
+
(t) => [check("audit_events_result", sql`${t.result} IN ('issued', 'denied', 'unavailable')`)],
|
|
61
|
+
);
|
package/src/db/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// `@sanoma-ai/workflows/db`, server-only: the runtime's tables and the database they are read
|
|
2
|
+
// with, for the packages that share them (the connectors' stores, the app's tests), and the
|
|
3
|
+
// migrator a connector applies its own generated folder with, under the preparation's lock. Never
|
|
4
|
+
// a workflow's import: the shipped `oxlint.json` refuses it, as it does `/wakeup-service`.
|
|
5
|
+
|
|
6
|
+
export { type Db, databaseOn, pgCode, underPreparationLock } from "../db.ts";
|
|
7
|
+
export * from "./company.ts";
|
|
8
|
+
export * from "./dbos.ts";
|
|
9
|
+
export * from "./identity.ts";
|
|
10
|
+
export * from "./kms.ts";
|
|
11
|
+
export * from "./migrate.ts";
|
|
12
|
+
export * from "./wakeup.ts";
|
package/src/db/kms.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { boolean, check, jsonb, pgSchema, primaryKey, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
|
3
|
+
import type { Envelope } from "../kms-secrets.ts";
|
|
4
|
+
|
|
5
|
+
// The KMS secret store's tables, in the schema `sanoma_kms` an administrator installs with
|
|
6
|
+
// `KMS_SECRETS_SQL` (`kms-secrets.ts`) and `sanoma.secretStore.prepare` (`storage-provisioning.ts`),
|
|
7
|
+
// with their grants: the runtime never makes them, so no migration is generated from this model.
|
|
8
|
+
// `installed-schemas.test.ts` holds the install SQL to it.
|
|
9
|
+
|
|
10
|
+
export const kms = pgSchema("sanoma_kms");
|
|
11
|
+
|
|
12
|
+
/** Each secret's envelope, by company, namespace and path. */
|
|
13
|
+
export const kmsSecrets = kms.table(
|
|
14
|
+
"secrets",
|
|
15
|
+
{
|
|
16
|
+
company: text().notNull(),
|
|
17
|
+
namespace: text().notNull(),
|
|
18
|
+
path: text().notNull(),
|
|
19
|
+
version: uuid().notNull(),
|
|
20
|
+
envelope: jsonb().$type<Envelope>().notNull(),
|
|
21
|
+
updatedBy: text().notNull(),
|
|
22
|
+
updatedAt: timestamp({ withTimezone: true })
|
|
23
|
+
.notNull()
|
|
24
|
+
.defaultNow()
|
|
25
|
+
.$onUpdate(() => sql`now()`),
|
|
26
|
+
},
|
|
27
|
+
(t) => [primaryKey({ columns: [t.company, t.namespace, t.path] })],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
/** The one row binding the database to its company and environment, and the runtime role's credential hash. */
|
|
31
|
+
export const kmsDeployment = kms.table(
|
|
32
|
+
"deployment",
|
|
33
|
+
{
|
|
34
|
+
singleton: boolean().primaryKey().default(true),
|
|
35
|
+
company: text().notNull(),
|
|
36
|
+
environment: text().notNull(),
|
|
37
|
+
runtimeRole: text().notNull(),
|
|
38
|
+
credentialHash: text().notNull(),
|
|
39
|
+
},
|
|
40
|
+
(t) => [check("deployment_singleton", sql`${t.singleton}`)],
|
|
41
|
+
);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
4
|
+
import type { Db } from "../db.ts";
|
|
5
|
+
import companyJournal from "../../drizzle/company/meta/_journal.json" with { type: "json" };
|
|
6
|
+
import wakeupJournal from "../../drizzle/wakeup/meta/_journal.json" with { type: "json" };
|
|
7
|
+
|
|
8
|
+
// The generated migrations (`pnpm --filter @sanoma-ai/workflows generate`), one folder per
|
|
9
|
+
// database the runtime makes tables in, each with its own journal table in the schema `drizzle`,
|
|
10
|
+
// since the wakeup service's database may be a company's own. A connector's tables have folders
|
|
11
|
+
// of their own, shaped the same (`@sanoma-ai/connector-better-auth`). The Worker, which never
|
|
12
|
+
// migrates, checks a database against the journal shipped with the package
|
|
13
|
+
// (`migrationsApplied`), with no file read: the journal is imported.
|
|
14
|
+
|
|
15
|
+
/** Drizzle's journal, `meta/_journal.json` of a migrations folder. */
|
|
16
|
+
export interface MigrationsJournal {
|
|
17
|
+
entries: { idx: number; when: number; tag: string }[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A migrations folder with the journal table recording what of it a database has. */
|
|
21
|
+
export interface Migrations {
|
|
22
|
+
/**
|
|
23
|
+
* The folder, beside the module declaring it (`new URL("../drizzle/<name>/", import.meta.url)`,
|
|
24
|
+
* from `src/` and from `dist/` alike), asked for only when migrating, never in the Worker.
|
|
25
|
+
*/
|
|
26
|
+
folder(): URL;
|
|
27
|
+
/** Its journal table in the schema `drizzle`. */
|
|
28
|
+
table: string;
|
|
29
|
+
journal: MigrationsJournal;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The tables every company database gets (`db/company.ts`). */
|
|
33
|
+
export const COMPANY_MIGRATIONS = {
|
|
34
|
+
folder: () => new URL("../../drizzle/company/", import.meta.url),
|
|
35
|
+
table: "__drizzle_migrations",
|
|
36
|
+
journal: companyJournal,
|
|
37
|
+
} satisfies Migrations;
|
|
38
|
+
|
|
39
|
+
/** The wakeup service's tables (`db/wakeup.ts`). */
|
|
40
|
+
export const WAKEUP_MIGRATIONS = {
|
|
41
|
+
folder: () => new URL("../../drizzle/wakeup/", import.meta.url),
|
|
42
|
+
table: "__drizzle_migrations_wakeup",
|
|
43
|
+
journal: wakeupJournal,
|
|
44
|
+
} satisfies Migrations;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Applies to `db` the migrations of `of` its database has not had, in one transaction, recording
|
|
48
|
+
* each in the journal table. Drizzle's migrator compares only the newest timestamp, so a migration
|
|
49
|
+
* older than the newest applied is skipped: the repository's `scripts/check-journal.ts` keeps a
|
|
50
|
+
* branch from adding one. Two processes migrating one database at once race (the migrator takes no lock): callers
|
|
51
|
+
* hold `underPreparationLock`.
|
|
52
|
+
*/
|
|
53
|
+
export const applyMigrations = (db: Db, of: Migrations): Promise<void> =>
|
|
54
|
+
migrate(db, { migrationsFolder: fileURLToPath(of.folder()), migrationsTable: of.table });
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether the database `db` is on has had every migration of `of`: each journal entry's `when`
|
|
58
|
+
* is a `created_at` of its journal table, whatever else the table holds (a database a newer
|
|
59
|
+
* release migrated serves this one, as after a rollback). False with no journal table.
|
|
60
|
+
*/
|
|
61
|
+
export async function migrationsApplied(db: Db, of: Migrations): Promise<boolean> {
|
|
62
|
+
const present = await db.execute<{ present: boolean }>(
|
|
63
|
+
sql`SELECT to_regclass(${`drizzle.${of.table}`}) IS NOT NULL AS present`,
|
|
64
|
+
);
|
|
65
|
+
if (present.rows[0]?.present !== true) return false;
|
|
66
|
+
const { rows } = await db.execute<{ at: string }>(
|
|
67
|
+
sql`SELECT created_at::text AS at FROM drizzle.${sql.identifier(of.table)}`,
|
|
68
|
+
);
|
|
69
|
+
const applied = new Set(rows.map((row) => Number(row.at)));
|
|
70
|
+
return of.journal.entries.every((entry) => applied.has(entry.when));
|
|
71
|
+
}
|
package/src/db/wakeup.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { index, integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
|
3
|
+
|
|
4
|
+
// The wakeup service's tables (`wakeup-service.ts`), in the service's own database: never a
|
|
5
|
+
// company's, which every company database gets. Migrations under `drizzle/wakeup/`.
|
|
6
|
+
|
|
7
|
+
const when = () => timestamp({ withTimezone: true });
|
|
8
|
+
|
|
9
|
+
/** One account per party that registers instances, its token's hash alone. */
|
|
10
|
+
export const wakeupAccounts = pgTable("sanoma_wakeup_accounts", {
|
|
11
|
+
id: uuid().primaryKey(),
|
|
12
|
+
tokenHash: text().notNull().unique(),
|
|
13
|
+
label: text().notNull(),
|
|
14
|
+
createdAt: when().notNull().defaultNow(),
|
|
15
|
+
revokedAt: when(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/** Each registered instance: its URL, its token's hash and its registered wake. */
|
|
19
|
+
export const wakeupInstances = pgTable(
|
|
20
|
+
"sanoma_wakeup_instances",
|
|
21
|
+
{
|
|
22
|
+
id: uuid().primaryKey(),
|
|
23
|
+
accountId: uuid()
|
|
24
|
+
.notNull()
|
|
25
|
+
.references(() => wakeupAccounts.id),
|
|
26
|
+
url: text().notNull(),
|
|
27
|
+
tokenHash: text().notNull().unique(),
|
|
28
|
+
at: when(),
|
|
29
|
+
calledAt: when(),
|
|
30
|
+
attempts: integer().notNull().default(0),
|
|
31
|
+
nextAttemptAt: when(),
|
|
32
|
+
lastError: text(),
|
|
33
|
+
createdAt: when().notNull().defaultNow(),
|
|
34
|
+
},
|
|
35
|
+
(t) => [
|
|
36
|
+
index("sanoma_wakeup_instances_at")
|
|
37
|
+
.on(t.at)
|
|
38
|
+
.where(sql`${t.at} IS NOT NULL`),
|
|
39
|
+
index("sanoma_wakeup_instances_account").on(t.accountId),
|
|
40
|
+
],
|
|
41
|
+
);
|