@headless-lms/core 0.1.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/LICENSE +21 -0
- package/dist/assets/events.d.ts +81 -0
- package/dist/assets/events.js +24 -0
- package/dist/assets/events.js.map +1 -0
- package/dist/assets/index.d.ts +7 -0
- package/dist/assets/index.js +3 -0
- package/dist/assets/model.d.ts +4 -0
- package/dist/assets/model.js +1 -0
- package/dist/assets/ports.d.ts +34 -0
- package/dist/assets/ports.js +1 -0
- package/dist/assets/service.d.ts +29 -0
- package/dist/assets/service.js +130 -0
- package/dist/assets/service.js.map +1 -0
- package/dist/automations/actions.d.ts +24 -0
- package/dist/automations/actions.js +59 -0
- package/dist/automations/actions.js.map +1 -0
- package/dist/automations/catalog.d.ts +12 -0
- package/dist/automations/catalog.js +224 -0
- package/dist/automations/catalog.js.map +1 -0
- package/dist/automations/events.d.ts +264 -0
- package/dist/automations/events.js +54 -0
- package/dist/automations/events.js.map +1 -0
- package/dist/automations/index.d.ts +9 -0
- package/dist/automations/index.js +4 -0
- package/dist/automations/model.d.ts +12 -0
- package/dist/automations/model.js +14 -0
- package/dist/automations/model.js.map +1 -0
- package/dist/automations/ports.d.ts +56 -0
- package/dist/automations/ports.js +1 -0
- package/dist/automations/service.d.ts +53 -0
- package/dist/automations/service.js +225 -0
- package/dist/automations/service.js.map +1 -0
- package/dist/automations/types.d.ts +3 -0
- package/dist/automations/types.js +1 -0
- package/dist/content/events.d.ts +410 -0
- package/dist/content/events.js +105 -0
- package/dist/content/events.js.map +1 -0
- package/dist/content/index.d.ts +8 -0
- package/dist/content/index.js +3 -0
- package/dist/content/model.d.ts +3 -0
- package/dist/content/model.js +1 -0
- package/dist/content/ports.d.ts +85 -0
- package/dist/content/ports.js +1 -0
- package/dist/content/service.d.ts +54 -0
- package/dist/content/service.js +479 -0
- package/dist/content/service.js.map +1 -0
- package/dist/content/types.d.ts +4 -0
- package/dist/content/types.js +1 -0
- package/dist/discussion/events.d.ts +101 -0
- package/dist/discussion/events.js +29 -0
- package/dist/discussion/events.js.map +1 -0
- package/dist/discussion/index.d.ts +8 -0
- package/dist/discussion/index.js +3 -0
- package/dist/discussion/model.d.ts +5 -0
- package/dist/discussion/model.js +1 -0
- package/dist/discussion/ports.d.ts +112 -0
- package/dist/discussion/ports.js +1 -0
- package/dist/discussion/service.d.ts +67 -0
- package/dist/discussion/service.js +343 -0
- package/dist/discussion/service.js.map +1 -0
- package/dist/discussion/types.d.ts +4 -0
- package/dist/discussion/types.js +1 -0
- package/dist/entitlements/events.d.ts +89 -0
- package/dist/entitlements/events.js +29 -0
- package/dist/entitlements/events.js.map +1 -0
- package/dist/entitlements/index.d.ts +7 -0
- package/dist/entitlements/index.js +3 -0
- package/dist/entitlements/model.d.ts +4 -0
- package/dist/entitlements/model.js +1 -0
- package/dist/entitlements/ports.d.ts +34 -0
- package/dist/entitlements/ports.js +1 -0
- package/dist/entitlements/service.d.ts +27 -0
- package/dist/entitlements/service.js +60 -0
- package/dist/entitlements/service.js.map +1 -0
- package/dist/identity/events.d.ts +63 -0
- package/dist/identity/events.js +24 -0
- package/dist/identity/events.js.map +1 -0
- package/dist/identity/index.d.ts +8 -0
- package/dist/identity/index.js +3 -0
- package/dist/identity/model.d.ts +3 -0
- package/dist/identity/model.js +1 -0
- package/dist/identity/ports.d.ts +58 -0
- package/dist/identity/ports.js +1 -0
- package/dist/identity/service.d.ts +38 -0
- package/dist/identity/service.js +79 -0
- package/dist/identity/service.js.map +1 -0
- package/dist/identity/types.d.ts +4 -0
- package/dist/identity/types.js +1 -0
- package/dist/integrations/events.d.ts +70 -0
- package/dist/integrations/events.js +24 -0
- package/dist/integrations/events.js.map +1 -0
- package/dist/integrations/index.d.ts +9 -0
- package/dist/integrations/index.js +6 -0
- package/dist/integrations/model.d.ts +28 -0
- package/dist/integrations/model.js +45 -0
- package/dist/integrations/model.js.map +1 -0
- package/dist/integrations/ports.d.ts +62 -0
- package/dist/integrations/ports.js +1 -0
- package/dist/integrations/registry.d.ts +7 -0
- package/dist/integrations/registry.js +16 -0
- package/dist/integrations/registry.js.map +1 -0
- package/dist/integrations/service.d.ts +47 -0
- package/dist/integrations/service.js +184 -0
- package/dist/integrations/service.js.map +1 -0
- package/dist/integrations/zod.d.ts +29 -0
- package/dist/integrations/zod.js +40 -0
- package/dist/integrations/zod.js.map +1 -0
- package/dist/organizations/events.d.ts +197 -0
- package/dist/organizations/events.js +59 -0
- package/dist/organizations/events.js.map +1 -0
- package/dist/organizations/index.d.ts +11 -0
- package/dist/organizations/index.js +5 -0
- package/dist/organizations/members.d.ts +32 -0
- package/dist/organizations/members.js +12 -0
- package/dist/organizations/members.js.map +1 -0
- package/dist/organizations/model.d.ts +3 -0
- package/dist/organizations/model.js +1 -0
- package/dist/organizations/ports.d.ts +127 -0
- package/dist/organizations/ports.js +1 -0
- package/dist/organizations/roles.d.ts +23 -0
- package/dist/organizations/roles.js +70 -0
- package/dist/organizations/roles.js.map +1 -0
- package/dist/organizations/service.d.ts +58 -0
- package/dist/organizations/service.js +359 -0
- package/dist/organizations/service.js.map +1 -0
- package/dist/organizations/types.d.ts +39 -0
- package/dist/organizations/types.js +1 -0
- package/dist/progress/events.d.ts +55 -0
- package/dist/progress/events.js +19 -0
- package/dist/progress/events.js.map +1 -0
- package/dist/progress/index.d.ts +8 -0
- package/dist/progress/index.js +3 -0
- package/dist/progress/model.d.ts +3 -0
- package/dist/progress/model.js +1 -0
- package/dist/progress/ports.d.ts +37 -0
- package/dist/progress/ports.js +1 -0
- package/dist/progress/service.d.ts +33 -0
- package/dist/progress/service.js +181 -0
- package/dist/progress/service.js.map +1 -0
- package/dist/progress/types.d.ts +4 -0
- package/dist/progress/types.js +1 -0
- package/dist/reporting/dashboard/index.d.ts +4 -0
- package/dist/reporting/dashboard/index.js +2 -0
- package/dist/reporting/dashboard/model.d.ts +14 -0
- package/dist/reporting/dashboard/model.js +1 -0
- package/dist/reporting/dashboard/ports.d.ts +13 -0
- package/dist/reporting/dashboard/ports.js +1 -0
- package/dist/reporting/dashboard/service.d.ts +19 -0
- package/dist/reporting/dashboard/service.js +20 -0
- package/dist/reporting/dashboard/service.js.map +1 -0
- package/dist/reporting/learn/index.d.ts +5 -0
- package/dist/reporting/learn/index.js +2 -0
- package/dist/reporting/learn/model.d.ts +23 -0
- package/dist/reporting/learn/model.js +1 -0
- package/dist/reporting/learn/ports.d.ts +42 -0
- package/dist/reporting/learn/ports.js +1 -0
- package/dist/reporting/learn/service.d.ts +48 -0
- package/dist/reporting/learn/service.js +111 -0
- package/dist/reporting/learn/service.js.map +1 -0
- package/dist/reporting/students/index.d.ts +4 -0
- package/dist/reporting/students/index.js +2 -0
- package/dist/reporting/students/model.d.ts +31 -0
- package/dist/reporting/students/model.js +1 -0
- package/dist/reporting/students/ports.d.ts +13 -0
- package/dist/reporting/students/ports.js +1 -0
- package/dist/reporting/students/service.d.ts +19 -0
- package/dist/reporting/students/service.js +20 -0
- package/dist/reporting/students/service.js.map +1 -0
- package/dist/shared/errors.d.ts +22 -0
- package/dist/shared/errors.js +34 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/events.d.ts +31 -0
- package/dist/shared/events.js +75 -0
- package/dist/shared/events.js.map +1 -0
- package/dist/shared/id.d.ts +44 -0
- package/dist/shared/id.js +53 -0
- package/dist/shared/id.js.map +1 -0
- package/dist/shared/invite-token.d.ts +9 -0
- package/dist/shared/invite-token.js +16 -0
- package/dist/shared/invite-token.js.map +1 -0
- package/dist/shared/logger.d.ts +19 -0
- package/dist/shared/logger.js +44 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/mailer.d.ts +23 -0
- package/dist/shared/mailer.js +27 -0
- package/dist/shared/mailer.js.map +1 -0
- package/dist/shared/ports.d.ts +62 -0
- package/dist/shared/ports.js +2 -0
- package/dist/shared/settings.d.ts +25 -0
- package/dist/shared/settings.js +21 -0
- package/dist/shared/settings.js.map +1 -0
- package/dist/types/assets.d.ts +2 -0
- package/dist/types/assets.js +1 -0
- package/dist/types/automations.d.ts +23 -0
- package/dist/types/automations.js +1 -0
- package/dist/types/content.d.ts +2 -0
- package/dist/types/content.js +1 -0
- package/dist/types/discussion.d.ts +36 -0
- package/dist/types/discussion.js +1 -0
- package/dist/types/email-templates.d.ts +57 -0
- package/dist/types/email-templates.js +1 -0
- package/dist/types/entitlements.d.ts +3 -0
- package/dist/types/entitlements.js +1 -0
- package/dist/types/events.d.ts +25 -0
- package/dist/types/events.js +1 -0
- package/dist/types/identity.d.ts +37 -0
- package/dist/types/identity.js +1 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/index.js +1 -0
- package/dist/types/integrations.d.ts +45 -0
- package/dist/types/integrations.js +1 -0
- package/dist/types/organizations.d.ts +15 -0
- package/dist/types/organizations.js +1 -0
- package/dist/types/ports.d.ts +57 -0
- package/dist/types/ports.js +1 -0
- package/dist/types/progress.d.ts +6 -0
- package/dist/types/progress.js +1 -0
- package/dist/types/schemas/assets.d.ts +109 -0
- package/dist/types/schemas/assets.js +49 -0
- package/dist/types/schemas/assets.js.map +1 -0
- package/dist/types/schemas/automations.d.ts +118 -0
- package/dist/types/schemas/automations.js +73 -0
- package/dist/types/schemas/automations.js.map +1 -0
- package/dist/types/schemas/content.d.ts +216 -0
- package/dist/types/schemas/content.js +138 -0
- package/dist/types/schemas/content.js.map +1 -0
- package/dist/types/schemas/credentials.d.ts +15 -0
- package/dist/types/schemas/credentials.js +15 -0
- package/dist/types/schemas/credentials.js.map +1 -0
- package/dist/types/schemas/discussion.d.ts +192 -0
- package/dist/types/schemas/discussion.js +100 -0
- package/dist/types/schemas/discussion.js.map +1 -0
- package/dist/types/schemas/entitlements.d.ts +50 -0
- package/dist/types/schemas/entitlements.js +38 -0
- package/dist/types/schemas/entitlements.js.map +1 -0
- package/dist/types/schemas/identity.d.ts +37 -0
- package/dist/types/schemas/identity.js +34 -0
- package/dist/types/schemas/identity.js.map +1 -0
- package/dist/types/schemas/index.d.ts +14 -0
- package/dist/types/schemas/index.js +14 -0
- package/dist/types/schemas/integrations.d.ts +36 -0
- package/dist/types/schemas/integrations.js +30 -0
- package/dist/types/schemas/integrations.js.map +1 -0
- package/dist/types/schemas/organizations.d.ts +135 -0
- package/dist/types/schemas/organizations.js +90 -0
- package/dist/types/schemas/organizations.js.map +1 -0
- package/dist/types/schemas/outbox.d.ts +19 -0
- package/dist/types/schemas/outbox.js +18 -0
- package/dist/types/schemas/outbox.js.map +1 -0
- package/dist/types/schemas/progress.d.ts +53 -0
- package/dist/types/schemas/progress.js +37 -0
- package/dist/types/schemas/progress.js.map +1 -0
- package/dist/types/schemas/settings.d.ts +16 -0
- package/dist/types/schemas/settings.js +15 -0
- package/dist/types/schemas/settings.js.map +1 -0
- package/dist/types/schemas/shared.d.ts +13 -0
- package/dist/types/schemas/shared.js +28 -0
- package/dist/types/schemas/shared.js.map +1 -0
- package/dist/types/shared.d.ts +25 -0
- package/dist/types/shared.js +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Logger } from "../../types/ports.js";
|
|
2
|
+
import "../../shared/ports.js";
|
|
3
|
+
import { Page, Student, StudentsQuery } from "./model.js";
|
|
4
|
+
import { StudentsReportRepository, StudentsReportService } from "./ports.js";
|
|
5
|
+
//#region src/reporting/students/service.d.ts
|
|
6
|
+
type StudentsReportServiceParams = {
|
|
7
|
+
repo: StudentsReportRepository;
|
|
8
|
+
logger?: Logger;
|
|
9
|
+
};
|
|
10
|
+
declare class StudentsReportServiceImpl implements StudentsReportService {
|
|
11
|
+
private readonly repo;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
constructor(params: StudentsReportServiceParams);
|
|
14
|
+
list(orgId: string, query: StudentsQuery): Promise<Page<Student>>;
|
|
15
|
+
get(orgId: string, id: string): Promise<Student | null>;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { StudentsReportServiceImpl, StudentsReportServiceParams };
|
|
19
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { noopLogger } from "../../shared/logger.js";
|
|
2
|
+
//#region src/reporting/students/service.ts
|
|
3
|
+
var StudentsReportServiceImpl = class {
|
|
4
|
+
repo;
|
|
5
|
+
logger;
|
|
6
|
+
constructor(params) {
|
|
7
|
+
this.repo = params.repo;
|
|
8
|
+
this.logger = params.logger ?? noopLogger;
|
|
9
|
+
}
|
|
10
|
+
list(orgId, query) {
|
|
11
|
+
return this.repo.list(orgId, query);
|
|
12
|
+
}
|
|
13
|
+
get(orgId, id) {
|
|
14
|
+
return this.repo.findById(orgId, id);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { StudentsReportServiceImpl };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../../src/reporting/students/service.ts"],"sourcesContent":["// reporting/students — service implementation (inbound port).\nimport type { Page, Student, StudentsQuery } from './model.js';\nimport type { StudentsReportRepository, StudentsReportService } from './ports.js';\nimport type { Logger } from '../../shared/ports.js';\nimport { noopLogger } from '../../shared/logger.js';\n\nexport type StudentsReportServiceParams = {\n repo: StudentsReportRepository;\n logger?: Logger;\n};\n\nexport class StudentsReportServiceImpl implements StudentsReportService {\n private readonly repo: StudentsReportRepository;\n private readonly logger: Logger;\n\n constructor(params: StudentsReportServiceParams) {\n this.repo = params.repo;\n this.logger = params.logger ?? noopLogger;\n }\n\n list(orgId: string, query: StudentsQuery): Promise<Page<Student>> {\n return this.repo.list(orgId, query);\n }\n\n get(orgId: string, id: string): Promise<Student | null> {\n return this.repo.findById(orgId, id);\n }\n}\n"],"mappings":";;AAWA,IAAa,4BAAb,MAAwE;CACtE;CACA;CAEA,YAAY,QAAqC;EAC/C,KAAK,OAAO,OAAO;EACnB,KAAK,SAAS,OAAO,UAAU;CACjC;CAEA,KAAK,OAAe,OAA8C;EAChE,OAAO,KAAK,KAAK,KAAK,OAAO,KAAK;CACpC;CAEA,IAAI,OAAe,IAAqC;EACtD,OAAO,KAAK,KAAK,SAAS,OAAO,EAAE;CACrC;AACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/shared/errors.d.ts
|
|
2
|
+
/** A command's target does not exist in this org. Queries return null for the
|
|
3
|
+
* same situation — a missing row is a normal answer to a question, but a
|
|
4
|
+
* failed precondition for a command. */
|
|
5
|
+
declare class NotFoundError extends Error {
|
|
6
|
+
readonly resource: string;
|
|
7
|
+
readonly id: string;
|
|
8
|
+
constructor(resource: string, id: string);
|
|
9
|
+
}
|
|
10
|
+
/** A command conflicts with existing state (duplicate email, already linked,
|
|
11
|
+
* …). The HTTP layer maps this to 409. */
|
|
12
|
+
declare class ConflictError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
/** The caller is authenticated but not permitted to perform this command.
|
|
16
|
+
* The HTTP layer maps this to 403. */
|
|
17
|
+
declare class ForbiddenError extends Error {
|
|
18
|
+
constructor(message: string);
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { ConflictError, ForbiddenError, NotFoundError };
|
|
22
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/shared/errors.ts
|
|
2
|
+
/** A command's target does not exist in this org. Queries return null for the
|
|
3
|
+
* same situation — a missing row is a normal answer to a question, but a
|
|
4
|
+
* failed precondition for a command. */
|
|
5
|
+
var NotFoundError = class extends Error {
|
|
6
|
+
resource;
|
|
7
|
+
id;
|
|
8
|
+
constructor(resource, id) {
|
|
9
|
+
super(`${resource} not found`);
|
|
10
|
+
this.resource = resource;
|
|
11
|
+
this.id = id;
|
|
12
|
+
this.name = "NotFoundError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/** A command conflicts with existing state (duplicate email, already linked,
|
|
16
|
+
* …). The HTTP layer maps this to 409. */
|
|
17
|
+
var ConflictError = class extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "ConflictError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/** The caller is authenticated but not permitted to perform this command.
|
|
24
|
+
* The HTTP layer maps this to 403. */
|
|
25
|
+
var ForbiddenError = class extends Error {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "ForbiddenError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
export { ConflictError, ForbiddenError, NotFoundError };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../src/shared/errors.ts"],"sourcesContent":["// Cross-cutting domain errors. Like `../shared/ports.js`, this file is\n// importable from any context; the central HTTP error handler maps these to\n// status codes so routes never hand-roll error replies.\n\n/** A command's target does not exist in this org. Queries return null for the\n * same situation — a missing row is a normal answer to a question, but a\n * failed precondition for a command. */\nexport class NotFoundError extends Error {\n constructor(\n readonly resource: string,\n readonly id: string,\n ) {\n super(`${resource} not found`);\n this.name = 'NotFoundError';\n }\n}\n\n/** A command conflicts with existing state (duplicate email, already linked,\n * …). The HTTP layer maps this to 409. */\nexport class ConflictError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConflictError';\n }\n}\n\n/** The caller is authenticated but not permitted to perform this command.\n * The HTTP layer maps this to 403. */\nexport class ForbiddenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ForbiddenError';\n }\n}\n"],"mappings":";;;;AAOA,IAAa,gBAAb,cAAmC,MAAM;CAE5B;CACA;CAFX,YACE,UACA,IACA;EACA,MAAM,GAAG,SAAS,WAAW;EAHpB,KAAA,WAAA;EACA,KAAA,KAAA;EAGT,KAAK,OAAO;CACd;AACF;;;AAIA,IAAa,gBAAb,cAAmC,MAAM;CACvC,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;;;AAIA,IAAa,iBAAb,cAAoC,MAAM;CACxC,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DomainEvent, EventMetadata, JsonValue } from "../types/shared.js";
|
|
2
|
+
import { EventDefinition, EventParseResult, MakeEventInput } from "../types/events.js";
|
|
3
|
+
import "../types/index.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/shared/events.d.ts
|
|
6
|
+
declare class InvalidDomainEventError extends Error {
|
|
7
|
+
constructor(type: string, detail: string);
|
|
8
|
+
}
|
|
9
|
+
declare const eventIdSchema: z.ZodString;
|
|
10
|
+
declare const eventJsonValueSchema: z.ZodType<JsonValue>;
|
|
11
|
+
declare const eventMetadataSchema: z.ZodType<EventMetadata>;
|
|
12
|
+
type VersionedDomainEvent<TData = unknown, TType extends string = string, TVersion extends number = number> = DomainEvent<TData, TType, TVersion> & {
|
|
13
|
+
readonly version: TVersion;
|
|
14
|
+
readonly occurredAt: string;
|
|
15
|
+
readonly data: TData;
|
|
16
|
+
};
|
|
17
|
+
interface RuntimeEventDefinition<E extends VersionedDomainEvent, TMakeData = E['data']> extends EventDefinition<E, TMakeData> {
|
|
18
|
+
readonly dataSchema: z.ZodType;
|
|
19
|
+
readonly eventSchema: z.ZodType<E>;
|
|
20
|
+
}
|
|
21
|
+
type EventOf<TDefinition extends EventDefinition<DomainEvent, unknown>> = TDefinition extends EventDefinition<infer E, unknown> ? E : never;
|
|
22
|
+
type EventOfValues<TDefinitions extends Record<string, EventDefinition<DomainEvent, unknown>>> = EventOf<TDefinitions[keyof TDefinitions]>;
|
|
23
|
+
interface DefineEventInput<TType extends string, TVersion extends number, TDataSchema extends z.ZodType> {
|
|
24
|
+
readonly type: TType;
|
|
25
|
+
readonly version: TVersion;
|
|
26
|
+
readonly data: TDataSchema;
|
|
27
|
+
}
|
|
28
|
+
declare function defineEvent<const TType extends string, const TVersion extends number, TDataSchema extends z.ZodType>(input: DefineEventInput<TType, TVersion, TDataSchema>): RuntimeEventDefinition<VersionedDomainEvent<z.output<TDataSchema>, TType, TVersion>, z.input<TDataSchema>>;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { DefineEventInput, type EventDefinition, EventOf, EventOfValues, type EventParseResult, InvalidDomainEventError, type MakeEventInput, RuntimeEventDefinition, VersionedDomainEvent, defineEvent, eventIdSchema, eventJsonValueSchema, eventMetadataSchema };
|
|
31
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/shared/events.ts
|
|
3
|
+
var InvalidDomainEventError = class extends Error {
|
|
4
|
+
constructor(type, detail) {
|
|
5
|
+
super(`invalid ${type} event: ${detail}`);
|
|
6
|
+
this.name = "InvalidDomainEventError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
const eventIdSchema = z.string().trim().min(1);
|
|
10
|
+
const eventJsonValueSchema = z.lazy(() => z.union([
|
|
11
|
+
z.null(),
|
|
12
|
+
z.string(),
|
|
13
|
+
z.number().finite(),
|
|
14
|
+
z.boolean(),
|
|
15
|
+
z.array(eventJsonValueSchema),
|
|
16
|
+
z.record(z.string(), eventJsonValueSchema)
|
|
17
|
+
]));
|
|
18
|
+
const eventMetadataSchema = z.record(z.string(), eventJsonValueSchema);
|
|
19
|
+
function defineEvent(input) {
|
|
20
|
+
const makeInputSchema = z.object({
|
|
21
|
+
orgId: eventIdSchema,
|
|
22
|
+
data: input.data,
|
|
23
|
+
metadata: eventMetadataSchema.optional()
|
|
24
|
+
}).strict();
|
|
25
|
+
const eventSchema = z.object({
|
|
26
|
+
type: z.literal(input.type),
|
|
27
|
+
version: z.literal(input.version),
|
|
28
|
+
id: eventIdSchema,
|
|
29
|
+
orgId: eventIdSchema,
|
|
30
|
+
occurredAt: z.string().trim().min(1),
|
|
31
|
+
data: input.data,
|
|
32
|
+
metadata: eventMetadataSchema.optional()
|
|
33
|
+
}).strict();
|
|
34
|
+
return {
|
|
35
|
+
type: input.type,
|
|
36
|
+
version: input.version,
|
|
37
|
+
dataSchema: input.data,
|
|
38
|
+
eventSchema,
|
|
39
|
+
make(value) {
|
|
40
|
+
const result = makeInputSchema.safeParse(value);
|
|
41
|
+
if (!result.success) throw invalidEvent(input.type, result.error);
|
|
42
|
+
return {
|
|
43
|
+
type: input.type,
|
|
44
|
+
version: input.version,
|
|
45
|
+
...result.data
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
is(event) {
|
|
49
|
+
return eventSchema.safeParse(event).success;
|
|
50
|
+
},
|
|
51
|
+
parse(event) {
|
|
52
|
+
const result = eventSchema.safeParse(event);
|
|
53
|
+
if (!result.success) throw invalidEvent(input.type, result.error);
|
|
54
|
+
return result.data;
|
|
55
|
+
},
|
|
56
|
+
safeParse(event) {
|
|
57
|
+
const result = eventSchema.safeParse(event);
|
|
58
|
+
if (!result.success) return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: invalidEvent(input.type, result.error)
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
data: result.data
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function invalidEvent(type, error) {
|
|
70
|
+
return new InvalidDomainEventError(type, error.issues.map((issue) => `${issue.path.join(".") || "event"}: ${issue.message}`).join("; "));
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { InvalidDomainEventError, defineEvent, eventIdSchema, eventJsonValueSchema, eventMetadataSchema };
|
|
74
|
+
|
|
75
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../../src/shared/events.ts"],"sourcesContent":["import { z } from 'zod';\nimport type {\n ContractNewDomainEvent,\n DomainEvent,\n EventDefinition,\n EventMetadata,\n EventParseResult,\n JsonValue,\n MakeEventInput,\n} from '../types/index.js';\n\nexport class InvalidDomainEventError extends Error {\n constructor(type: string, detail: string) {\n super(`invalid ${type} event: ${detail}`);\n this.name = 'InvalidDomainEventError';\n }\n}\n\nexport const eventIdSchema: z.ZodString = z.string().trim().min(1);\n\nexport const eventJsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n z.null(),\n z.string(),\n z.number().finite(),\n z.boolean(),\n z.array(eventJsonValueSchema),\n z.record(z.string(), eventJsonValueSchema),\n ]),\n);\n\nexport const eventMetadataSchema: z.ZodType<EventMetadata> = z.record(\n z.string(),\n eventJsonValueSchema,\n);\n\nexport type VersionedDomainEvent<\n TData = unknown,\n TType extends string = string,\n TVersion extends number = number,\n> = DomainEvent<TData, TType, TVersion> & {\n readonly version: TVersion;\n readonly occurredAt: string;\n readonly data: TData;\n};\n\nexport interface RuntimeEventDefinition<\n E extends VersionedDomainEvent,\n TMakeData = E['data'],\n> extends EventDefinition<E, TMakeData> {\n readonly dataSchema: z.ZodType;\n readonly eventSchema: z.ZodType<E>;\n}\n\nexport type EventOf<TDefinition extends EventDefinition<DomainEvent, unknown>> =\n TDefinition extends EventDefinition<infer E, unknown> ? E : never;\n\nexport type EventOfValues<TDefinitions extends Record<string, EventDefinition<DomainEvent, unknown>>> =\n EventOf<TDefinitions[keyof TDefinitions]>;\n\nexport interface DefineEventInput<\n TType extends string,\n TVersion extends number,\n TDataSchema extends z.ZodType,\n> {\n readonly type: TType;\n readonly version: TVersion;\n readonly data: TDataSchema;\n}\n\nexport function defineEvent<\n const TType extends string,\n const TVersion extends number,\n TDataSchema extends z.ZodType,\n>(\n input: DefineEventInput<TType, TVersion, TDataSchema>,\n): RuntimeEventDefinition<\n VersionedDomainEvent<z.output<TDataSchema>, TType, TVersion>,\n z.input<TDataSchema>\n> {\n type Event = VersionedDomainEvent<z.output<TDataSchema>, TType, TVersion>;\n const makeInputSchema = z.object({\n orgId: eventIdSchema,\n data: input.data,\n metadata: eventMetadataSchema.optional(),\n }).strict();\n const eventSchema = z.object({\n type: z.literal(input.type),\n version: z.literal(input.version),\n id: eventIdSchema,\n orgId: eventIdSchema,\n occurredAt: z.string().trim().min(1),\n data: input.data,\n metadata: eventMetadataSchema.optional(),\n }).strict();\n\n return {\n type: input.type,\n version: input.version,\n dataSchema: input.data,\n eventSchema: eventSchema as unknown as z.ZodType<Event>,\n make(value: MakeEventInput<z.input<TDataSchema>>) {\n const result = makeInputSchema.safeParse(value);\n if (!result.success) {\n throw invalidEvent(input.type, result.error);\n }\n return {\n type: input.type,\n version: input.version,\n ...result.data,\n } as ContractNewDomainEvent<Event>;\n },\n is(event: unknown): event is Event {\n return eventSchema.safeParse(event).success;\n },\n parse(event: unknown) {\n const result = eventSchema.safeParse(event);\n if (!result.success) {\n throw invalidEvent(input.type, result.error);\n }\n return result.data as Event;\n },\n safeParse(event: unknown): EventParseResult<Event> {\n const result = eventSchema.safeParse(event);\n if (!result.success) {\n return { success: false, error: invalidEvent(input.type, result.error) };\n }\n return { success: true, data: result.data as Event };\n },\n };\n}\n\nfunction invalidEvent(type: string, error: z.ZodError): InvalidDomainEventError {\n return new InvalidDomainEventError(\n type,\n error.issues.map((issue) => `${issue.path.join('.') || 'event'}: ${issue.message}`).join('; '),\n );\n}\n\nexport type {\n EventDefinition,\n EventParseResult,\n MakeEventInput,\n};\n"],"mappings":";;AAWA,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,MAAc,QAAgB;EACxC,MAAM,WAAW,KAAK,UAAU,QAAQ;EACxC,KAAK,OAAO;CACd;AACF;AAEA,MAAa,gBAA6B,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;AAEjE,MAAa,uBAA6C,EAAE,WAC1D,EAAE,MAAM;CACN,EAAE,KAAK;CACP,EAAE,OAAO;CACT,EAAE,OAAO,CAAC,CAAC,OAAO;CAClB,EAAE,QAAQ;CACV,EAAE,MAAM,oBAAoB;CAC5B,EAAE,OAAO,EAAE,OAAO,GAAG,oBAAoB;AAC3C,CAAC,CACH;AAEA,MAAa,sBAAgD,EAAE,OAC7D,EAAE,OAAO,GACT,oBACF;AAoCA,SAAgB,YAKd,OAIA;CAEA,MAAM,kBAAkB,EAAE,OAAO;EAC/B,OAAO;EACP,MAAM,MAAM;EACZ,UAAU,oBAAoB,SAAS;CACzC,CAAC,CAAC,CAAC,OAAO;CACV,MAAM,cAAc,EAAE,OAAO;EAC3B,MAAM,EAAE,QAAQ,MAAM,IAAI;EAC1B,SAAS,EAAE,QAAQ,MAAM,OAAO;EAChC,IAAI;EACJ,OAAO;EACP,YAAY,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;EACnC,MAAM,MAAM;EACZ,UAAU,oBAAoB,SAAS;CACzC,CAAC,CAAC,CAAC,OAAO;CAEV,OAAO;EACL,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,YAAY,MAAM;EACL;EACb,KAAK,OAA6C;GAChD,MAAM,SAAS,gBAAgB,UAAU,KAAK;GAC9C,IAAI,CAAC,OAAO,SACV,MAAM,aAAa,MAAM,MAAM,OAAO,KAAK;GAE7C,OAAO;IACL,MAAM,MAAM;IACZ,SAAS,MAAM;IACf,GAAG,OAAO;GACZ;EACF;EACA,GAAG,OAAgC;GACjC,OAAO,YAAY,UAAU,KAAK,CAAC,CAAC;EACtC;EACA,MAAM,OAAgB;GACpB,MAAM,SAAS,YAAY,UAAU,KAAK;GAC1C,IAAI,CAAC,OAAO,SACV,MAAM,aAAa,MAAM,MAAM,OAAO,KAAK;GAE7C,OAAO,OAAO;EAChB;EACA,UAAU,OAAyC;GACjD,MAAM,SAAS,YAAY,UAAU,KAAK;GAC1C,IAAI,CAAC,OAAO,SACV,OAAO;IAAE,SAAS;IAAO,OAAO,aAAa,MAAM,MAAM,OAAO,KAAK;GAAE;GAEzE,OAAO;IAAE,SAAS;IAAM,MAAM,OAAO;GAAc;EACrD;CACF;AACF;AAEA,SAAS,aAAa,MAAc,OAA4C;CAC9E,OAAO,IAAI,wBACT,MACA,MAAM,OAAO,KAAK,UAAU,GAAG,MAAM,KAAK,KAAK,GAAG,KAAK,QAAQ,IAAI,MAAM,SAAS,CAAC,CAAC,KAAK,IAAI,CAC/F;AACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/shared/id.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Prefixed, KSUID-bodied string ids — `${prefix}_${ksuid}`, e.g. `org_2QZ4mK...`.
|
|
4
|
+
*
|
|
5
|
+
* The body is a real KSUID (27-char base62, 4-byte timestamp + 16 random bytes),
|
|
6
|
+
* so ids are lexicographically time-sortable. The prefix marks the entity type at
|
|
7
|
+
* a glance. A core utility (one implementation, nothing to swap), called directly
|
|
8
|
+
* by core services and used as the Drizzle `.$defaultFn` for every domain id column.
|
|
9
|
+
*/
|
|
10
|
+
declare const ID_PREFIXES: {
|
|
11
|
+
readonly organization: "org";
|
|
12
|
+
readonly user: "usr";
|
|
13
|
+
readonly student: "stu";
|
|
14
|
+
readonly orgUser: "orm";
|
|
15
|
+
readonly invite: "ivt";
|
|
16
|
+
readonly course: "crs";
|
|
17
|
+
readonly module: "mod";
|
|
18
|
+
readonly activity: "act";
|
|
19
|
+
readonly activityAsset: "aca";
|
|
20
|
+
readonly download: "dwn";
|
|
21
|
+
readonly downloadAsset: "dla";
|
|
22
|
+
readonly asset: "ast";
|
|
23
|
+
readonly entitlement: "ent";
|
|
24
|
+
readonly progress: "prg";
|
|
25
|
+
readonly credential: "crd";
|
|
26
|
+
readonly connection: "con";
|
|
27
|
+
readonly event: "evt";
|
|
28
|
+
readonly automation: "atm";
|
|
29
|
+
readonly automationRun: "run";
|
|
30
|
+
readonly comment: "cmt";
|
|
31
|
+
readonly commentReport: "crp";
|
|
32
|
+
};
|
|
33
|
+
type IdType = keyof typeof ID_PREFIXES;
|
|
34
|
+
/** A bare KSUID body (27-char base62, time-sortable), no prefix. */
|
|
35
|
+
declare function ksuid(): string;
|
|
36
|
+
/** Attach an arbitrary prefix to a fresh KSUID — `${prefix}_${ksuid}`. */
|
|
37
|
+
declare function prefixId(prefix: string): string;
|
|
38
|
+
/** Generate a prefixed, time-sortable id for the given domain entity type. */
|
|
39
|
+
declare function genId(type: IdType): string;
|
|
40
|
+
/** True when `value` carries the prefix for `type` (shape check only, not existence). */
|
|
41
|
+
declare function isId(type: IdType, value: string): boolean;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { ID_PREFIXES, IdType, genId, isId, ksuid, prefixId };
|
|
44
|
+
//# sourceMappingURL=id.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import KSUID from "ksuid";
|
|
2
|
+
//#region src/shared/id.ts
|
|
3
|
+
/**
|
|
4
|
+
* Prefixed, KSUID-bodied string ids — `${prefix}_${ksuid}`, e.g. `org_2QZ4mK...`.
|
|
5
|
+
*
|
|
6
|
+
* The body is a real KSUID (27-char base62, 4-byte timestamp + 16 random bytes),
|
|
7
|
+
* so ids are lexicographically time-sortable. The prefix marks the entity type at
|
|
8
|
+
* a glance. A core utility (one implementation, nothing to swap), called directly
|
|
9
|
+
* by core services and used as the Drizzle `.$defaultFn` for every domain id column.
|
|
10
|
+
*/
|
|
11
|
+
const ID_PREFIXES = {
|
|
12
|
+
organization: "org",
|
|
13
|
+
user: "usr",
|
|
14
|
+
student: "stu",
|
|
15
|
+
orgUser: "orm",
|
|
16
|
+
invite: "ivt",
|
|
17
|
+
course: "crs",
|
|
18
|
+
module: "mod",
|
|
19
|
+
activity: "act",
|
|
20
|
+
activityAsset: "aca",
|
|
21
|
+
download: "dwn",
|
|
22
|
+
downloadAsset: "dla",
|
|
23
|
+
asset: "ast",
|
|
24
|
+
entitlement: "ent",
|
|
25
|
+
progress: "prg",
|
|
26
|
+
credential: "crd",
|
|
27
|
+
connection: "con",
|
|
28
|
+
event: "evt",
|
|
29
|
+
automation: "atm",
|
|
30
|
+
automationRun: "run",
|
|
31
|
+
comment: "cmt",
|
|
32
|
+
commentReport: "crp"
|
|
33
|
+
};
|
|
34
|
+
/** A bare KSUID body (27-char base62, time-sortable), no prefix. */
|
|
35
|
+
function ksuid() {
|
|
36
|
+
return KSUID.randomSync().string;
|
|
37
|
+
}
|
|
38
|
+
/** Attach an arbitrary prefix to a fresh KSUID — `${prefix}_${ksuid}`. */
|
|
39
|
+
function prefixId(prefix) {
|
|
40
|
+
return `${prefix}_${ksuid()}`;
|
|
41
|
+
}
|
|
42
|
+
/** Generate a prefixed, time-sortable id for the given domain entity type. */
|
|
43
|
+
function genId(type) {
|
|
44
|
+
return prefixId(ID_PREFIXES[type]);
|
|
45
|
+
}
|
|
46
|
+
/** True when `value` carries the prefix for `type` (shape check only, not existence). */
|
|
47
|
+
function isId(type, value) {
|
|
48
|
+
return value.startsWith(`${ID_PREFIXES[type]}_`);
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { ID_PREFIXES, genId, isId, ksuid, prefixId };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.js","names":[],"sources":["../../src/shared/id.ts"],"sourcesContent":["import KSUID from 'ksuid';\n\n/**\n * Prefixed, KSUID-bodied string ids — `${prefix}_${ksuid}`, e.g. `org_2QZ4mK...`.\n *\n * The body is a real KSUID (27-char base62, 4-byte timestamp + 16 random bytes),\n * so ids are lexicographically time-sortable. The prefix marks the entity type at\n * a glance. A core utility (one implementation, nothing to swap), called directly\n * by core services and used as the Drizzle `.$defaultFn` for every domain id column.\n */\nexport const ID_PREFIXES = {\n organization: 'org',\n user: 'usr',\n student: 'stu',\n orgUser: 'orm',\n invite: 'ivt',\n course: 'crs',\n module: 'mod',\n activity: 'act',\n activityAsset: 'aca',\n download: 'dwn',\n downloadAsset: 'dla',\n asset: 'ast',\n entitlement: 'ent',\n progress: 'prg',\n credential: 'crd',\n connection: 'con',\n event: 'evt',\n automation: 'atm',\n automationRun: 'run',\n comment: 'cmt',\n commentReport: 'crp',\n} as const;\n\nexport type IdType = keyof typeof ID_PREFIXES;\n\n/** A bare KSUID body (27-char base62, time-sortable), no prefix. */\nexport function ksuid(): string {\n return KSUID.randomSync().string;\n}\n\n/** Attach an arbitrary prefix to a fresh KSUID — `${prefix}_${ksuid}`. */\nexport function prefixId(prefix: string): string {\n return `${prefix}_${ksuid()}`;\n}\n\n/** Generate a prefixed, time-sortable id for the given domain entity type. */\nexport function genId(type: IdType): string {\n return prefixId(ID_PREFIXES[type]);\n}\n\n/** True when `value` carries the prefix for `type` (shape check only, not existence). */\nexport function isId(type: IdType, value: string): boolean {\n return value.startsWith(`${ID_PREFIXES[type]}_`);\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAa,cAAc;CACzB,cAAc;CACd,MAAM;CACN,SAAS;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,eAAe;CACf,UAAU;CACV,eAAe;CACf,OAAO;CACP,aAAa;CACb,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,OAAO;CACP,YAAY;CACZ,eAAe;CACf,SAAS;CACT,eAAe;AACjB;;AAKA,SAAgB,QAAgB;CAC9B,OAAO,MAAM,WAAW,CAAC,CAAC;AAC5B;;AAGA,SAAgB,SAAS,QAAwB;CAC/C,OAAO,GAAG,OAAO,GAAG,MAAM;AAC5B;;AAGA,SAAgB,MAAM,MAAsB;CAC1C,OAAO,SAAS,YAAY,KAAK;AACnC;;AAGA,SAAgB,KAAK,MAAc,OAAwB;CACzD,OAAO,MAAM,WAAW,GAAG,YAAY,MAAM,EAAE;AACjD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/shared/invite-token.d.ts
|
|
2
|
+
declare function generateInviteToken(): {
|
|
3
|
+
token: string;
|
|
4
|
+
tokenHash: string;
|
|
5
|
+
};
|
|
6
|
+
declare function hashInviteToken(token: string): string;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { generateInviteToken, hashInviteToken };
|
|
9
|
+
//# sourceMappingURL=invite-token.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
//#region src/shared/invite-token.ts
|
|
3
|
+
function generateInviteToken() {
|
|
4
|
+
const token = randomBytes(32).toString("base64url");
|
|
5
|
+
return {
|
|
6
|
+
token,
|
|
7
|
+
tokenHash: hashInviteToken(token)
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function hashInviteToken(token) {
|
|
11
|
+
return createHash("sha256").update(token).digest("hex");
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { generateInviteToken, hashInviteToken };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=invite-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invite-token.js","names":[],"sources":["../../src/shared/invite-token.ts"],"sourcesContent":["// Domain-owned invite tokens: an unguessable secret in the emailed link, only\n// its sha256 at rest. Same core-utility status as id.ts (one implementation,\n// nothing to swap).\nimport { createHash, randomBytes } from 'node:crypto';\n\nexport function generateInviteToken(): { token: string; tokenHash: string } {\n const token = randomBytes(32).toString('base64url');\n return { token, tokenHash: hashInviteToken(token) };\n}\n\nexport function hashInviteToken(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\n"],"mappings":";;AAKA,SAAgB,sBAA4D;CAC1E,MAAM,QAAQ,YAAY,EAAE,CAAC,CAAC,SAAS,WAAW;CAClD,OAAO;EAAE;EAAO,WAAW,gBAAgB,KAAK;CAAE;AACpD;AAEA,SAAgB,gBAAgB,OAAuB;CACrD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;AACxD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Logger } from "../types/ports.js";
|
|
2
|
+
import "./ports.js";
|
|
3
|
+
//#region src/shared/logger.d.ts
|
|
4
|
+
/** Discards everything. The default for constructors the container overrides. */
|
|
5
|
+
declare const noopLogger: Logger;
|
|
6
|
+
interface CapturedLog {
|
|
7
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
8
|
+
msg: string;
|
|
9
|
+
meta: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
/** In-memory logger for tests: every entry (from the root or any child) lands
|
|
12
|
+
* in `entries`, with child bindings merged into meta. */
|
|
13
|
+
declare function createCapturingLogger(): {
|
|
14
|
+
logger: Logger;
|
|
15
|
+
entries: CapturedLog[];
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { CapturedLog, createCapturingLogger, noopLogger };
|
|
19
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/shared/logger.ts
|
|
2
|
+
/** Discards everything. The default for constructors the container overrides. */
|
|
3
|
+
const noopLogger = {
|
|
4
|
+
debug: () => {},
|
|
5
|
+
info: () => {},
|
|
6
|
+
warn: () => {},
|
|
7
|
+
error: () => {},
|
|
8
|
+
child: () => noopLogger
|
|
9
|
+
};
|
|
10
|
+
/** In-memory logger for tests: every entry (from the root or any child) lands
|
|
11
|
+
* in `entries`, with child bindings merged into meta. */
|
|
12
|
+
function createCapturingLogger() {
|
|
13
|
+
const entries = [];
|
|
14
|
+
const make = (bindings) => {
|
|
15
|
+
const record = (level) => (msg, meta) => {
|
|
16
|
+
entries.push({
|
|
17
|
+
level,
|
|
18
|
+
msg,
|
|
19
|
+
meta: {
|
|
20
|
+
...bindings,
|
|
21
|
+
...meta
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
debug: record("debug"),
|
|
27
|
+
info: record("info"),
|
|
28
|
+
warn: record("warn"),
|
|
29
|
+
error: record("error"),
|
|
30
|
+
child: (more) => make({
|
|
31
|
+
...bindings,
|
|
32
|
+
...more
|
|
33
|
+
})
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
logger: make({}),
|
|
38
|
+
entries
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { createCapturingLogger, noopLogger };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","names":[],"sources":["../../src/shared/logger.ts"],"sourcesContent":["// Test/default implementations of the Logger port. The real pino-backed\n// implementation lives in adapters/logging; these exist so constructors can\n// default to a silent logger and tests can assert on emitted entries.\nimport type { Logger } from './ports.js';\n\n/** Discards everything. The default for constructors the container overrides. */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => noopLogger,\n};\n\nexport interface CapturedLog {\n level: 'debug' | 'info' | 'warn' | 'error';\n msg: string;\n meta: Record<string, unknown>;\n}\n\n/** In-memory logger for tests: every entry (from the root or any child) lands\n * in `entries`, with child bindings merged into meta. */\nexport function createCapturingLogger(): { logger: Logger; entries: CapturedLog[] } {\n const entries: CapturedLog[] = [];\n const make = (bindings: Record<string, unknown>): Logger => {\n const record =\n (level: CapturedLog['level']) => (msg: string, meta?: Record<string, unknown>) => {\n entries.push({ level, msg, meta: { ...bindings, ...meta } });\n };\n return {\n debug: record('debug'),\n info: record('info'),\n warn: record('warn'),\n error: record('error'),\n child: (more) => make({ ...bindings, ...more }),\n };\n };\n return { logger: make({}), entries };\n}\n"],"mappings":";;AAMA,MAAa,aAAqB;CAChC,aAAa,CAAC;CACd,YAAY,CAAC;CACb,YAAY,CAAC;CACb,aAAa,CAAC;CACd,aAAa;AACf;;;AAUA,SAAgB,wBAAoE;CAClF,MAAM,UAAyB,CAAC;CAChC,MAAM,QAAQ,aAA8C;EAC1D,MAAM,UACH,WAAiC,KAAa,SAAmC;GAChF,QAAQ,KAAK;IAAE;IAAO;IAAK,MAAM;KAAE,GAAG;KAAU,GAAG;IAAK;GAAE,CAAC;EAC7D;EACF,OAAO;GACL,OAAO,OAAO,OAAO;GACrB,MAAM,OAAO,MAAM;GACnB,MAAM,OAAO,MAAM;GACnB,OAAO,OAAO,OAAO;GACrB,QAAQ,SAAS,KAAK;IAAE,GAAG;IAAU,GAAG;GAAK,CAAC;EAChD;CACF;CACA,OAAO;EAAE,QAAQ,KAAK,CAAC,CAAC;EAAG;CAAQ;AACrC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { EmailSender } from "../types/ports.js";
|
|
2
|
+
import { EmailTemplateId, EmailTemplateParams, TemplateContext, TemplateRenderer } from "../types/email-templates.js";
|
|
3
|
+
import "./ports.js";
|
|
4
|
+
//#region src/shared/mailer.d.ts
|
|
5
|
+
/** Resolves the recipient/content details a template needs when the
|
|
6
|
+
* triggering event carries only row ids. Wired by the app layer. */
|
|
7
|
+
interface MailerLookups {
|
|
8
|
+
orgUserEmail(orgId: string, orgUserId: string): Promise<string | null>;
|
|
9
|
+
contentInfo(orgId: string, contentId: string): Promise<{
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
} | null>;
|
|
13
|
+
}
|
|
14
|
+
declare class Mailer {
|
|
15
|
+
private readonly templates;
|
|
16
|
+
private readonly email;
|
|
17
|
+
private readonly ctx;
|
|
18
|
+
constructor(templates: TemplateRenderer, email: EmailSender, ctx: TemplateContext);
|
|
19
|
+
send<K extends EmailTemplateId>(to: string, id: K, params: EmailTemplateParams[K], ctx?: Partial<TemplateContext>): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { Mailer, MailerLookups };
|
|
23
|
+
//# sourceMappingURL=mailer.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/shared/mailer.ts
|
|
2
|
+
var Mailer = class {
|
|
3
|
+
templates;
|
|
4
|
+
email;
|
|
5
|
+
ctx;
|
|
6
|
+
constructor(templates, email, ctx) {
|
|
7
|
+
this.templates = templates;
|
|
8
|
+
this.email = email;
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
async send(to, id, params, ctx) {
|
|
12
|
+
const content = await this.templates.render(id, {
|
|
13
|
+
...this.ctx,
|
|
14
|
+
...ctx
|
|
15
|
+
}, params);
|
|
16
|
+
await this.email.send({
|
|
17
|
+
to,
|
|
18
|
+
subject: content.subject,
|
|
19
|
+
text: content.text,
|
|
20
|
+
html: content.html
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { Mailer };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=mailer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailer.js","names":[],"sources":["../../src/shared/mailer.ts"],"sourcesContent":["// Composes a TemplateRenderer with an EmailSender: callers name a template,\n// the mailer resolves content and hands it to the transport.\nimport type {\n EmailSender,\n EmailTemplateId,\n EmailTemplateParams,\n TemplateContext,\n TemplateRenderer,\n} from './ports.js';\n\n/** Resolves the recipient/content details a template needs when the\n * triggering event carries only row ids. Wired by the app layer. */\nexport interface MailerLookups {\n orgUserEmail(orgId: string, orgUserId: string): Promise<string | null>;\n contentInfo(orgId: string, contentId: string): Promise<{ id: string; title: string } | null>;\n}\n\nexport class Mailer {\n constructor(\n private readonly templates: TemplateRenderer,\n private readonly email: EmailSender,\n private readonly ctx: TemplateContext,\n ) {}\n\n async send<K extends EmailTemplateId>(\n to: string,\n id: K,\n params: EmailTemplateParams[K],\n ctx?: Partial<TemplateContext>,\n ): Promise<void> {\n const content = await this.templates.render(id, { ...this.ctx, ...ctx }, params);\n await this.email.send({ to, subject: content.subject, text: content.text, html: content.html });\n }\n\n\n}\n"],"mappings":";AAiBA,IAAa,SAAb,MAAoB;CAEC;CACA;CACA;CAHnB,YACE,WACA,OACA,KACA;EAHiB,KAAA,YAAA;EACA,KAAA,QAAA;EACA,KAAA,MAAA;CAChB;CAEH,MAAM,KACJ,IACA,IACA,QACA,KACe;EACf,MAAM,UAAU,MAAM,KAAK,UAAU,OAAO,IAAI;GAAE,GAAG,KAAK;GAAK,GAAG;EAAI,GAAG,MAAM;EAC/E,MAAM,KAAK,MAAM,KAAK;GAAE;GAAI,SAAS,QAAQ;GAAS,MAAM,QAAQ;GAAM,MAAM,QAAQ;EAAK,CAAC;CAChG;AAGF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DomainEvent, NewDomainEvent } from "../types/shared.js";
|
|
2
|
+
import { EventDefinition } from "../types/events.js";
|
|
3
|
+
import { EmailMessage, EmailSender, Logger, ObjectStorage, PresignDownloadInput, PresignedUpload, StoredObjectInfo } from "../types/ports.js";
|
|
4
|
+
import { EmailContent, EmailTemplateId, EmailTemplateParams, TemplateContext, TemplateRenderer } from "../types/email-templates.js";
|
|
5
|
+
import "../types/index.js";
|
|
6
|
+
import { EventOf, EventOfValues, InvalidDomainEventError, RuntimeEventDefinition, defineEvent, eventIdSchema, eventJsonValueSchema, eventMetadataSchema } from "./events.js";
|
|
7
|
+
//#region src/shared/ports.d.ts
|
|
8
|
+
interface Clock {
|
|
9
|
+
now(): Date;
|
|
10
|
+
}
|
|
11
|
+
interface EventBus {
|
|
12
|
+
publish(event: DomainEvent): Promise<void>;
|
|
13
|
+
subscribe<E extends DomainEvent>(definition: EventDefinition<E>, handler: (event: E) => Promise<void>): void;
|
|
14
|
+
subscribe(type: string, handler: (event: DomainEvent) => Promise<void>): void;
|
|
15
|
+
subscribeAll(handler: (event: DomainEvent) => Promise<void>): void;
|
|
16
|
+
}
|
|
17
|
+
/** Appends domain events to the transactional outbox. A tx-bound member of a
|
|
18
|
+
* UnitOfWork scope, so the append shares that transaction — the event
|
|
19
|
+
* becomes visible to the relay only when the surrounding write commits. */
|
|
20
|
+
interface OutboxAppender {
|
|
21
|
+
append<E extends NewDomainEvent>(events: E[]): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/** Runs a callback atomically: every port in the scope executes in one
|
|
24
|
+
* database transaction; a thrown error rolls all of it back. */
|
|
25
|
+
interface UnitOfWork<Scope> {
|
|
26
|
+
run<T>(fn: (scope: Scope) => Promise<T>): Promise<T>;
|
|
27
|
+
}
|
|
28
|
+
interface OutboxMessage {
|
|
29
|
+
id: string;
|
|
30
|
+
/** Prior failed dispatch count — input to the relay's backoff. */
|
|
31
|
+
attempts: number;
|
|
32
|
+
payload: DomainEvent;
|
|
33
|
+
}
|
|
34
|
+
interface OutboxStore {
|
|
35
|
+
/** Due, unexhausted, unprocessed messages in id order; claims them for this reader. */
|
|
36
|
+
fetchBatch(limit: number): Promise<OutboxMessage[]>;
|
|
37
|
+
/** Published to the EventBus — stamps processedAt. */
|
|
38
|
+
markProcessed(id: string): Promise<void>;
|
|
39
|
+
/** Dispatch failed — increments attempts, records the error, schedules the retry. */
|
|
40
|
+
markFailed(id: string, error: string, nextAttemptAt: Date): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/** The relay mechanism — how committed outbox rows become dispatched events.
|
|
43
|
+
* Swappable: same-process poller today; LISTEN/NOTIFY or an external worker
|
|
44
|
+
* later, without touching producers or subscribers. */
|
|
45
|
+
interface OutboxRelay {
|
|
46
|
+
start(): void;
|
|
47
|
+
/** Graceful: resolves after the in-flight batch finishes. Safe if never started. */
|
|
48
|
+
stop(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
interface CredentialStore {
|
|
51
|
+
/** Encrypt and persist an org's secrets. Returns the ref the domain stores. */
|
|
52
|
+
store(orgId: string, secrets: Record<string, unknown>): Promise<string>;
|
|
53
|
+
/** Decrypt secrets at point of use, or null if the ref doesn't exist in the org. */
|
|
54
|
+
reveal(orgId: string, ref: string): Promise<Record<string, unknown> | null>;
|
|
55
|
+
/** Replace stored secrets in place (e.g. reconnect / token refresh). */
|
|
56
|
+
update(orgId: string, ref: string, secrets: Record<string, unknown>): Promise<void>;
|
|
57
|
+
/** Permanently delete stored secrets (e.g. disconnect). */
|
|
58
|
+
destroy(orgId: string, ref: string): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { Clock, CredentialStore, type DomainEvent, type EmailContent, type EmailMessage, type EmailSender, type EmailTemplateId, type EmailTemplateParams, EventBus, type EventDefinition, type EventOf, type EventOfValues, InvalidDomainEventError, type Logger, type NewDomainEvent, type ObjectStorage, OutboxAppender, OutboxMessage, OutboxRelay, OutboxStore, type PresignDownloadInput, type PresignedUpload, type RuntimeEventDefinition, type StoredObjectInfo, type TemplateContext, type TemplateRenderer, UnitOfWork, defineEvent, eventIdSchema, eventJsonValueSchema, eventMetadataSchema };
|
|
62
|
+
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/shared/settings.d.ts
|
|
2
|
+
interface SettingsRecord {
|
|
3
|
+
namespace: string;
|
|
4
|
+
scopeId: string;
|
|
5
|
+
value?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
declare enum SettingsNamespace {
|
|
8
|
+
content = "content"
|
|
9
|
+
}
|
|
10
|
+
interface SettingsRepository {
|
|
11
|
+
/** Rows for the given scopes, optionally narrowed to one namespace. */
|
|
12
|
+
find(orgId: string, scopeId: string, namespace?: string): Promise<SettingsRecord[]>;
|
|
13
|
+
/** Deep-merges the patch into the row's value, inserting when absent. Nested
|
|
14
|
+
* objects merge key by key; arrays and scalars are replaced outright. */
|
|
15
|
+
patch(orgId: string, namespace: string, scopeId: string, value: unknown): Promise<SettingsRecord>;
|
|
16
|
+
}
|
|
17
|
+
declare class SettingsService {
|
|
18
|
+
private readonly repo;
|
|
19
|
+
constructor(repo: SettingsRepository);
|
|
20
|
+
get<T>(orgId: string, namespace: string, scopeId: string, defaultIfNull?: T): Promise<T | undefined>;
|
|
21
|
+
patch<T>(orgId: string, namespace: string, scopeId: string, value: unknown): Promise<T>;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { SettingsNamespace, SettingsRecord, SettingsRepository, SettingsService };
|
|
25
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/shared/settings.ts
|
|
2
|
+
let SettingsNamespace = /* @__PURE__ */ function(SettingsNamespace) {
|
|
3
|
+
SettingsNamespace["content"] = "content";
|
|
4
|
+
return SettingsNamespace;
|
|
5
|
+
}({});
|
|
6
|
+
var SettingsService = class {
|
|
7
|
+
repo;
|
|
8
|
+
constructor(repo) {
|
|
9
|
+
this.repo = repo;
|
|
10
|
+
}
|
|
11
|
+
async get(orgId, namespace, scopeId, defaultIfNull) {
|
|
12
|
+
return (await this.repo.find(orgId, scopeId, namespace))[0]?.value ?? defaultIfNull;
|
|
13
|
+
}
|
|
14
|
+
async patch(orgId, namespace, scopeId, value) {
|
|
15
|
+
return (await this.repo.patch(orgId, namespace, scopeId, value)).value;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { SettingsNamespace, SettingsService };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","names":[],"sources":["../../src/shared/settings.ts"],"sourcesContent":["// Cross-cutting settings: storage, scoping and resolution for every domain's\n// configuration. Not a bounded context: it is a leaf every context may import, and it imports\n// no context.\n\nexport interface SettingsRecord {\n namespace: string;\n scopeId: string;\n value?: Record<string,unknown>;\n}\n\nexport enum SettingsNamespace {\n content = 'content',\n}\n\nexport interface SettingsRepository {\n /** Rows for the given scopes, optionally narrowed to one namespace. */\n find(orgId: string, scopeId: string, namespace?: string): Promise<SettingsRecord[]>;\n /** Deep-merges the patch into the row's value, inserting when absent. Nested\n * objects merge key by key; arrays and scalars are replaced outright. */\n patch(orgId: string, namespace: string, scopeId: string, value: unknown): Promise<SettingsRecord>;\n}\n\nexport class SettingsService {\n constructor(private readonly repo: SettingsRepository) {}\n\n async get<T>(\n orgId: string,\n namespace: string,\n scopeId: string,\n defaultIfNull?: T,\n ): Promise<T | undefined> {\n const rows = await this.repo.find(orgId, scopeId, namespace);\n return (rows[0]?.value as T) ?? defaultIfNull;\n }\n\n async patch<T>(orgId: string, namespace: string, scopeId: string, value: unknown): Promise<T> {\n const row = await this.repo.patch(orgId, namespace, scopeId, value);\n return row.value as T;\n }\n}\n"],"mappings":";AAUA,IAAY,oBAAL,yBAAA,mBAAA;CACL,kBAAA,aAAA;;AACF,EAAA,CAAA,CAAA;AAUA,IAAa,kBAAb,MAA6B;CACE;CAA7B,YAAY,MAA2C;EAA1B,KAAA,OAAA;CAA2B;CAExD,MAAM,IACJ,OACA,WACA,SACA,eACwB;EAExB,QAAQ,MADW,KAAK,KAAK,KAAK,OAAO,SAAS,SAAS,EAAA,CAC9C,EAAE,EAAE,SAAe;CAClC;CAEA,MAAM,MAAS,OAAe,WAAmB,SAAiB,OAA4B;EAE5F,QAAO,MADW,KAAK,KAAK,MAAM,OAAO,WAAW,SAAS,KAAK,EAAA,CACvD;CACb;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|