@headless-lms/adapter-db 0.1.0 → 0.2.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/README.md +79 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/migrate.js.map +1 -1
- package/dist/repositories/assets.d.ts +1 -1
- package/dist/repositories/assets.js +4 -1
- package/dist/repositories/assets.js.map +1 -1
- package/dist/repositories/automations.d.ts +7 -5
- package/dist/repositories/automations.js +35 -10
- package/dist/repositories/automations.js.map +1 -1
- package/dist/repositories/content.d.ts +21 -7
- package/dist/repositories/content.js +168 -18
- package/dist/repositories/content.js.map +1 -1
- package/dist/repositories/course-analytics.d.ts +15 -0
- package/dist/repositories/course-analytics.js +137 -0
- package/dist/repositories/course-analytics.js.map +1 -0
- package/dist/repositories/credentials.js +2 -0
- package/dist/repositories/credentials.js.map +1 -1
- package/dist/repositories/dashboard.js +2 -0
- package/dist/repositories/dashboard.js.map +1 -1
- package/dist/repositories/discussion.d.ts +1 -1
- package/dist/repositories/discussion.js +2 -0
- package/dist/repositories/discussion.js.map +1 -1
- package/dist/repositories/entitlements.d.ts +11 -5
- package/dist/repositories/entitlements.js +42 -17
- package/dist/repositories/entitlements.js.map +1 -1
- package/dist/repositories/identity.js +2 -0
- package/dist/repositories/identity.js.map +1 -1
- package/dist/repositories/integrations.d.ts +1 -1
- package/dist/repositories/integrations.js +2 -0
- package/dist/repositories/integrations.js.map +1 -1
- package/dist/repositories/learn.js +6 -4
- package/dist/repositories/learn.js.map +1 -1
- package/dist/repositories/members.js +2 -0
- package/dist/repositories/members.js.map +1 -1
- package/dist/repositories/org-user-profile.js.map +1 -1
- package/dist/repositories/organizations.d.ts +4 -6
- package/dist/repositories/organizations.js +26 -45
- package/dist/repositories/organizations.js.map +1 -1
- package/dist/repositories/outbox.js +3 -0
- package/dist/repositories/outbox.js.map +1 -1
- package/dist/repositories/pg-errors.d.ts +53 -5
- package/dist/repositories/pg-errors.js +97 -8
- package/dist/repositories/pg-errors.js.map +1 -1
- package/dist/repositories/progress.d.ts +1 -1
- package/dist/repositories/progress.js +3 -0
- package/dist/repositories/progress.js.map +1 -1
- package/dist/repositories/settings.js +2 -0
- package/dist/repositories/settings.js.map +1 -1
- package/dist/repositories/students.d.ts +2 -1
- package/dist/repositories/students.js +80 -3
- package/dist/repositories/students.js.map +1 -1
- package/dist/schema/assets.d.ts +17 -0
- package/dist/schema/assets.js +2 -1
- package/dist/schema/assets.js.map +1 -1
- package/dist/schema/automations.d.ts +85 -8
- package/dist/schema/automations.js +7 -4
- package/dist/schema/automations.js.map +1 -1
- package/dist/schema/better-auth.js.map +1 -1
- package/dist/schema/content.d.ts +272 -22
- package/dist/schema/content.js +40 -5
- package/dist/schema/content.js.map +1 -1
- package/dist/schema/credentials.js +1 -1
- package/dist/schema/credentials.js.map +1 -1
- package/dist/schema/discussion.d.ts +34 -0
- package/dist/schema/discussion.js +4 -2
- package/dist/schema/discussion.js.map +1 -1
- package/dist/schema/entitlements.d.ts +52 -1
- package/dist/schema/entitlements.js +15 -5
- package/dist/schema/entitlements.js.map +1 -1
- package/dist/schema/identity.js.map +1 -1
- package/dist/schema/index.d.ts +3 -3
- package/dist/schema/index.js +4 -2
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/integrations.js +1 -1
- package/dist/schema/integrations.js.map +1 -1
- package/dist/schema/organizations.d.ts +17 -0
- package/dist/schema/organizations.js +2 -1
- package/dist/schema/organizations.js.map +1 -1
- package/dist/schema/outbox.d.ts +17 -0
- package/dist/schema/outbox.js +1 -0
- package/dist/schema/outbox.js.map +1 -1
- package/dist/schema/progress.d.ts +17 -0
- package/dist/schema/progress.js +1 -0
- package/dist/schema/progress.js.map +1 -1
- package/dist/schema/settings.js.map +1 -1
- package/dist/unit-of-work.js +7 -2
- package/dist/unit-of-work.js.map +1 -1
- package/drizzle/0000_baseline.sql +0 -1
- package/drizzle/0001_new_sentry.sql +43 -0
- package/drizzle/0002_automation_kinds.sql +7 -0
- package/drizzle/meta/0000_snapshot.json +167 -597
- package/drizzle/meta/0001_snapshot.json +3769 -0
- package/drizzle/meta/0002_snapshot.json +3770 -0
- package/drizzle/meta/_journal.json +16 -2
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @headless-lms/adapter-db
|
|
2
|
+
|
|
3
|
+
Drizzle/Postgres persistence adapter. Implements the outbound repository ports from
|
|
4
|
+
`@headless-lms/core` and ships the migrations for the LMS database.
|
|
5
|
+
|
|
6
|
+
It depends on `@headless-lms/core`, `drizzle-orm` and `pg` — never on the server or
|
|
7
|
+
any other adapter.
|
|
8
|
+
|
|
9
|
+
## Layout
|
|
10
|
+
|
|
11
|
+
| Path | What lives there |
|
|
12
|
+
| --------------------- | -------------------------------------------------------------------------- |
|
|
13
|
+
| `src/schema/` | Drizzle tables, grouped by owning context, barrelled in `index.ts` |
|
|
14
|
+
| `src/repositories/` | One `Drizzle*Repository` per context port, plus pg error translation |
|
|
15
|
+
| `src/client.ts` | `createDb` — pool + drizzle instance; `Db`, `Tx`, `DbExecutor` types |
|
|
16
|
+
| `src/unit-of-work.ts` | `DrizzleUnitOfWork` — transaction scope for the core `UnitOfWork` port |
|
|
17
|
+
| `src/migrate.ts` | `runMigrations(databaseUrl)` against the packaged `drizzle/` folder |
|
|
18
|
+
| `drizzle/` | Generated SQL migrations and snapshots (committed, shipped in the package) |
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createDb, runMigrations, DrizzleProgressRepository } from "@headless-lms/adapter-db";
|
|
24
|
+
|
|
25
|
+
await runMigrations(process.env.DATABASE_URL!);
|
|
26
|
+
|
|
27
|
+
const db = createDb(process.env.DATABASE_URL!);
|
|
28
|
+
const progress = new DrizzleProgressRepository(db, logger);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Repositories take a `DbExecutor`, so the same class works against the root `db` or a
|
|
32
|
+
transaction handle. `DrizzleUnitOfWork` builds a tx-bound scope, guaranteeing nothing
|
|
33
|
+
escapes the transaction:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const uow = new DrizzleUnitOfWork(db, (tx) => ({
|
|
37
|
+
progress: new DrizzleProgressRepository(tx),
|
|
38
|
+
outbox: new DrizzleOutboxRepository(tx),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
await uow.run(async ({ progress, outbox }) => {
|
|
42
|
+
/* ... */
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Error translation
|
|
47
|
+
|
|
48
|
+
`repositories/pg-errors.ts` turns driver failures into domain errors — unique violations
|
|
49
|
+
become `ConflictError`, everything else becomes a `DbQueryError` carrying the SQL text and
|
|
50
|
+
structured pg fields but never the bind params (they can hold PII).
|
|
51
|
+
|
|
52
|
+
## Drift checks
|
|
53
|
+
|
|
54
|
+
`schema/drift.ts` provides compile-time assertions that a table's row type matches its
|
|
55
|
+
domain type from `@headless-lms/core/schemas`. A row field may be wider than the domain's,
|
|
56
|
+
never narrower, missing, or extra. Drift fails `pnpm typecheck` with the offending keys.
|
|
57
|
+
|
|
58
|
+
## Migration workflow
|
|
59
|
+
|
|
60
|
+
Push during dev, generate once at the end.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pnpm db:push # apply schema changes straight to the dev database, no files
|
|
64
|
+
pnpm db:generate # diff against the last committed snapshot → one migration
|
|
65
|
+
pnpm db:migrate # apply pending migrations
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
While iterating locally, use `db:push`. When the schema is settled and ready to ship, run
|
|
69
|
+
`db:generate` once — it diffs against the last committed snapshot, so you get a single
|
|
70
|
+
migration containing everything since the previous release.
|
|
71
|
+
|
|
72
|
+
All three read `DATABASE_URL` from the repo-root `.env`.
|
|
73
|
+
|
|
74
|
+
## Tests
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pnpm test # vitest
|
|
78
|
+
pnpm typecheck
|
|
79
|
+
```
|
package/dist/client.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
|
3
3
|
//#region src/client.d.ts
|
|
4
4
|
/** Transaction executor — the same query surface as the root db.
|
|
5
5
|
* (Promoted from the structure.ts repository's local alias.) */
|
|
6
|
-
type Tx = Parameters<Parameters<NodePgDatabase[
|
|
6
|
+
type Tx = Parameters<Parameters<NodePgDatabase["transaction"]>[0]>[0];
|
|
7
7
|
/** What a Drizzle repository executes against: the root db OR a transaction
|
|
8
8
|
* handle — tx-bound repo instances are how DrizzleUnitOfWork guarantees no
|
|
9
9
|
* statement escapes the transaction. */
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["// Drizzle client + connection. Owns connection pooling (Drizzle does not).\nimport { drizzle, type NodePgDatabase } from
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["// Drizzle client + connection. Owns connection pooling (Drizzle does not).\nimport { drizzle, type NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport pg from \"pg\";\n\n/** Transaction executor — the same query surface as the root db.\n * (Promoted from the structure.ts repository's local alias.) */\nexport type Tx = Parameters<Parameters<NodePgDatabase[\"transaction\"]>[0]>[0];\n\n/** What a Drizzle repository executes against: the root db OR a transaction\n * handle — tx-bound repo instances are how DrizzleUnitOfWork guarantees no\n * statement escapes the transaction. */\nexport type DbExecutor = NodePgDatabase | Tx;\n\n// Centralized schema — re-exported so the db adapter is the single source for tables.\nexport * as schema from \"./schema/index.js\";\n\nexport type Db = ReturnType<typeof createDb>;\n\nexport function createDb(connectionString: string) {\n const pool = new pg.Pool({ connectionString });\n return drizzle(pool);\n}\n"],"mappings":";;;;AAkBA,SAAgB,SAAS,kBAA0B;CAEjD,OAAO,QAAQ,IADE,GAAG,KAAK,EAAE,iBAAiB,CAC1B,CAAC;AACrB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { migrationsFolder, runMigrations } from "./migrate.js";
|
|
|
5
5
|
import { DrizzleAssetsRepository } from "./repositories/assets.js";
|
|
6
6
|
import { DrizzleAutomationRunsRepository, DrizzleAutomationsRepository } from "./repositories/automations.js";
|
|
7
7
|
import { DrizzleContentRepository } from "./repositories/content.js";
|
|
8
|
+
import { DrizzleCourseAnalyticsRepository } from "./repositories/course-analytics.js";
|
|
8
9
|
import { DrizzleCredentialStore, decryptSecret, encryptSecret } from "./repositories/credentials.js";
|
|
9
10
|
import { DrizzleDashboardRepository } from "./repositories/dashboard.js";
|
|
10
11
|
import { DrizzleDiscussionRepository } from "./repositories/discussion.js";
|
|
@@ -19,4 +20,4 @@ import { DrizzleOutboxAppender, DrizzleOutboxStore, OUTBOX_MAX_ATTEMPTS } from "
|
|
|
19
20
|
import { DrizzleProgressRepository } from "./repositories/progress.js";
|
|
20
21
|
import { DrizzleSettingsRepository, deepMerge } from "./repositories/settings.js";
|
|
21
22
|
import { DrizzleStudentsRepository } from "./repositories/students.js";
|
|
22
|
-
export { type Db, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDashboardRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleLearnRepository, DrizzleMembersRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleStudentsRepository, DrizzleUnitOfWork, OUTBOX_MAX_ATTEMPTS, createDb, decryptSecret, deepMerge, encryptSecret, migrationsFolder, orgUserProfileColumns, runMigrations, index_d_exports as schema };
|
|
23
|
+
export { type Db, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCourseAnalyticsRepository, DrizzleCredentialStore, DrizzleDashboardRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleLearnRepository, DrizzleMembersRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleStudentsRepository, DrizzleUnitOfWork, OUTBOX_MAX_ATTEMPTS, createDb, decryptSecret, deepMerge, encryptSecret, migrationsFolder, orgUserProfileColumns, runMigrations, index_d_exports as schema };
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,9 @@ import { DrizzleUnitOfWork } from "./unit-of-work.js";
|
|
|
4
4
|
import { migrationsFolder, runMigrations } from "./migrate.js";
|
|
5
5
|
import { DrizzleAssetsRepository } from "./repositories/assets.js";
|
|
6
6
|
import { DrizzleAutomationRunsRepository, DrizzleAutomationsRepository } from "./repositories/automations.js";
|
|
7
|
+
import { DrizzleSettingsRepository, deepMerge } from "./repositories/settings.js";
|
|
7
8
|
import { DrizzleContentRepository } from "./repositories/content.js";
|
|
9
|
+
import { DrizzleCourseAnalyticsRepository } from "./repositories/course-analytics.js";
|
|
8
10
|
import { DrizzleCredentialStore, decryptSecret, encryptSecret } from "./repositories/credentials.js";
|
|
9
11
|
import { DrizzleDashboardRepository } from "./repositories/dashboard.js";
|
|
10
12
|
import { orgUserProfileColumns } from "./repositories/org-user-profile.js";
|
|
@@ -17,6 +19,5 @@ import { DrizzleMembersRepository } from "./repositories/members.js";
|
|
|
17
19
|
import { DrizzleOrganizationsRepository } from "./repositories/organizations.js";
|
|
18
20
|
import { DrizzleOutboxAppender, DrizzleOutboxStore, OUTBOX_MAX_ATTEMPTS } from "./repositories/outbox.js";
|
|
19
21
|
import { DrizzleProgressRepository } from "./repositories/progress.js";
|
|
20
|
-
import { DrizzleSettingsRepository, deepMerge } from "./repositories/settings.js";
|
|
21
22
|
import { DrizzleStudentsRepository } from "./repositories/students.js";
|
|
22
|
-
export { DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDashboardRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleLearnRepository, DrizzleMembersRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleStudentsRepository, DrizzleUnitOfWork, OUTBOX_MAX_ATTEMPTS, createDb, decryptSecret, deepMerge, encryptSecret, migrationsFolder, orgUserProfileColumns, runMigrations, schema_exports as schema };
|
|
23
|
+
export { DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCourseAnalyticsRepository, DrizzleCredentialStore, DrizzleDashboardRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleLearnRepository, DrizzleMembersRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleStudentsRepository, DrizzleUnitOfWork, OUTBOX_MAX_ATTEMPTS, createDb, decryptSecret, deepMerge, encryptSecret, migrationsFolder, orgUserProfileColumns, runMigrations, schema_exports as schema };
|
package/dist/migrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.js","names":[],"sources":["../src/migrate.ts"],"sourcesContent":["// Runs the drizzle migrations shipped inside this package against the\n// installation's database. Fails loudly and legibly — this is the first\n// command every new installation runs.\nimport { fileURLToPath } from
|
|
1
|
+
{"version":3,"file":"migrate.js","names":[],"sources":["../src/migrate.ts"],"sourcesContent":["// Runs the drizzle migrations shipped inside this package against the\n// installation's database. Fails loudly and legibly — this is the first\n// command every new installation runs.\nimport { fileURLToPath } from \"node:url\";\nimport pg from \"pg\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { migrate } from \"drizzle-orm/node-postgres/migrator\";\n\n/** drizzle/ sits at the package root: ../drizzle from src/ AND dist/. */\nexport function migrationsFolder(): string {\n return fileURLToPath(new URL(\"../drizzle/\", import.meta.url));\n}\n\nexport async function runMigrations(databaseUrl: string): Promise<void> {\n if (!databaseUrl) {\n throw new Error(\n \"DATABASE_URL is not set. Put it in your .env (e.g. postgres://postgres:postgres@localhost:8005/headless_lms) and re-run.\",\n );\n }\n const pool = new pg.Pool({ connectionString: databaseUrl, max: 1 });\n try {\n await migrate(drizzle(pool), { migrationsFolder: migrationsFolder() });\n } catch (err) {\n throw new Error(\n `Migration failed against ${databaseUrl.replace(/\\/\\/.*@/, \"//***@\")}: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err },\n );\n } finally {\n await pool.end();\n }\n}\n"],"mappings":";;;;;;AASA,SAAgB,mBAA2B;CACzC,OAAO,cAAc,IAAI,IAAI,eAAe,OAAO,KAAK,GAAG,CAAC;AAC9D;AAEA,eAAsB,cAAc,aAAoC;CACtE,IAAI,CAAC,aACH,MAAM,IAAI,MACR,0HACF;CAEF,MAAM,OAAO,IAAI,GAAG,KAAK;EAAE,kBAAkB;EAAa,KAAK;CAAE,CAAC;CAClE,IAAI;EACF,MAAM,QAAQ,QAAQ,IAAI,GAAG,EAAE,kBAAkB,iBAAiB,EAAE,CAAC;CACvE,SAAS,KAAK;EACZ,MAAM,IAAI,MACR,4BAA4B,YAAY,QAAQ,WAAW,QAAQ,EAAE,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KACxH,EAAE,OAAO,IAAI,CACf;CACF,UAAU;EACR,MAAM,KAAK,IAAI;CACjB;AACF"}
|
|
@@ -9,7 +9,7 @@ declare class DrizzleAssetsRepository implements AssetsRepository {
|
|
|
9
9
|
insert(orgId: string, asset: Asset): Promise<Asset>;
|
|
10
10
|
list(orgId: string, query: AssetsQuery): Promise<Page<Asset>>;
|
|
11
11
|
findById(orgId: string, id: string): Promise<Asset | null>;
|
|
12
|
-
update(id: string, patch: Partial<Pick<Asset,
|
|
12
|
+
update(id: string, patch: Partial<Pick<Asset, "size" | "contentType" | "status">>): Promise<Asset | null>;
|
|
13
13
|
delete(id: string): Promise<boolean>;
|
|
14
14
|
}
|
|
15
15
|
//#endregion
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assets } from "../schema/assets.js";
|
|
2
|
+
import { translateDbErrors } from "./pg-errors.js";
|
|
2
3
|
import { and, asc, desc, eq, ilike, sql } from "drizzle-orm";
|
|
3
4
|
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
4
5
|
//#region src/repositories/assets.ts
|
|
@@ -13,7 +14,8 @@ function toAsset(row) {
|
|
|
13
14
|
size: row.size,
|
|
14
15
|
status: row.status,
|
|
15
16
|
uploadedBy: row.uploadedBy,
|
|
16
|
-
createdAt: row.createdAt
|
|
17
|
+
createdAt: row.createdAt,
|
|
18
|
+
updatedAt: row.updatedAt
|
|
17
19
|
};
|
|
18
20
|
}
|
|
19
21
|
const SORT_COLUMNS = {
|
|
@@ -84,6 +86,7 @@ var DrizzleAssetsRepository = class {
|
|
|
84
86
|
return (await this.db.delete(assets).where(eq(assets.id, id)).returning({ id: assets.id })).length > 0;
|
|
85
87
|
}
|
|
86
88
|
};
|
|
89
|
+
translateDbErrors(DrizzleAssetsRepository);
|
|
87
90
|
//#endregion
|
|
88
91
|
export { DrizzleAssetsRepository };
|
|
89
92
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.js","names":[],"sources":["../../src/repositories/assets.ts"],"sourcesContent":["// assets — Drizzle repository (implements the core outbound port).\nimport { and, eq, ilike, sql, asc, desc, type SQL } from
|
|
1
|
+
{"version":3,"file":"assets.js","names":[],"sources":["../../src/repositories/assets.ts"],"sourcesContent":["// assets — Drizzle repository (implements the core outbound port).\nimport { and, eq, ilike, sql, asc, desc, type SQL } from \"drizzle-orm\";\nimport type { AssetsRepository } from \"@headless-lms/core/assets\";\nimport type {\n Asset,\n AssetKind,\n AssetStatus,\n AssetsQuery,\n Logger,\n Page,\n} from \"@headless-lms/core/types\";\nimport { assets } from \"../schema/assets.js\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport type { DbExecutor } from \"../client.js\";\nimport { translateDbErrors } from \"./pg-errors.js\";\n\ntype Row = typeof assets.$inferSelect;\n\nfunction toAsset(row: Row): Asset {\n return {\n orgId: row.orgId,\n id: row.id,\n key: row.key,\n kind: row.kind as AssetKind,\n filename: row.filename,\n contentType: row.contentType,\n size: row.size,\n status: row.status as AssetStatus,\n uploadedBy: row.uploadedBy,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\n// Whitelisted sort columns; anything else falls back to newest-first.\nconst SORT_COLUMNS = {\n filename: assets.filename,\n size: assets.size,\n createdAt: assets.createdAt,\n} as const;\n\nexport class DrizzleAssetsRepository implements AssetsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, asset: Asset): Promise<Asset> {\n if (asset.orgId !== orgId) {\n throw new Error(\"asset org mismatch\");\n }\n const [row] = await this.db\n .insert(assets)\n .values({\n id: asset.id,\n orgId: asset.orgId,\n key: asset.key,\n kind: asset.kind,\n filename: asset.filename,\n contentType: asset.contentType,\n size: asset.size,\n status: asset.status,\n uploadedBy: asset.uploadedBy,\n createdAt: new Date(asset.createdAt),\n })\n .returning();\n if (!row) {\n throw new Error(\"failed to insert asset\");\n }\n return toAsset(row);\n }\n\n async list(orgId: string, query: AssetsQuery): Promise<Page<Asset>> {\n const filters: (SQL | undefined)[] = [eq(assets.orgId, orgId)];\n if (query.kind) {\n filters.push(eq(assets.kind, query.kind));\n }\n const q = query.search?.trim();\n if (q) {\n filters.push(ilike(assets.filename, `%${q}%`));\n }\n const where = and(...filters);\n\n const orderBy = (() => {\n if (!query.sort) {\n return desc(assets.createdAt);\n }\n const descending = query.sort.startsWith(\"-\");\n const field = descending ? query.sort.slice(1) : query.sort;\n const col = SORT_COLUMNS[field as keyof typeof SORT_COLUMNS] ?? assets.createdAt;\n return descending ? desc(col) : asc(col);\n })();\n\n const offset = (query.page - 1) * query.pageSize;\n const rows = await this.db\n .select()\n .from(assets)\n .where(where)\n .orderBy(orderBy)\n .limit(query.pageSize)\n .offset(offset);\n\n const counted = await this.db\n .select({ value: sql<number>`count(*)::int` })\n .from(assets)\n .where(where);\n\n return {\n rows: rows.map(toAsset),\n total: counted[0]?.value ?? 0,\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async findById(orgId: string, id: string): Promise<Asset | null> {\n const [row] = await this.db\n .select()\n .from(assets)\n .where(and(eq(assets.orgId, orgId), eq(assets.id, id)))\n .limit(1);\n return row ? toAsset(row) : null;\n }\n\n async update(\n id: string,\n patch: Partial<Pick<Asset, \"size\" | \"contentType\" | \"status\">>,\n ): Promise<Asset | null> {\n const [row] = await this.db\n .update(assets)\n .set({\n ...(patch.size !== undefined ? { size: patch.size } : {}),\n ...(patch.contentType !== undefined ? { contentType: patch.contentType } : {}),\n ...(patch.status !== undefined ? { status: patch.status } : {}),\n })\n .where(eq(assets.id, id))\n .returning();\n return row ? toAsset(row) : null;\n }\n\n async delete(id: string): Promise<boolean> {\n const deleted = await this.db\n .delete(assets)\n .where(eq(assets.id, id))\n .returning({ id: assets.id });\n return deleted.length > 0;\n }\n}\ntranslateDbErrors(DrizzleAssetsRepository);\n"],"mappings":";;;;;AAkBA,SAAS,QAAQ,KAAiB;CAChC,OAAO;EACL,OAAO,IAAI;EACX,IAAI,IAAI;EACR,KAAK,IAAI;EACT,MAAM,IAAI;EACV,UAAU,IAAI;EACd,aAAa,IAAI;EACjB,MAAM,IAAI;EACV,QAAQ,IAAI;EACZ,YAAY,IAAI;EAChB,WAAW,IAAI;EACf,WAAW,IAAI;CACjB;AACF;AAGA,MAAM,eAAe;CACnB,UAAU,OAAO;CACjB,MAAM,OAAO;CACb,WAAW,OAAO;AACpB;AAEA,IAAa,0BAAb,MAAiE;CAE5C;CACA;CAFnB,YACE,IACA,SAAkC,YAClC;EAFiB,KAAA,KAAA;EACA,KAAA,SAAA;CAChB;CAEH,MAAM,OAAO,OAAe,OAA8B;EACxD,IAAI,MAAM,UAAU,OAClB,MAAM,IAAI,MAAM,oBAAoB;EAEtC,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO,MAAM,CAAC,CACd,OAAO;GACN,IAAI,MAAM;GACV,OAAO,MAAM;GACb,KAAK,MAAM;GACX,MAAM,MAAM;GACZ,UAAU,MAAM;GAChB,aAAa,MAAM;GACnB,MAAM,MAAM;GACZ,QAAQ,MAAM;GACd,YAAY,MAAM;GAClB,WAAW,IAAI,KAAK,MAAM,SAAS;EACrC,CAAC,CAAC,CACD,UAAU;EACb,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,wBAAwB;EAE1C,OAAO,QAAQ,GAAG;CACpB;CAEA,MAAM,KAAK,OAAe,OAA0C;EAClE,MAAM,UAA+B,CAAC,GAAG,OAAO,OAAO,KAAK,CAAC;EAC7D,IAAI,MAAM,MACR,QAAQ,KAAK,GAAG,OAAO,MAAM,MAAM,IAAI,CAAC;EAE1C,MAAM,IAAI,MAAM,QAAQ,KAAK;EAC7B,IAAI,GACF,QAAQ,KAAK,MAAM,OAAO,UAAU,IAAI,EAAE,EAAE,CAAC;EAE/C,MAAM,QAAQ,IAAI,GAAG,OAAO;EAE5B,MAAM,iBAAiB;GACrB,IAAI,CAAC,MAAM,MACT,OAAO,KAAK,OAAO,SAAS;GAE9B,MAAM,aAAa,MAAM,KAAK,WAAW,GAAG;GAC5C,MAAM,QAAQ,aAAa,MAAM,KAAK,MAAM,CAAC,IAAI,MAAM;GACvD,MAAM,MAAM,aAAa,UAAuC,OAAO;GACvE,OAAO,aAAa,KAAK,GAAG,IAAI,IAAI,GAAG;EACzC,EAAA,CAAG;EAEH,MAAM,UAAU,MAAM,OAAO,KAAK,MAAM;EACxC,MAAM,OAAO,MAAM,KAAK,GACrB,OAAO,CAAC,CACR,KAAK,MAAM,CAAC,CACZ,MAAM,KAAK,CAAC,CACZ,QAAQ,OAAO,CAAC,CAChB,MAAM,MAAM,QAAQ,CAAC,CACrB,OAAO,MAAM;EAEhB,MAAM,UAAU,MAAM,KAAK,GACxB,OAAO,EAAE,OAAO,GAAW,gBAAgB,CAAC,CAAC,CAC7C,KAAK,MAAM,CAAC,CACZ,MAAM,KAAK;EAEd,OAAO;GACL,MAAM,KAAK,IAAI,OAAO;GACtB,OAAO,QAAQ,EAAE,EAAE,SAAS;GAC5B,MAAM,MAAM;GACZ,UAAU,MAAM;EAClB;CACF;CAEA,MAAM,SAAS,OAAe,IAAmC;EAC/D,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO,CAAC,CACR,KAAK,MAAM,CAAC,CACZ,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,GAAG,GAAG,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CACtD,MAAM,CAAC;EACV,OAAO,MAAM,QAAQ,GAAG,IAAI;CAC9B;CAEA,MAAM,OACJ,IACA,OACuB;EACvB,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO,MAAM,CAAC,CACd,IAAI;GACH,GAAI,MAAM,SAAS,KAAA,IAAY,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;GACvD,GAAI,MAAM,gBAAgB,KAAA,IAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;GAC5E,GAAI,MAAM,WAAW,KAAA,IAAY,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;EAC/D,CAAC,CAAC,CACD,MAAM,GAAG,OAAO,IAAI,EAAE,CAAC,CAAC,CACxB,UAAU;EACb,OAAO,MAAM,QAAQ,GAAG,IAAI;CAC9B;CAEA,MAAM,OAAO,IAA8B;EAKzC,QAAO,MAJe,KAAK,GACxB,OAAO,MAAM,CAAC,CACd,MAAM,GAAG,OAAO,IAAI,EAAE,CAAC,CAAC,CACxB,UAAU,EAAE,IAAI,OAAO,GAAG,CAAC,EAAA,CACf,SAAS;CAC1B;AACF;AACA,kBAAkB,uBAAuB"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { DbExecutor } from "../client.js";
|
|
2
2
|
import { Logger } from "@headless-lms/core/shared/ports";
|
|
3
|
-
import { Automation, AutomationRun, AutomationRunsQuery, AutomationRunsRepository, AutomationsRepository,
|
|
3
|
+
import { Automation, AutomationKind, AutomationRun, AutomationRunsQuery, AutomationRunsRepository, AutomationsRepository, NewAutomation, NewAutomationRun, Page, UpdateAutomationInput } from "@headless-lms/core/automations";
|
|
4
4
|
//#region src/repositories/automations.d.ts
|
|
5
5
|
declare class DrizzleAutomationsRepository implements AutomationsRepository {
|
|
6
6
|
private readonly db;
|
|
7
7
|
private readonly logger;
|
|
8
8
|
constructor(db: DbExecutor, logger?: Logger);
|
|
9
|
-
insert(orgId: string, input:
|
|
9
|
+
insert(orgId: string, input: NewAutomation): Promise<Automation>;
|
|
10
10
|
update(orgId: string, id: string, patch: UpdateAutomationInput): Promise<Automation | null>;
|
|
11
11
|
delete(orgId: string, id: string): Promise<Automation | null>;
|
|
12
12
|
findById(orgId: string, id: string): Promise<Automation | null>;
|
|
13
|
-
listByOrg(orgId: string): Promise<Automation[]>;
|
|
13
|
+
listByOrg(orgId: string, kind?: AutomationKind): Promise<Automation[]>;
|
|
14
14
|
listByTrigger(orgId: string, trigger: string): Promise<Automation[]>;
|
|
15
15
|
}
|
|
16
16
|
declare class DrizzleAutomationRunsRepository implements AutomationRunsRepository {
|
|
@@ -18,9 +18,11 @@ declare class DrizzleAutomationRunsRepository implements AutomationRunsRepositor
|
|
|
18
18
|
private readonly logger;
|
|
19
19
|
constructor(db: DbExecutor, logger?: Logger);
|
|
20
20
|
insert(orgId: string, run: NewAutomationRun): Promise<AutomationRun | null>;
|
|
21
|
+
findById(orgId: string, id: string): Promise<AutomationRun | null>;
|
|
22
|
+
restart(orgId: string, id: string, startedAt: Date): Promise<AutomationRun | null>;
|
|
21
23
|
recordOutcome(orgId: string, id: string, outcome: {
|
|
22
|
-
status: AutomationRun[
|
|
23
|
-
actionResults: AutomationRun[
|
|
24
|
+
status: AutomationRun["status"];
|
|
25
|
+
actionResults: AutomationRun["actionResults"];
|
|
24
26
|
finishedAt: Date;
|
|
25
27
|
}): Promise<AutomationRun | null>;
|
|
26
28
|
list(orgId: string, automationId: string, query: AutomationRunsQuery): Promise<Page<AutomationRun>>;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { automationRuns, automations } from "../schema/automations.js";
|
|
2
2
|
import "../schema/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import { translateDbErrors } from "./pg-errors.js";
|
|
4
|
+
import { and, arrayContains, asc, desc, eq, ne, sql } from "drizzle-orm";
|
|
4
5
|
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
5
6
|
//#region src/repositories/automations.ts
|
|
6
7
|
const automationSelection = {
|
|
7
8
|
orgId: automations.orgId,
|
|
8
9
|
id: automations.id,
|
|
9
10
|
name: automations.name,
|
|
11
|
+
kind: automations.kind,
|
|
10
12
|
description: automations.description,
|
|
11
|
-
|
|
13
|
+
triggers: automations.triggers,
|
|
12
14
|
actions: automations.actions,
|
|
13
15
|
enabled: automations.enabled,
|
|
14
16
|
createdAt: automations.createdAt,
|
|
@@ -19,8 +21,9 @@ function toAutomation(row) {
|
|
|
19
21
|
orgId: row.orgId,
|
|
20
22
|
id: row.id,
|
|
21
23
|
name: row.name,
|
|
24
|
+
kind: row.kind,
|
|
22
25
|
description: row.description,
|
|
23
|
-
|
|
26
|
+
triggers: row.triggers,
|
|
24
27
|
actions: row.actions,
|
|
25
28
|
enabled: row.enabled,
|
|
26
29
|
createdAt: row.createdAt,
|
|
@@ -38,8 +41,9 @@ var DrizzleAutomationsRepository = class {
|
|
|
38
41
|
const [inserted] = await this.db.insert(automations).values({
|
|
39
42
|
orgId,
|
|
40
43
|
name: input.name,
|
|
44
|
+
kind: input.kind,
|
|
41
45
|
description: input.description,
|
|
42
|
-
|
|
46
|
+
triggers: input.triggers,
|
|
43
47
|
actions: input.actions
|
|
44
48
|
}).returning({ id: automations.id });
|
|
45
49
|
if (!inserted) throw new Error("failed to insert automation");
|
|
@@ -51,7 +55,7 @@ var DrizzleAutomationsRepository = class {
|
|
|
51
55
|
const set = { updatedAt: /* @__PURE__ */ new Date() };
|
|
52
56
|
if (patch.name !== void 0) set.name = patch.name;
|
|
53
57
|
if (patch.description !== void 0) set.description = patch.description;
|
|
54
|
-
if (patch.
|
|
58
|
+
if (patch.triggers !== void 0) set.triggers = patch.triggers;
|
|
55
59
|
if (patch.actions !== void 0) set.actions = patch.actions;
|
|
56
60
|
if (patch.enabled !== void 0) set.enabled = patch.enabled;
|
|
57
61
|
const [updated] = await this.db.update(automations).set(set).where(and(eq(automations.orgId, orgId), eq(automations.id, id))).returning({ id: automations.id });
|
|
@@ -66,11 +70,13 @@ var DrizzleAutomationsRepository = class {
|
|
|
66
70
|
const [row] = await this.db.select(automationSelection).from(automations).where(and(eq(automations.orgId, orgId), eq(automations.id, id))).limit(1);
|
|
67
71
|
return row ? toAutomation(row) : null;
|
|
68
72
|
}
|
|
69
|
-
async listByOrg(orgId) {
|
|
70
|
-
|
|
73
|
+
async listByOrg(orgId, kind) {
|
|
74
|
+
const conditions = [eq(automations.orgId, orgId)];
|
|
75
|
+
if (kind) conditions.push(eq(automations.kind, kind));
|
|
76
|
+
return (await this.db.select(automationSelection).from(automations).where(and(...conditions)).orderBy(desc(automations.createdAt))).map(toAutomation);
|
|
71
77
|
}
|
|
72
78
|
async listByTrigger(orgId, trigger) {
|
|
73
|
-
return (await this.db.select(automationSelection).from(automations).where(and(eq(automations.orgId, orgId),
|
|
79
|
+
return (await this.db.select(automationSelection).from(automations).where(and(eq(automations.orgId, orgId), arrayContains(automations.triggers, [trigger]))).orderBy(desc(automations.createdAt))).map(toAutomation);
|
|
74
80
|
}
|
|
75
81
|
};
|
|
76
82
|
const runSelection = {
|
|
@@ -83,7 +89,9 @@ const runSelection = {
|
|
|
83
89
|
status: automationRuns.status,
|
|
84
90
|
actionResults: automationRuns.actionResults,
|
|
85
91
|
startedAt: automationRuns.startedAt,
|
|
86
|
-
finishedAt: automationRuns.finishedAt
|
|
92
|
+
finishedAt: automationRuns.finishedAt,
|
|
93
|
+
createdAt: automationRuns.createdAt,
|
|
94
|
+
updatedAt: automationRuns.updatedAt
|
|
87
95
|
};
|
|
88
96
|
function toAutomationRun(row) {
|
|
89
97
|
return {
|
|
@@ -96,7 +104,9 @@ function toAutomationRun(row) {
|
|
|
96
104
|
status: row.status,
|
|
97
105
|
actionResults: row.actionResults,
|
|
98
106
|
startedAt: row.startedAt,
|
|
99
|
-
finishedAt: row.finishedAt
|
|
107
|
+
finishedAt: row.finishedAt,
|
|
108
|
+
createdAt: row.createdAt,
|
|
109
|
+
updatedAt: row.updatedAt
|
|
100
110
|
};
|
|
101
111
|
}
|
|
102
112
|
const sortColumns = {
|
|
@@ -129,6 +139,19 @@ var DrizzleAutomationRunsRepository = class {
|
|
|
129
139
|
] }).returning(runSelection);
|
|
130
140
|
return inserted ? toAutomationRun(inserted) : null;
|
|
131
141
|
}
|
|
142
|
+
async findById(orgId, id) {
|
|
143
|
+
const [row] = await this.db.select(runSelection).from(automationRuns).where(and(eq(automationRuns.orgId, orgId), eq(automationRuns.id, id))).limit(1);
|
|
144
|
+
return row ? toAutomationRun(row) : null;
|
|
145
|
+
}
|
|
146
|
+
async restart(orgId, id, startedAt) {
|
|
147
|
+
const [restarted] = await this.db.update(automationRuns).set({
|
|
148
|
+
status: "running",
|
|
149
|
+
actionResults: [],
|
|
150
|
+
startedAt,
|
|
151
|
+
finishedAt: null
|
|
152
|
+
}).where(and(eq(automationRuns.orgId, orgId), eq(automationRuns.id, id), ne(automationRuns.status, "running"))).returning(runSelection);
|
|
153
|
+
return restarted ? toAutomationRun(restarted) : null;
|
|
154
|
+
}
|
|
132
155
|
async recordOutcome(orgId, id, outcome) {
|
|
133
156
|
const [updated] = await this.db.update(automationRuns).set({
|
|
134
157
|
status: outcome.status,
|
|
@@ -159,6 +182,8 @@ var DrizzleAutomationRunsRepository = class {
|
|
|
159
182
|
};
|
|
160
183
|
}
|
|
161
184
|
};
|
|
185
|
+
translateDbErrors(DrizzleAutomationsRepository);
|
|
186
|
+
translateDbErrors(DrizzleAutomationRunsRepository);
|
|
162
187
|
//#endregion
|
|
163
188
|
export { DrizzleAutomationRunsRepository, DrizzleAutomationsRepository };
|
|
164
189
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automations.js","names":[],"sources":["../../src/repositories/automations.ts"],"sourcesContent":["// automations — Drizzle repositories (implement the core outbound ports).\n// Org-scoped: every method takes the domain `organizations.id` and constrains\n// its queries to that tenant. `Automation`/`AutomationRun` carry no `orgId`\n// column of their own in the domain type (mirrors the `Entitlement` shape) —\n// `AutomationRun` is the one exception, since it's the run's own `orgId`\n// field the service reads back off `handle`'s event.\nimport { and, asc, desc, eq, sql, type SQL } from 'drizzle-orm';\nimport type { DbExecutor } from '../client.js';\nimport type {\n Automation,\n AutomationRun,\n AutomationRunsQuery,\n AutomationRunsRepository,\n AutomationsRepository,\n CreateAutomationInput,\n NewAutomationRun,\n Page,\n UpdateAutomationInput,\n} from '@headless-lms/core/automations';\nimport { automations, automationRuns } from '../schema/index.js';\nimport type { Logger } from '@headless-lms/core/shared/ports';\nimport { noopLogger } from '@headless-lms/core/shared/logger';\n\nconst automationSelection = {\n orgId: automations.orgId,\n id: automations.id,\n name: automations.name,\n description: automations.description,\n trigger: automations.trigger,\n actions: automations.actions,\n enabled: automations.enabled,\n createdAt: automations.createdAt,\n updatedAt: automations.updatedAt,\n};\n\ninterface AutomationRow {\n orgId: string;\n id: string;\n name: string;\n description: string | null;\n trigger: string;\n actions: Automation['actions'];\n enabled: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction toAutomation(row: AutomationRow): Automation {\n return {\n orgId: row.orgId,\n id: row.id,\n name: row.name,\n description: row.description,\n trigger: row.trigger,\n actions: row.actions,\n enabled: row.enabled,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport class DrizzleAutomationsRepository implements AutomationsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, input: CreateAutomationInput): Promise<Automation> {\n const [inserted] = await this.db\n .insert(automations)\n .values({\n orgId,\n name: input.name,\n description: input.description,\n trigger: input.trigger,\n actions: input.actions,\n })\n .returning({ id: automations.id });\n if (!inserted) {\n throw new Error('failed to insert automation');\n }\n const created = await this.findById(orgId, inserted.id);\n if (!created) {\n throw new Error('failed to load created automation');\n }\n return created;\n }\n\n async update(orgId: string, id: string, patch: UpdateAutomationInput): Promise<Automation | null> {\n const set: Record<string, unknown> = { updatedAt: new Date() };\n if (patch.name !== undefined) {\n set.name = patch.name;\n }\n if (patch.description !== undefined) {\n set.description = patch.description;\n }\n if (patch.trigger !== undefined) {\n set.trigger = patch.trigger;\n }\n if (patch.actions !== undefined) {\n set.actions = patch.actions;\n }\n if (patch.enabled !== undefined) {\n set.enabled = patch.enabled;\n }\n\n const [updated] = await this.db\n .update(automations)\n .set(set)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .returning({ id: automations.id });\n if (!updated) {\n return null;\n }\n return this.findById(orgId, id);\n }\n\n async delete(orgId: string, id: string): Promise<Automation | null> {\n const [deleted] = await this.db\n .delete(automations)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .returning(automationSelection);\n return deleted ? toAutomation(deleted) : null;\n }\n\n async findById(orgId: string, id: string): Promise<Automation | null> {\n const [row] = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .limit(1);\n return row ? toAutomation(row) : null;\n }\n\n async listByOrg(orgId: string): Promise<Automation[]> {\n const rows = await this.db\n .select(automationSelection)\n .from(automations)\n .where(eq(automations.orgId, orgId))\n .orderBy(desc(automations.createdAt));\n return rows.map(toAutomation);\n }\n\n async listByTrigger(orgId: string, trigger: string): Promise<Automation[]> {\n const rows = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(eq(automations.orgId, orgId), eq(automations.trigger, trigger)))\n .orderBy(desc(automations.createdAt));\n return rows.map(toAutomation);\n }\n}\n\nconst runSelection = {\n id: automationRuns.id,\n orgId: automationRuns.orgId,\n automationId: automationRuns.automationId,\n trigger: automationRuns.trigger,\n eventId: automationRuns.eventId,\n event: automationRuns.event,\n status: automationRuns.status,\n actionResults: automationRuns.actionResults,\n startedAt: automationRuns.startedAt,\n finishedAt: automationRuns.finishedAt,\n};\n\ninterface RunRow {\n id: string;\n orgId: string;\n automationId: string;\n trigger: string;\n eventId: string;\n event: AutomationRun['event'];\n status: AutomationRun['status'];\n actionResults: AutomationRun['actionResults'];\n startedAt: Date;\n finishedAt: Date | null;\n}\n\nfunction toAutomationRun(row: RunRow): AutomationRun {\n return {\n id: row.id,\n orgId: row.orgId,\n automationId: row.automationId,\n trigger: row.trigger,\n eventId: row.eventId,\n event: row.event,\n status: row.status,\n actionResults: row.actionResults,\n startedAt: row.startedAt,\n finishedAt: row.finishedAt,\n };\n}\n\n// Sortable columns by the client-facing field name. `-field` = desc.\nconst sortColumns = {\n startedAt: automationRuns.startedAt,\n finishedAt: automationRuns.finishedAt,\n status: automationRuns.status,\n} as const;\n\nexport class DrizzleAutomationRunsRepository implements AutomationRunsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, run: NewAutomationRun): Promise<AutomationRun | null> {\n const [inserted] = await this.db\n .insert(automationRuns)\n .values({\n orgId,\n automationId: run.automationId,\n trigger: run.trigger,\n eventId: run.event.id,\n event: run.event,\n status: run.status,\n actionResults: run.actionResults,\n startedAt: run.startedAt,\n finishedAt: run.finishedAt,\n })\n // A redelivered trigger event hits the unique (org, automation, event) index; no row comes back.\n .onConflictDoNothing({\n target: [automationRuns.orgId, automationRuns.automationId, automationRuns.eventId],\n })\n .returning(runSelection);\n return inserted ? toAutomationRun(inserted) : null;\n }\n\n async recordOutcome(\n orgId: string,\n id: string,\n outcome: {\n status: AutomationRun['status'];\n actionResults: AutomationRun['actionResults'];\n finishedAt: Date;\n },\n ): Promise<AutomationRun | null> {\n const [updated] = await this.db\n .update(automationRuns)\n .set({\n status: outcome.status,\n actionResults: outcome.actionResults,\n finishedAt: outcome.finishedAt,\n })\n .where(and(eq(automationRuns.orgId, orgId), eq(automationRuns.id, id)))\n .returning(runSelection);\n return updated ? toAutomationRun(updated) : null;\n }\n\n async list(orgId: string, automationId: string, query: AutomationRunsQuery): Promise<Page<AutomationRun>> {\n const conditions: SQL[] = [\n eq(automationRuns.orgId, orgId),\n eq(automationRuns.automationId, automationId),\n ];\n if (query.status) {\n conditions.push(eq(automationRuns.status, query.status));\n }\n const where = and(...conditions);\n\n // Sort: `-field` for descending; default to most-recently started first.\n let orderBy: SQL;\n if (query.sort) {\n const isDesc = query.sort.startsWith('-');\n const field = (isDesc ? query.sort.slice(1) : query.sort) as keyof typeof sortColumns;\n const col = sortColumns[field] ?? automationRuns.startedAt;\n orderBy = isDesc ? desc(col) : asc(col);\n } else {\n orderBy = desc(automationRuns.startedAt);\n }\n\n const offset = (query.page - 1) * query.pageSize;\n\n const rows = await this.db\n .select(runSelection)\n .from(automationRuns)\n .where(where)\n .orderBy(orderBy)\n .limit(query.pageSize)\n .offset(offset);\n\n const [{ total } = { total: 0 }] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(automationRuns)\n .where(where);\n\n return {\n rows: rows.map(toAutomationRun),\n total,\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"automations.js","names":[],"sources":["../../src/repositories/automations.ts"],"sourcesContent":["// automations — Drizzle repositories (implement the core outbound ports).\n// Org-scoped: every method takes the domain `organizations.id` and constrains\n// its queries to that tenant. `Automation`/`AutomationRun` carry no `orgId`\n// column of their own in the domain type (mirrors the `Entitlement` shape) —\n// `AutomationRun` is the one exception, since it's the run's own `orgId`\n// field the service reads back off `handle`'s event.\nimport { and, arrayContains, asc, desc, eq, ne, sql, type SQL } from \"drizzle-orm\";\nimport type { DbExecutor } from \"../client.js\";\nimport type {\n Automation,\n AutomationKind,\n AutomationRun,\n AutomationRunsQuery,\n AutomationRunsRepository,\n AutomationsRepository,\n NewAutomation,\n NewAutomationRun,\n Page,\n UpdateAutomationInput,\n} from \"@headless-lms/core/automations\";\nimport { automations, automationRuns } from \"../schema/index.js\";\nimport type { Logger } from \"@headless-lms/core/shared/ports\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport { translateDbErrors } from \"./pg-errors.js\";\n\nconst automationSelection = {\n orgId: automations.orgId,\n id: automations.id,\n name: automations.name,\n kind: automations.kind,\n description: automations.description,\n triggers: automations.triggers,\n actions: automations.actions,\n enabled: automations.enabled,\n createdAt: automations.createdAt,\n updatedAt: automations.updatedAt,\n};\n\ninterface AutomationRow {\n orgId: string;\n id: string;\n name: string;\n kind: AutomationKind;\n description: string | null;\n triggers: string[];\n actions: Automation[\"actions\"];\n enabled: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction toAutomation(row: AutomationRow): Automation {\n return {\n orgId: row.orgId,\n id: row.id,\n name: row.name,\n kind: row.kind,\n description: row.description,\n triggers: row.triggers,\n actions: row.actions,\n enabled: row.enabled,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport class DrizzleAutomationsRepository implements AutomationsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, input: NewAutomation): Promise<Automation> {\n const [inserted] = await this.db\n .insert(automations)\n .values({\n orgId,\n name: input.name,\n kind: input.kind,\n description: input.description,\n triggers: input.triggers,\n actions: input.actions,\n })\n .returning({ id: automations.id });\n if (!inserted) {\n throw new Error(\"failed to insert automation\");\n }\n const created = await this.findById(orgId, inserted.id);\n if (!created) {\n throw new Error(\"failed to load created automation\");\n }\n return created;\n }\n\n async update(\n orgId: string,\n id: string,\n patch: UpdateAutomationInput,\n ): Promise<Automation | null> {\n const set: Record<string, unknown> = { updatedAt: new Date() };\n if (patch.name !== undefined) {\n set.name = patch.name;\n }\n if (patch.description !== undefined) {\n set.description = patch.description;\n }\n if (patch.triggers !== undefined) {\n set.triggers = patch.triggers;\n }\n if (patch.actions !== undefined) {\n set.actions = patch.actions;\n }\n if (patch.enabled !== undefined) {\n set.enabled = patch.enabled;\n }\n\n const [updated] = await this.db\n .update(automations)\n .set(set)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .returning({ id: automations.id });\n if (!updated) {\n return null;\n }\n return this.findById(orgId, id);\n }\n\n async delete(orgId: string, id: string): Promise<Automation | null> {\n const [deleted] = await this.db\n .delete(automations)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .returning(automationSelection);\n return deleted ? toAutomation(deleted) : null;\n }\n\n async findById(orgId: string, id: string): Promise<Automation | null> {\n const [row] = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .limit(1);\n return row ? toAutomation(row) : null;\n }\n\n async listByOrg(orgId: string, kind?: AutomationKind): Promise<Automation[]> {\n const conditions: SQL[] = [eq(automations.orgId, orgId)];\n if (kind) {\n conditions.push(eq(automations.kind, kind));\n }\n const rows = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(...conditions))\n .orderBy(desc(automations.createdAt));\n return rows.map(toAutomation);\n }\n\n async listByTrigger(orgId: string, trigger: string): Promise<Automation[]> {\n const rows = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(eq(automations.orgId, orgId), arrayContains(automations.triggers, [trigger])))\n .orderBy(desc(automations.createdAt));\n return rows.map(toAutomation);\n }\n}\n\nconst runSelection = {\n id: automationRuns.id,\n orgId: automationRuns.orgId,\n automationId: automationRuns.automationId,\n trigger: automationRuns.trigger,\n eventId: automationRuns.eventId,\n event: automationRuns.event,\n status: automationRuns.status,\n actionResults: automationRuns.actionResults,\n startedAt: automationRuns.startedAt,\n finishedAt: automationRuns.finishedAt,\n createdAt: automationRuns.createdAt,\n updatedAt: automationRuns.updatedAt,\n};\n\ninterface RunRow {\n id: string;\n orgId: string;\n automationId: string;\n trigger: string;\n eventId: string;\n event: AutomationRun[\"event\"];\n status: AutomationRun[\"status\"];\n actionResults: AutomationRun[\"actionResults\"];\n startedAt: Date;\n finishedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction toAutomationRun(row: RunRow): AutomationRun {\n return {\n id: row.id,\n orgId: row.orgId,\n automationId: row.automationId,\n trigger: row.trigger,\n eventId: row.eventId,\n event: row.event,\n status: row.status,\n actionResults: row.actionResults,\n startedAt: row.startedAt,\n finishedAt: row.finishedAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\n// Sortable columns by the client-facing field name. `-field` = desc.\nconst sortColumns = {\n startedAt: automationRuns.startedAt,\n finishedAt: automationRuns.finishedAt,\n status: automationRuns.status,\n} as const;\n\nexport class DrizzleAutomationRunsRepository implements AutomationRunsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, run: NewAutomationRun): Promise<AutomationRun | null> {\n const [inserted] = await this.db\n .insert(automationRuns)\n .values({\n orgId,\n automationId: run.automationId,\n trigger: run.trigger,\n eventId: run.event.id,\n event: run.event,\n status: run.status,\n actionResults: run.actionResults,\n startedAt: run.startedAt,\n finishedAt: run.finishedAt,\n })\n // A redelivered trigger event hits the unique (org, automation, event) index; no row comes back.\n .onConflictDoNothing({\n target: [automationRuns.orgId, automationRuns.automationId, automationRuns.eventId],\n })\n .returning(runSelection);\n return inserted ? toAutomationRun(inserted) : null;\n }\n\n async findById(orgId: string, id: string): Promise<AutomationRun | null> {\n const [row] = await this.db\n .select(runSelection)\n .from(automationRuns)\n .where(and(eq(automationRuns.orgId, orgId), eq(automationRuns.id, id)))\n .limit(1);\n return row ? toAutomationRun(row) : null;\n }\n\n async restart(orgId: string, id: string, startedAt: Date): Promise<AutomationRun | null> {\n const [restarted] = await this.db\n .update(automationRuns)\n .set({ status: \"running\", actionResults: [], startedAt, finishedAt: null })\n .where(\n and(\n eq(automationRuns.orgId, orgId),\n eq(automationRuns.id, id),\n ne(automationRuns.status, \"running\"),\n ),\n )\n .returning(runSelection);\n return restarted ? toAutomationRun(restarted) : null;\n }\n\n async recordOutcome(\n orgId: string,\n id: string,\n outcome: {\n status: AutomationRun[\"status\"];\n actionResults: AutomationRun[\"actionResults\"];\n finishedAt: Date;\n },\n ): Promise<AutomationRun | null> {\n const [updated] = await this.db\n .update(automationRuns)\n .set({\n status: outcome.status,\n actionResults: outcome.actionResults,\n finishedAt: outcome.finishedAt,\n })\n .where(and(eq(automationRuns.orgId, orgId), eq(automationRuns.id, id)))\n .returning(runSelection);\n return updated ? toAutomationRun(updated) : null;\n }\n\n async list(\n orgId: string,\n automationId: string,\n query: AutomationRunsQuery,\n ): Promise<Page<AutomationRun>> {\n const conditions: SQL[] = [\n eq(automationRuns.orgId, orgId),\n eq(automationRuns.automationId, automationId),\n ];\n if (query.status) {\n conditions.push(eq(automationRuns.status, query.status));\n }\n const where = and(...conditions);\n\n // Sort: `-field` for descending; default to most-recently started first.\n let orderBy: SQL;\n if (query.sort) {\n const isDesc = query.sort.startsWith(\"-\");\n const field = (isDesc ? query.sort.slice(1) : query.sort) as keyof typeof sortColumns;\n const col = sortColumns[field] ?? automationRuns.startedAt;\n orderBy = isDesc ? desc(col) : asc(col);\n } else {\n orderBy = desc(automationRuns.startedAt);\n }\n\n const offset = (query.page - 1) * query.pageSize;\n\n const rows = await this.db\n .select(runSelection)\n .from(automationRuns)\n .where(where)\n .orderBy(orderBy)\n .limit(query.pageSize)\n .offset(offset);\n\n const [{ total } = { total: 0 }] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(automationRuns)\n .where(where);\n\n return {\n rows: rows.map(toAutomationRun),\n total,\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n}\ntranslateDbErrors(DrizzleAutomationsRepository);\ntranslateDbErrors(DrizzleAutomationRunsRepository);\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DbExecutor } from "../client.js";
|
|
2
2
|
import { Logger } from "@headless-lms/core/shared/ports";
|
|
3
|
-
import { Activity, AddDownloadAssetInput, ContentRepository, Course, CreateCourseInput, CreateDownloadInput, Download, DownloadAsset, ListCoursesQuery, ListDownloadsQuery, Module, Page, SaveActivityInput, UpdateCourseInput, UpdateDownloadInput } from "@headless-lms/core/content";
|
|
3
|
+
import { Activity, ActivityAsset, AddDownloadAssetInput, Bundle, BundleItem, ContentRepository, Course, CourseSettings, CreateBundleInput, CreateCourseInput, CreateDownloadInput, Download, DownloadAsset, ListBundlesQuery, ListCoursesQuery, ListDownloadsQuery, Module, Page, SaveActivityInput, UpdateBundleInput, UpdateCourseInput, UpdateDownloadInput } from "@headless-lms/core/content";
|
|
4
4
|
//#region src/repositories/content.d.ts
|
|
5
5
|
declare class DrizzleContentRepository implements ContentRepository {
|
|
6
6
|
private readonly db;
|
|
@@ -9,13 +9,15 @@ declare class DrizzleContentRepository implements ContentRepository {
|
|
|
9
9
|
/** On the root db this opens a transaction; tx-bound (ContentUnitOfWork) it
|
|
10
10
|
* nests as a savepoint inside the surrounding transaction. */
|
|
11
11
|
private tx;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
listCourses(orgId: string, query: ListCoursesQuery): Promise<Page<Course>>;
|
|
13
|
+
findCourseById(orgId: string, id: string): Promise<Course | null>;
|
|
14
|
+
createCourse(orgId: string, input: CreateCourseInput, slug: string): Promise<Course>;
|
|
15
|
+
updateCourse(orgId: string, id: string, patch: UpdateCourseInput): Promise<Course | null>;
|
|
16
|
+
patchCourseSettings(orgId: string, id: string, value: Partial<CourseSettings>): Promise<CourseSettings>;
|
|
17
|
+
deleteCourse(orgId: string, id: string): Promise<boolean>;
|
|
18
|
+
listCourseModules(orgId: string, courseId: string): Promise<Module[]>;
|
|
18
19
|
listActivitiesForCourse(orgId: string, courseId: string): Promise<Activity[]>;
|
|
20
|
+
listActivityAssetsForCourse(orgId: string, courseId: string): Promise<ActivityAsset[]>;
|
|
19
21
|
findActivity(orgId: string, activityId: string): Promise<Activity | null>;
|
|
20
22
|
findModule(orgId: string, moduleId: string): Promise<Module | null>;
|
|
21
23
|
/** The course's full ordered module list. */
|
|
@@ -46,6 +48,18 @@ declare class DrizzleContentRepository implements ContentRepository {
|
|
|
46
48
|
removeDownloadAsset(orgId: string, downloadId: string, assetId: string): Promise<DownloadAsset[]>;
|
|
47
49
|
renameDownloadAsset(orgId: string, downloadId: string, assetId: string, displayName: string | null): Promise<DownloadAsset[]>;
|
|
48
50
|
reorderDownloadAssets(orgId: string, downloadId: string, assetIds: string[]): Promise<DownloadAsset[]>;
|
|
51
|
+
listBundles(orgId: string, query: ListBundlesQuery): Promise<Page<Bundle>>;
|
|
52
|
+
findBundleById(orgId: string, id: string): Promise<Bundle | null>;
|
|
53
|
+
/** The bundle and its initial items are one write: an unknown content id
|
|
54
|
+
* rolls the bundle back rather than leaving it created and half-filled. */
|
|
55
|
+
createBundle(orgId: string, input: CreateBundleInput): Promise<Bundle>;
|
|
56
|
+
updateBundle(orgId: string, id: string, patch: UpdateBundleInput): Promise<Bundle | null>;
|
|
57
|
+
deleteBundle(orgId: string, id: string): Promise<boolean>;
|
|
58
|
+
listBundleItems(orgId: string, bundleId: string): Promise<BundleItem[]>;
|
|
59
|
+
/** Assert the bundle exists in the org; throw otherwise. */
|
|
60
|
+
private assertBundle;
|
|
61
|
+
addBundleItem(orgId: string, bundleId: string, contentId: string): Promise<BundleItem[]>;
|
|
62
|
+
removeBundleItem(orgId: string, bundleId: string, contentId: string): Promise<BundleItem[]>;
|
|
49
63
|
}
|
|
50
64
|
//#endregion
|
|
51
65
|
export { DrizzleContentRepository };
|