@headless-lms/server 0.3.0 → 0.5.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 +14 -0
- package/dist/app/community-mail.d.ts +10 -10
- package/dist/app/community-mail.js +72 -28
- package/dist/app/community-mail.js.map +1 -1
- package/dist/app/container.d.ts +48 -10
- package/dist/app/container.js +268 -48
- package/dist/app/container.js.map +1 -1
- package/dist/app/notification-channels.d.ts +47 -0
- package/dist/app/notification-channels.js +175 -0
- package/dist/app/notification-channels.js.map +1 -0
- package/dist/app/notification-reads.d.ts +23 -0
- package/dist/app/notification-reads.js +74 -0
- package/dist/app/notification-reads.js.map +1 -0
- package/dist/app/platform.d.ts +14 -0
- package/dist/app/platform.js +14 -0
- package/dist/app/platform.js.map +1 -0
- package/dist/http/config.d.ts +4 -0
- package/dist/http/errors.d.ts +1 -0
- package/dist/http/errors.js +24 -4
- package/dist/http/errors.js.map +1 -1
- package/dist/http/fastify.d.ts +8 -0
- package/dist/http/mcp/catalogue.d.ts +11 -9
- package/dist/http/mcp/catalogue.js +15 -21
- package/dist/http/mcp/catalogue.js.map +1 -1
- package/dist/http/mcp/challenge.js +3 -3
- package/dist/http/mcp/challenge.js.map +1 -1
- package/dist/http/mcp/route.js +7 -7
- package/dist/http/mcp/route.js.map +1 -1
- package/dist/http/mcp/test-support.d.ts +6 -5
- package/dist/http/mcp/test-support.js +32 -11
- package/dist/http/mcp/test-support.js.map +1 -1
- package/dist/http/mcp/tool.d.ts +3 -9
- package/dist/http/mcp/tool.js +14 -6
- package/dist/http/mcp/tool.js.map +1 -1
- package/dist/http/mcp/tools/automations.js +10 -20
- package/dist/http/mcp/tools/automations.js.map +1 -1
- package/dist/http/mcp/tools/catalog.js +25 -42
- package/dist/http/mcp/tools/catalog.js.map +1 -1
- package/dist/http/mcp/tools/comments.js +9 -19
- package/dist/http/mcp/tools/comments.js.map +1 -1
- package/dist/http/mcp/tools/communities.js +16 -31
- package/dist/http/mcp/tools/communities.js.map +1 -1
- package/dist/http/mcp/tools/community-members.js +9 -16
- package/dist/http/mcp/tools/community-members.js.map +1 -1
- package/dist/http/mcp/tools/community-moderation.js +13 -25
- package/dist/http/mcp/tools/community-moderation.js.map +1 -1
- package/dist/http/mcp/tools/courses.js +11 -21
- package/dist/http/mcp/tools/courses.js.map +1 -1
- package/dist/http/mcp/tools/events.d.ts +6 -0
- package/dist/http/mcp/tools/events.js +170 -0
- package/dist/http/mcp/tools/events.js.map +1 -0
- package/dist/http/mcp/tools/index.d.ts +5 -1
- package/dist/http/mcp/tools/index.js +45 -10
- package/dist/http/mcp/tools/index.js.map +1 -1
- package/dist/http/mcp/tools/organization.d.ts +6 -0
- package/dist/http/mcp/tools/organization.js +214 -0
- package/dist/http/mcp/tools/organization.js.map +1 -0
- package/dist/http/mcp/tools/people.js +30 -49
- package/dist/http/mcp/tools/people.js.map +1 -1
- package/dist/http/plugins/auth.js +37 -8
- package/dist/http/plugins/auth.js.map +1 -1
- package/dist/http/plugins/error-handler.d.ts +5 -1
- package/dist/http/plugins/error-handler.js +13 -3
- package/dist/http/plugins/error-handler.js.map +1 -1
- package/dist/http/plugins/openapi.js +18 -2
- package/dist/http/plugins/openapi.js.map +1 -1
- package/dist/http/plugins/platform.d.ts +7 -0
- package/dist/http/plugins/platform.js +13 -0
- package/dist/http/plugins/platform.js.map +1 -0
- package/dist/http/routes/access.js +54 -36
- package/dist/http/routes/access.js.map +1 -1
- package/dist/http/routes/activities.js +45 -45
- package/dist/http/routes/activities.js.map +1 -1
- package/dist/http/routes/assets.js +30 -65
- package/dist/http/routes/assets.js.map +1 -1
- package/dist/http/routes/automations.js +37 -42
- package/dist/http/routes/automations.js.map +1 -1
- package/dist/http/routes/bundles.js +33 -36
- package/dist/http/routes/bundles.js.map +1 -1
- package/dist/http/routes/communities.js +144 -136
- package/dist/http/routes/communities.js.map +1 -1
- package/dist/http/routes/community-moderation.js +83 -137
- package/dist/http/routes/community-moderation.js.map +1 -1
- package/dist/http/routes/course-analytics.js +32 -15
- package/dist/http/routes/course-analytics.js.map +1 -1
- package/dist/http/routes/courses.js +25 -32
- package/dist/http/routes/courses.js.map +1 -1
- package/dist/http/routes/dashboard.js +9 -9
- package/dist/http/routes/dashboard.js.map +1 -1
- package/dist/http/routes/discussion.js +21 -39
- package/dist/http/routes/discussion.js.map +1 -1
- package/dist/http/routes/downloads.js +41 -47
- package/dist/http/routes/downloads.js.map +1 -1
- package/dist/http/routes/entitlements.js +19 -23
- package/dist/http/routes/entitlements.js.map +1 -1
- package/dist/http/routes/events.d.ts +7 -0
- package/dist/http/routes/events.js +333 -0
- package/dist/http/routes/events.js.map +1 -0
- package/dist/http/routes/integrations.js +40 -73
- package/dist/http/routes/integrations.js.map +1 -1
- package/dist/http/routes/member/assets.js +10 -13
- package/dist/http/routes/member/assets.js.map +1 -1
- package/dist/http/routes/member/chats.js +177 -75
- package/dist/http/routes/member/chats.js.map +1 -1
- package/dist/http/routes/member/comments.js +65 -26
- package/dist/http/routes/member/comments.js.map +1 -1
- package/dist/http/routes/member/community.js +68 -51
- package/dist/http/routes/member/community.js.map +1 -1
- package/dist/http/routes/member/courses.js +12 -19
- package/dist/http/routes/member/courses.js.map +1 -1
- package/dist/http/routes/member/devices.d.ts +7 -0
- package/dist/http/routes/member/devices.js +97 -0
- package/dist/http/routes/member/devices.js.map +1 -0
- package/dist/http/routes/member/downloads.js +6 -9
- package/dist/http/routes/member/downloads.js.map +1 -1
- package/dist/http/routes/member/events.d.ts +7 -0
- package/dist/http/routes/member/events.js +386 -0
- package/dist/http/routes/member/events.js.map +1 -0
- package/dist/http/routes/member/files.js +71 -46
- package/dist/http/routes/member/files.js.map +1 -1
- package/dist/http/routes/member/index.js +6 -0
- package/dist/http/routes/member/index.js.map +1 -1
- package/dist/http/routes/member/notification-settings.d.ts +7 -0
- package/dist/http/routes/member/notification-settings.js +100 -0
- package/dist/http/routes/member/notification-settings.js.map +1 -0
- package/dist/http/routes/member/notifications.js +69 -45
- package/dist/http/routes/member/notifications.js.map +1 -1
- package/dist/http/routes/member/org.js +5 -15
- package/dist/http/routes/member/org.js.map +1 -1
- package/dist/http/routes/member/posts.js +94 -95
- package/dist/http/routes/member/posts.js.map +1 -1
- package/dist/http/routes/member/profile.js +15 -6
- package/dist/http/routes/member/profile.js.map +1 -1
- package/dist/http/routes/member/progress.js +7 -11
- package/dist/http/routes/member/progress.js.map +1 -1
- package/dist/http/routes/organizations.js +415 -110
- package/dist/http/routes/organizations.js.map +1 -1
- package/dist/http/routes/platform.d.ts +7 -0
- package/dist/http/routes/platform.js +61 -0
- package/dist/http/routes/platform.js.map +1 -0
- package/dist/http/routes/students.js +12 -75
- package/dist/http/routes/students.js.map +1 -1
- package/dist/http/routes/webhooks.js +30 -39
- package/dist/http/routes/webhooks.js.map +1 -1
- package/dist/http/routes.js +9 -0
- package/dist/http/routes.js.map +1 -1
- package/dist/http/schemas/access.d.ts +346 -64
- package/dist/http/schemas/access.js +39 -12
- package/dist/http/schemas/access.js.map +1 -1
- package/dist/http/schemas/assets.d.ts +13 -8
- package/dist/http/schemas/assets.js +6 -5
- package/dist/http/schemas/assets.js.map +1 -1
- package/dist/http/schemas/automations.d.ts +4 -8
- package/dist/http/schemas/chats.d.ts +238 -36
- package/dist/http/schemas/chats.js +14 -7
- package/dist/http/schemas/chats.js.map +1 -1
- package/dist/http/schemas/community.d.ts +140 -33
- package/dist/http/schemas/community.js +25 -9
- package/dist/http/schemas/community.js.map +1 -1
- package/dist/http/schemas/course-analytics.d.ts +51 -1
- package/dist/http/schemas/course-analytics.js +19 -1
- package/dist/http/schemas/course-analytics.js.map +1 -1
- package/dist/http/schemas/dashboard.d.ts +1 -0
- package/dist/http/schemas/dashboard.js +2 -1
- package/dist/http/schemas/dashboard.js.map +1 -1
- package/dist/http/schemas/discussion.d.ts +45 -73
- package/dist/http/schemas/discussion.js +16 -4
- package/dist/http/schemas/discussion.js.map +1 -1
- package/dist/http/schemas/entitlements.d.ts +9 -12
- package/dist/http/schemas/entitlements.js +11 -5
- package/dist/http/schemas/entitlements.js.map +1 -1
- package/dist/http/schemas/events.d.ts +527 -0
- package/dist/http/schemas/events.js +64 -0
- package/dist/http/schemas/events.js.map +1 -0
- package/dist/http/schemas/files.d.ts +15 -5
- package/dist/http/schemas/files.js +10 -2
- package/dist/http/schemas/files.js.map +1 -1
- package/dist/http/schemas/index.d.ts +14 -12
- package/dist/http/schemas/index.js +14 -12
- package/dist/http/schemas/invites.d.ts +3 -47
- package/dist/http/schemas/invites.js +4 -25
- package/dist/http/schemas/invites.js.map +1 -1
- package/dist/http/schemas/member.d.ts +94 -7
- package/dist/http/schemas/member.js +7 -3
- package/dist/http/schemas/member.js.map +1 -1
- package/dist/http/schemas/members.d.ts +329 -65
- package/dist/http/schemas/members.js +67 -25
- package/dist/http/schemas/members.js.map +1 -1
- package/dist/http/schemas/notifications.d.ts +502 -175
- package/dist/http/schemas/notifications.js +148 -28
- package/dist/http/schemas/notifications.js.map +1 -1
- package/dist/http/schemas/platform.d.ts +37 -0
- package/dist/http/schemas/platform.js +19 -0
- package/dist/http/schemas/platform.js.map +1 -0
- package/dist/http/schemas/posts.d.ts +92 -38
- package/dist/http/schemas/posts.js +4 -4
- package/dist/http/schemas/posts.js.map +1 -1
- package/dist/http/schemas/profile.d.ts +1 -1
- package/dist/http/schemas/shared.d.ts +10 -13
- package/dist/http/schemas/shared.js +3 -11
- package/dist/http/schemas/shared.js.map +1 -1
- package/dist/http/schemas/students.d.ts +22 -0
- package/dist/http/schemas/students.js +3 -1
- package/dist/http/schemas/students.js.map +1 -1
- package/dist/http/scope.d.ts +9 -12
- package/dist/http/scope.js +30 -35
- package/dist/http/scope.js.map +1 -1
- package/dist/http/server.js +9 -0
- package/dist/http/server.js.map +1 -1
- package/dist/http/shutdown.d.ts +11 -0
- package/dist/http/shutdown.js +40 -0
- package/dist/http/shutdown.js.map +1 -0
- package/dist/http/sse.d.ts +19 -0
- package/dist/http/sse.js +104 -0
- package/dist/http/sse.js.map +1 -0
- package/dist/http/test-caller.d.ts +13 -0
- package/dist/http/test-caller.js +14 -0
- package/dist/http/test-caller.js.map +1 -0
- package/dist/http/well-known.js +2 -2
- package/dist/http/well-known.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/packages/server/package.js +1 -1
- package/package.json +7 -6
- package/dist/http/routes/member/actor.d.ts +0 -14
- package/dist/http/routes/member/actor.js +0 -11
- package/dist/http/routes/member/actor.js.map +0 -1
|
@@ -12,9 +12,10 @@ const ValidationErrorBody = ErrorBody.extend({ issues: z.array(z.object({
|
|
|
12
12
|
path: z.string().describe("Where in the request, such as `body.topics[1].name`"),
|
|
13
13
|
message: z.string()
|
|
14
14
|
})).optional() });
|
|
15
|
+
const UnavailableErrorBody = ErrorBody.extend({ details: z.record(z.string(), z.string()).optional().describe("Ids of what the request left behind, such as the post kept as a draft") });
|
|
15
16
|
/** Response schemas for the refusals a route can answer. A 400 keeps the validation `issues`. */
|
|
16
17
|
function refusals(...statuses) {
|
|
17
|
-
return Object.fromEntries(statuses.map((status) => [status, status === 400 ? ValidationErrorBody : ErrorBody]));
|
|
18
|
+
return Object.fromEntries(statuses.map((status) => [status, status === 400 || status === 422 ? ValidationErrorBody : status === 503 ? UnavailableErrorBody : ErrorBody]));
|
|
18
19
|
}
|
|
19
20
|
/** An RFC 3339 date-time in a request, such as `2026-10-29T12:00:00Z`, read as a Date. */
|
|
20
21
|
const DateTimeInput = z.iso.datetime({ offset: true }).pipe(z.coerce.date());
|
|
@@ -43,15 +44,6 @@ function paginated(row) {
|
|
|
43
44
|
pageSize: z.number().int()
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
|
-
/** All four org roles. `members.ts` exports a staff-only three-value `Role`;
|
|
47
|
-
* this is the full set, used wherever a learner can also appear. */
|
|
48
|
-
const OrgRole = z.enum([
|
|
49
|
-
"owner",
|
|
50
|
-
"admin",
|
|
51
|
-
"instructor",
|
|
52
|
-
"moderator",
|
|
53
|
-
"student"
|
|
54
|
-
]);
|
|
55
47
|
/** Mirrors UserProfile in @headless-lms/core/types — keyed on the auth user id. */
|
|
56
48
|
const UserProfileSchema = z.object({
|
|
57
49
|
id: z.string(),
|
|
@@ -68,6 +60,6 @@ const OrgUserProfileSchema = z.object({
|
|
|
68
60
|
image: z.string().nullable()
|
|
69
61
|
});
|
|
70
62
|
//#endregion
|
|
71
|
-
export { DateTimeInput, ErrorBody, ListQuery,
|
|
63
|
+
export { DateTimeInput, ErrorBody, ListQuery, OrgUserProfileSchema, PageQuery, UnavailableErrorBody, UserProfileSchema, ValidationErrorBody, paginated, refusals };
|
|
72
64
|
|
|
73
65
|
//# sourceMappingURL=shared.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","names":[],"sources":["../../../src/http/schemas/shared.ts"],"sourcesContent":["// Shared contract primitives reused across resource contracts.\nimport { z } from 'zod';\n\n/** Fails typecheck when a schema and its domain type drift apart. */\nexport type Matches<A, _B extends A> = true;\n\n/** Uniform error envelope returned by every non-2xx response. */\nexport const ErrorBody = z.object({\n error: z.string(),\n message: z.string().optional(),\n /** Server-side correlation id — quote it to find the matching API log line. */\n requestId: z.string().optional(),\n});\nexport type ErrorBody = z.infer<typeof ErrorBody>;\n\n/** A 400 from request validation: `message` names the first problem, `issues` lists them all. */\nexport const ValidationErrorBody = ErrorBody.extend({\n issues: z\n .array(\n z.object({\n path: z.string().describe('Where in the request, such as `body.topics[1].name`'),\n message: z.string(),\n }),\n )\n .optional(),\n});\nexport type ValidationErrorBody = z.infer<typeof ValidationErrorBody>;\n\ntype RefusalStatus = 400 | 403 | 404 | 409 | 413 | 415 | 502;\ntype RefusalBodies<S extends RefusalStatus> = {\n [K in S]: K extends 400 ? typeof ValidationErrorBody : typeof ErrorBody;\n};\n\n/** Response schemas for the refusals a route can answer. A 400 keeps the validation `issues`. */\nexport function refusals<S extends RefusalStatus>(...statuses: S[]): RefusalBodies<S> {\n return Object.fromEntries(\n statuses.map((status) => [status
|
|
1
|
+
{"version":3,"file":"shared.js","names":[],"sources":["../../../src/http/schemas/shared.ts"],"sourcesContent":["// Shared contract primitives reused across resource contracts.\nimport { z } from 'zod';\n\n/** Fails typecheck when a schema and its domain type drift apart. */\nexport type Matches<A, _B extends A> = true;\n\n/** Uniform error envelope returned by every non-2xx response. */\nexport const ErrorBody = z.object({\n error: z.string(),\n message: z.string().optional(),\n /** Server-side correlation id — quote it to find the matching API log line. */\n requestId: z.string().optional(),\n});\nexport type ErrorBody = z.infer<typeof ErrorBody>;\n\n/** A 400 from request validation: `message` names the first problem, `issues` lists them all. */\nexport const ValidationErrorBody = ErrorBody.extend({\n issues: z\n .array(\n z.object({\n path: z.string().describe('Where in the request, such as `body.topics[1].name`'),\n message: z.string(),\n }),\n )\n .optional(),\n});\nexport type ValidationErrorBody = z.infer<typeof ValidationErrorBody>;\n\nexport const UnavailableErrorBody = ErrorBody.extend({\n details: z\n .record(z.string(), z.string())\n .optional()\n .describe('Ids of what the request left behind, such as the post kept as a draft'),\n});\nexport type UnavailableErrorBody = z.infer<typeof UnavailableErrorBody>;\n\ntype RefusalStatus = 400 | 403 | 404 | 409 | 413 | 415 | 422 | 502 | 503;\ntype RefusalBodies<S extends RefusalStatus> = {\n [K in S]: K extends 400 | 422\n ? typeof ValidationErrorBody\n : K extends 503\n ? typeof UnavailableErrorBody\n : typeof ErrorBody;\n};\n\n/** Response schemas for the refusals a route can answer. A 400 keeps the validation `issues`. */\nexport function refusals<S extends RefusalStatus>(...statuses: S[]): RefusalBodies<S> {\n return Object.fromEntries(\n statuses.map((status) => [\n status,\n status === 400 || status === 422\n ? ValidationErrorBody\n : status === 503\n ? UnavailableErrorBody\n : ErrorBody,\n ]),\n ) as RefusalBodies<S>;\n}\n\n/** An RFC 3339 date-time in a request, such as `2026-10-29T12:00:00Z`, read as a Date. */\nexport const DateTimeInput = z.iso.datetime({ offset: true }).pipe(z.coerce.date());\n\n/** Page position only, for lists that take no search or sort. */\nexport const PageQuery = z\n .object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n })\n .strict();\nexport type PageQuery = z.infer<typeof PageQuery>;\n\n/**\n * Common list query for page-based collections. `z.coerce` because query-string\n * values arrive as strings; defaults make the params optional for callers.\n */\nexport const ListQuery = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n search: z.string().trim().max(200).optional(),\n /** Sort field, optionally prefixed with `-` for descending (e.g. `-updatedAt`). */\n sort: z.string().optional(),\n});\nexport type ListQuery = z.infer<typeof ListQuery>;\n\n/** Wrap a row schema into the standard paginated envelope. */\nexport function paginated<T extends z.ZodTypeAny>(row: T) {\n return z.object({\n rows: z.array(row),\n total: z.number().int(),\n page: z.number().int(),\n pageSize: z.number().int(),\n });\n}\n\n/** Mirrors UserProfile in @headless-lms/core/types — keyed on the auth user id. */\nexport const UserProfileSchema = z.object({\n id: z.string(),\n name: z.string(),\n email: z.string(),\n image: z.string().nullable(),\n});\nexport type UserProfileSchema = z.infer<typeof UserProfileSchema>;\n\n/** Mirrors OrgUserProfile in @headless-lms/core/types — keyed on org_users.id. */\nexport const OrgUserProfileSchema = z.object({\n id: z.string(),\n firstName: z.string().nullable(),\n lastName: z.string().nullable(),\n email: z.string(),\n image: z.string().nullable(),\n});\nexport type OrgUserProfileSchema = z.infer<typeof OrgUserProfileSchema>;\n"],"mappings":";;;AAOA,MAAa,YAAY,EAAE,OAAO;CAChC,OAAO,EAAE,OAAO;CAChB,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;;CAE7B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;AACjC,CAAC;;AAID,MAAa,sBAAsB,UAAU,OAAO,EAClD,QAAQ,EACL,MACC,EAAE,OAAO;CACP,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS,qDAAqD;CAC/E,SAAS,EAAE,OAAO;AACpB,CAAC,CACH,CAAC,CACA,SAAS,EACd,CAAC;AAGD,MAAa,uBAAuB,UAAU,OAAO,EACnD,SAAS,EACN,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAC9B,SAAS,CAAC,CACV,SAAS,uEAAuE,EACrF,CAAC;;AAaD,SAAgB,SAAkC,GAAG,UAAiC;CACpF,OAAO,OAAO,YACZ,SAAS,KAAK,WAAW,CACvB,QACA,WAAW,OAAO,WAAW,MACzB,sBACA,WAAW,MACT,uBACA,SACR,CAAC,CACH;AACF;;AAGA,MAAa,gBAAgB,EAAE,IAAI,SAAS,EAAE,QAAQ,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;;AAGlF,MAAa,YAAY,EACtB,OAAO;CACN,MAAM,EAAE,OAAO,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;CAC9C,UAAU,EAAE,OAAO,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE;AAC9D,CAAC,CAAC,CACD,OAAO;;;;;AAOV,MAAa,YAAY,EAAE,OAAO;CAChC,MAAM,EAAE,OAAO,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;CAC9C,UAAU,EAAE,OAAO,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE;CAC5D,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,SAAS;;CAE5C,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;AAC5B,CAAC;;AAID,SAAgB,UAAkC,KAAQ;CACxD,OAAO,EAAE,OAAO;EACd,MAAM,EAAE,MAAM,GAAG;EACjB,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI;EACtB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI;EACrB,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI;CAC3B,CAAC;AACH;;AAGA,MAAa,oBAAoB,EAAE,OAAO;CACxC,IAAI,EAAE,OAAO;CACb,MAAM,EAAE,OAAO;CACf,OAAO,EAAE,OAAO;CAChB,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;AAC7B,CAAC;;AAID,MAAa,uBAAuB,EAAE,OAAO;CAC3C,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,OAAO,EAAE,OAAO;CAChB,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;AAC7B,CAAC"}
|
|
@@ -19,6 +19,17 @@ declare const Student: z.ZodObject<{
|
|
|
19
19
|
}>;
|
|
20
20
|
joinedAt: z.ZodString;
|
|
21
21
|
lastActiveAt: z.ZodNullable<z.ZodString>;
|
|
22
|
+
items: z.ZodArray<z.ZodObject<{
|
|
23
|
+
contentId: z.ZodString;
|
|
24
|
+
contentType: z.ZodEnum<{
|
|
25
|
+
community: "community";
|
|
26
|
+
course: "course";
|
|
27
|
+
download: "download";
|
|
28
|
+
event: "event";
|
|
29
|
+
}>;
|
|
30
|
+
roleKey: z.ZodString;
|
|
31
|
+
roleName: z.ZodString;
|
|
32
|
+
}, z.core.$strip>>;
|
|
22
33
|
}, z.core.$strip>;
|
|
23
34
|
type Student = z.infer<typeof Student>;
|
|
24
35
|
declare const StudentCourseProgress: z.ZodObject<{
|
|
@@ -71,6 +82,17 @@ declare const StudentsPage: z.ZodObject<{
|
|
|
71
82
|
}>;
|
|
72
83
|
joinedAt: z.ZodString;
|
|
73
84
|
lastActiveAt: z.ZodNullable<z.ZodString>;
|
|
85
|
+
items: z.ZodArray<z.ZodObject<{
|
|
86
|
+
contentId: z.ZodString;
|
|
87
|
+
contentType: z.ZodEnum<{
|
|
88
|
+
community: "community";
|
|
89
|
+
course: "course";
|
|
90
|
+
download: "download";
|
|
91
|
+
event: "event";
|
|
92
|
+
}>;
|
|
93
|
+
roleKey: z.ZodString;
|
|
94
|
+
roleName: z.ZodString;
|
|
95
|
+
}, z.core.$strip>>;
|
|
74
96
|
}, z.core.$strip>>;
|
|
75
97
|
total: z.ZodNumber;
|
|
76
98
|
page: z.ZodNumber;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ListQuery, OrgUserProfileSchema, paginated } from "./shared.js";
|
|
2
|
+
import { ItemRole } from "./members.js";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
//#region src/http/schemas/students.ts
|
|
4
5
|
const OrgUserStatus = z.enum(["invited", "active"]);
|
|
@@ -9,7 +10,8 @@ const Student = OrgUserProfileSchema.extend({
|
|
|
9
10
|
/** `invited` means an admin added them and they have not accepted yet. */
|
|
10
11
|
status: OrgUserStatus,
|
|
11
12
|
joinedAt: z.string(),
|
|
12
|
-
lastActiveAt: z.string().nullable()
|
|
13
|
+
lastActiveAt: z.string().nullable(),
|
|
14
|
+
items: z.array(ItemRole).describe("The roles they hold on content items.")
|
|
13
15
|
});
|
|
14
16
|
const StudentCourseProgress = z.object({
|
|
15
17
|
courseId: z.string(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"students.js","names":[],"sources":["../../../src/http/schemas/students.ts"],"sourcesContent":["// Students resource schemas.\nimport { z } from 'zod';\nimport { ListQuery, paginated, OrgUserProfileSchema } from './shared.js';\n\nexport const OrgUserStatus = z.enum(['invited', 'active']);\nexport type OrgUserStatus = z.infer<typeof OrgUserStatus>;\n\nexport const Student = OrgUserProfileSchema.extend({\n entitlementCount: z.number().int(),\n /** 0–100, averaged across active entitlements. */\n avgProgress: z.number().int(),\n /** `invited` means an admin added them and they have not accepted yet. */\n status: OrgUserStatus,\n joinedAt: z.string(),\n lastActiveAt: z.string().nullable(),\n});\nexport type Student = z.infer<typeof Student>;\n\nexport const StudentCourseProgress = z.object({\n courseId: z.string(),\n title: z.string(),\n totalActivities: z.number().int(),\n completedActivities: z.number().int(),\n /** 0–100, completed published activities over the course's total. */\n progress: z.number().int(),\n startedAt: z.string().nullable(),\n lastActivityAt: z.string().nullable(),\n completedAt: z.string().nullable(),\n});\nexport type StudentCourseProgress = z.infer<typeof StudentCourseProgress>;\n\nexport const StudentAnalytics = z.object({\n enrolled: z.number().int(),\n started: z.number().int(),\n completed: z.number().int(),\n avgProgress: z.number().int(),\n courses: z.array(StudentCourseProgress),\n});\nexport type StudentAnalytics = z.infer<typeof StudentAnalytics>;\n\nexport const StudentsQuery = ListQuery;\nexport type StudentsQuery = z.infer<typeof StudentsQuery>;\n\nexport const StudentsPage = paginated(Student);\nexport type StudentsPage = z.infer<typeof StudentsPage>;\n\nexport const StudentIdParam = z.object({ id: z.string() });\nexport type StudentIdParam = z.infer<typeof StudentIdParam>;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"students.js","names":[],"sources":["../../../src/http/schemas/students.ts"],"sourcesContent":["// Students resource schemas.\nimport { z } from 'zod';\nimport { ListQuery, paginated, OrgUserProfileSchema } from './shared.js';\nimport { ItemRole } from './members.js';\n\nexport const OrgUserStatus = z.enum(['invited', 'active']);\nexport type OrgUserStatus = z.infer<typeof OrgUserStatus>;\n\nexport const Student = OrgUserProfileSchema.extend({\n entitlementCount: z.number().int(),\n /** 0–100, averaged across active entitlements. */\n avgProgress: z.number().int(),\n /** `invited` means an admin added them and they have not accepted yet. */\n status: OrgUserStatus,\n joinedAt: z.string(),\n lastActiveAt: z.string().nullable(),\n items: z.array(ItemRole).describe('The roles they hold on content items.'),\n});\nexport type Student = z.infer<typeof Student>;\n\nexport const StudentCourseProgress = z.object({\n courseId: z.string(),\n title: z.string(),\n totalActivities: z.number().int(),\n completedActivities: z.number().int(),\n /** 0–100, completed published activities over the course's total. */\n progress: z.number().int(),\n startedAt: z.string().nullable(),\n lastActivityAt: z.string().nullable(),\n completedAt: z.string().nullable(),\n});\nexport type StudentCourseProgress = z.infer<typeof StudentCourseProgress>;\n\nexport const StudentAnalytics = z.object({\n enrolled: z.number().int(),\n started: z.number().int(),\n completed: z.number().int(),\n avgProgress: z.number().int(),\n courses: z.array(StudentCourseProgress),\n});\nexport type StudentAnalytics = z.infer<typeof StudentAnalytics>;\n\nexport const StudentsQuery = ListQuery;\nexport type StudentsQuery = z.infer<typeof StudentsQuery>;\n\nexport const StudentsPage = paginated(Student);\nexport type StudentsPage = z.infer<typeof StudentsPage>;\n\nexport const StudentIdParam = z.object({ id: z.string() });\nexport type StudentIdParam = z.infer<typeof StudentIdParam>;\n"],"mappings":";;;;AAKA,MAAa,gBAAgB,EAAE,KAAK,CAAC,WAAW,QAAQ,CAAC;AAGzD,MAAa,UAAU,qBAAqB,OAAO;CACjD,kBAAkB,EAAE,OAAO,CAAC,CAAC,IAAI;;CAEjC,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI;;CAE5B,QAAQ;CACR,UAAU,EAAE,OAAO;CACnB,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,OAAO,EAAE,MAAM,QAAQ,CAAC,CAAC,SAAS,uCAAuC;AAC3E,CAAC;AAGD,MAAa,wBAAwB,EAAE,OAAO;CAC5C,UAAU,EAAE,OAAO;CACnB,OAAO,EAAE,OAAO;CAChB,iBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI;CAChC,qBAAqB,EAAE,OAAO,CAAC,CAAC,IAAI;;CAEpC,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI;CACzB,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,gBAAgB,EAAE,OAAO,CAAC,CAAC,SAAS;CACpC,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;AACnC,CAAC;AAGD,MAAa,mBAAmB,EAAE,OAAO;CACvC,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI;CACzB,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI;CACxB,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI;CAC1B,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI;CAC5B,SAAS,EAAE,MAAM,qBAAqB;AACxC,CAAC;AAGD,MAAa,gBAAgB;AAG7B,MAAa,eAAe,UAAU,OAAO;AAG7C,MAAa,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/http/scope.d.ts
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import { Container } from "../app/container.js";
|
|
2
|
-
import {
|
|
2
|
+
import { CallerLimit } from "@headless-lms/core/organizations";
|
|
3
3
|
import { FastifyRequest } from "fastify";
|
|
4
|
+
import { Actor } from "@headless-lms/core/shared/actor";
|
|
4
5
|
//#region src/http/scope.d.ts
|
|
5
|
-
interface
|
|
6
|
-
|
|
6
|
+
interface Caller {
|
|
7
|
+
actor: Actor;
|
|
7
8
|
orgId: string;
|
|
8
|
-
/** Domain `users.id` of the acting person. */
|
|
9
9
|
userId: string;
|
|
10
|
-
/** Domain `org_users.id` — the acting org user in this org. */
|
|
11
10
|
orgUserId: string;
|
|
12
|
-
/** The role held in this org. */
|
|
13
|
-
role: StaffRole;
|
|
14
|
-
/** Better-auth organization id (for writes that go through the auth provider). */
|
|
15
11
|
authOrgId: string;
|
|
12
|
+
authUserId: string;
|
|
16
13
|
}
|
|
17
|
-
/** Thrown when the session has no resolvable active org / domain user. */
|
|
18
14
|
declare class NoActiveOrgError extends Error {}
|
|
19
|
-
declare function
|
|
20
|
-
declare function
|
|
15
|
+
declare function resolveCaller(container: Container, req: FastifyRequest): Promise<Caller>;
|
|
16
|
+
declare function resolveActor(container: Container, req: FastifyRequest): Promise<Actor>;
|
|
17
|
+
declare function resolveCallerFor(container: Container, authUserId: string, authOrgId: string, limit: CallerLimit): Promise<Caller>;
|
|
21
18
|
//#endregion
|
|
22
|
-
export { NoActiveOrgError,
|
|
19
|
+
export { Caller, NoActiveOrgError, resolveActor, resolveCaller, resolveCallerFor };
|
|
23
20
|
//# sourceMappingURL=scope.d.ts.map
|
package/dist/http/scope.js
CHANGED
|
@@ -1,44 +1,39 @@
|
|
|
1
|
-
import { isStaffRole } from "@headless-lms/core/organizations";
|
|
2
1
|
//#region src/http/scope.ts
|
|
3
|
-
/** Thrown when the session has no resolvable active org / domain user. */
|
|
4
2
|
var NoActiveOrgError = class extends Error {};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (!
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (!
|
|
14
|
-
const orgUser = await container.organizations.getOrgUser(org.id, user.id);
|
|
15
|
-
if (!orgUser || !isStaffRole(orgUser.role)) throw new NoActiveOrgError("Only staff of this organization can do this");
|
|
16
|
-
container.requestContext.set({ orgId: org.id });
|
|
3
|
+
function callerFrom(container, resolved, authUserId, authOrgId) {
|
|
4
|
+
if (!resolved) throw new NoActiveOrgError("The active organization no longer exists");
|
|
5
|
+
if (!resolved.userId) throw new NoActiveOrgError("Your account is not set up yet");
|
|
6
|
+
if (!resolved.orgUserId) throw new NoActiveOrgError("You are not a member of this organization");
|
|
7
|
+
container.requestContext.set({
|
|
8
|
+
orgId: resolved.orgId,
|
|
9
|
+
actorId: resolved.orgUserId
|
|
10
|
+
});
|
|
11
|
+
if (!resolved.actor) throw new NoActiveOrgError("Accept your invitation to this organization to continue");
|
|
17
12
|
return {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
authOrgId
|
|
13
|
+
actor: resolved.actor,
|
|
14
|
+
orgId: resolved.orgId,
|
|
15
|
+
userId: resolved.userId,
|
|
16
|
+
orgUserId: resolved.orgUserId,
|
|
17
|
+
authOrgId,
|
|
18
|
+
authUserId
|
|
23
19
|
};
|
|
24
20
|
}
|
|
25
|
-
async function
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (!
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
container
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
21
|
+
async function resolveCaller(container, req) {
|
|
22
|
+
const authUserId = req.authUserId;
|
|
23
|
+
const authOrgId = req.authOrgId ?? null;
|
|
24
|
+
if (!authUserId) throw new NoActiveOrgError("Sign in to continue");
|
|
25
|
+
if (!authOrgId || !req.orgId) throw new NoActiveOrgError("Choose an organization to continue");
|
|
26
|
+
const resolved = req.resolvedCaller ?? await container.organizations.resolveCaller(authOrgId, authUserId);
|
|
27
|
+
req.resolvedCaller = resolved ?? void 0;
|
|
28
|
+
return callerFrom(container, resolved, authUserId, authOrgId);
|
|
29
|
+
}
|
|
30
|
+
async function resolveActor(container, req) {
|
|
31
|
+
return (await resolveCaller(container, req)).actor;
|
|
32
|
+
}
|
|
33
|
+
async function resolveCallerFor(container, authUserId, authOrgId, limit) {
|
|
34
|
+
return callerFrom(container, await container.organizations.resolveCaller(authOrgId, authUserId, limit), authUserId, authOrgId);
|
|
40
35
|
}
|
|
41
36
|
//#endregion
|
|
42
|
-
export { NoActiveOrgError,
|
|
37
|
+
export { NoActiveOrgError, resolveActor, resolveCaller, resolveCallerFor };
|
|
43
38
|
|
|
44
39
|
//# sourceMappingURL=scope.js.map
|
package/dist/http/scope.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope.js","names":[],"sources":["../../src/http/scope.ts"],"sourcesContent":["import type { FastifyRequest } from 'fastify';\nimport type { Container } from '../app/container.js';\nimport {
|
|
1
|
+
{"version":3,"file":"scope.js","names":[],"sources":["../../src/http/scope.ts"],"sourcesContent":["import type { FastifyRequest } from 'fastify';\nimport type { Container } from '../app/container.js';\nimport type { Actor } from '@headless-lms/core/shared/actor';\nimport type { CallerLimit, ResolvedCaller } from '@headless-lms/core/organizations';\n\nexport interface Caller {\n actor: Actor;\n orgId: string;\n userId: string;\n orgUserId: string;\n authOrgId: string;\n authUserId: string;\n}\n\nexport class NoActiveOrgError extends Error {}\n\nfunction callerFrom(\n container: Container,\n resolved: ResolvedCaller | null,\n authUserId: string,\n authOrgId: string,\n): Caller {\n if (!resolved) {\n throw new NoActiveOrgError('The active organization no longer exists');\n }\n if (!resolved.userId) {\n throw new NoActiveOrgError('Your account is not set up yet');\n }\n if (!resolved.orgUserId) {\n throw new NoActiveOrgError('You are not a member of this organization');\n }\n container.requestContext.set({ orgId: resolved.orgId, actorId: resolved.orgUserId });\n if (!resolved.actor) {\n throw new NoActiveOrgError('Accept your invitation to this organization to continue');\n }\n return {\n actor: resolved.actor,\n orgId: resolved.orgId,\n userId: resolved.userId,\n orgUserId: resolved.orgUserId,\n authOrgId,\n authUserId,\n };\n}\n\nexport async function resolveCaller(container: Container, req: FastifyRequest): Promise<Caller> {\n const authUserId = req.authUserId;\n const authOrgId = req.authOrgId ?? null;\n if (!authUserId) {\n throw new NoActiveOrgError('Sign in to continue');\n }\n if (!authOrgId || !req.orgId) {\n throw new NoActiveOrgError('Choose an organization to continue');\n }\n const resolved =\n req.resolvedCaller ?? (await container.organizations.resolveCaller(authOrgId, authUserId));\n req.resolvedCaller = resolved ?? undefined;\n return callerFrom(container, resolved, authUserId, authOrgId);\n}\n\nexport async function resolveActor(container: Container, req: FastifyRequest): Promise<Actor> {\n return (await resolveCaller(container, req)).actor;\n}\n\nexport async function resolveCallerFor(\n container: Container,\n authUserId: string,\n authOrgId: string,\n limit: CallerLimit,\n): Promise<Caller> {\n return callerFrom(\n container,\n await container.organizations.resolveCaller(authOrgId, authUserId, limit),\n authUserId,\n authOrgId,\n );\n}\n"],"mappings":";AAcA,IAAa,mBAAb,cAAsC,MAAM,CAAC;AAE7C,SAAS,WACP,WACA,UACA,YACA,WACQ;CACR,IAAI,CAAC,UACH,MAAM,IAAI,iBAAiB,0CAA0C;CAEvE,IAAI,CAAC,SAAS,QACZ,MAAM,IAAI,iBAAiB,gCAAgC;CAE7D,IAAI,CAAC,SAAS,WACZ,MAAM,IAAI,iBAAiB,2CAA2C;CAExE,UAAU,eAAe,IAAI;EAAE,OAAO,SAAS;EAAO,SAAS,SAAS;CAAU,CAAC;CACnF,IAAI,CAAC,SAAS,OACZ,MAAM,IAAI,iBAAiB,yDAAyD;CAEtF,OAAO;EACL,OAAO,SAAS;EAChB,OAAO,SAAS;EAChB,QAAQ,SAAS;EACjB,WAAW,SAAS;EACpB;EACA;CACF;AACF;AAEA,eAAsB,cAAc,WAAsB,KAAsC;CAC9F,MAAM,aAAa,IAAI;CACvB,MAAM,YAAY,IAAI,aAAa;CACnC,IAAI,CAAC,YACH,MAAM,IAAI,iBAAiB,qBAAqB;CAElD,IAAI,CAAC,aAAa,CAAC,IAAI,OACrB,MAAM,IAAI,iBAAiB,oCAAoC;CAEjE,MAAM,WACJ,IAAI,kBAAmB,MAAM,UAAU,cAAc,cAAc,WAAW,UAAU;CAC1F,IAAI,iBAAiB,YAAY,KAAA;CACjC,OAAO,WAAW,WAAW,UAAU,YAAY,SAAS;AAC9D;AAEA,eAAsB,aAAa,WAAsB,KAAqC;CAC5F,QAAQ,MAAM,cAAc,WAAW,GAAG,EAAA,CAAG;AAC/C;AAEA,eAAsB,iBACpB,WACA,YACA,WACA,OACiB;CACjB,OAAO,WACL,WACA,MAAM,UAAU,cAAc,cAAc,WAAW,YAAY,KAAK,GACxE,YACA,SACF;AACF"}
|
package/dist/http/server.js
CHANGED
|
@@ -3,11 +3,13 @@ import { registerOpenApi } from "./plugins/openapi.js";
|
|
|
3
3
|
import { registerAuth } from "./plugins/auth.js";
|
|
4
4
|
import { registerWellKnown } from "./well-known.js";
|
|
5
5
|
import { errorHandler, notFoundHandler } from "./plugins/error-handler.js";
|
|
6
|
+
import { trackEventStreams } from "./sse.js";
|
|
6
7
|
import { registerRoutes } from "./routes.js";
|
|
7
8
|
import { registerMcp } from "./mcp/route.js";
|
|
8
9
|
import Fastify from "fastify";
|
|
9
10
|
import { serializerCompiler, validatorCompiler } from "fastify-type-provider-zod";
|
|
10
11
|
//#region src/http/server.ts
|
|
12
|
+
const STREAMS_CLOSE_MS = 2e3;
|
|
11
13
|
async function buildServer(config, container) {
|
|
12
14
|
const app = Fastify({
|
|
13
15
|
loggerInstance: container.loggerInstance,
|
|
@@ -28,9 +30,16 @@ async function buildServer(config, container) {
|
|
|
28
30
|
if (container.mcp.oauthEnabled) registerWellKnown(app, container);
|
|
29
31
|
registerRoutes(app, container);
|
|
30
32
|
registerMcp(app, container);
|
|
33
|
+
const eventStreams = trackEventStreams(app);
|
|
34
|
+
app.addHook("preClose", async () => {
|
|
35
|
+
const deadline = Date.now() + STREAMS_CLOSE_MS;
|
|
36
|
+
await container.chatHub?.close();
|
|
37
|
+
await eventStreams.settled(Math.max(0, deadline - Date.now()));
|
|
38
|
+
});
|
|
31
39
|
app.addHook("onClose", async () => {
|
|
32
40
|
await container.outboxRelay.stop();
|
|
33
41
|
await container.automationEngine.stop();
|
|
42
|
+
await container.pubsub?.stop();
|
|
34
43
|
});
|
|
35
44
|
return app;
|
|
36
45
|
}
|
package/dist/http/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../../src/http/server.ts"],"sourcesContent":["import Fastify, { type FastifyBaseLogger, type FastifyInstance } from 'fastify';\nimport { serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';\nimport type { Container } from '../app/container.js';\nimport type { ServerConfig } from './config.js';\nimport { registerCors } from './plugins/cors.js';\nimport { registerOpenApi } from './plugins/openapi.js';\nimport { registerAuth } from './plugins/auth.js';\nimport { registerWellKnown } from './well-known.js';\nimport { errorHandler, notFoundHandler } from './plugins/error-handler.js';\nimport { registerRoutes } from './routes.js';\nimport { registerMcp } from './mcp/route.js';\n\nexport async function buildServer(\n config: ServerConfig,\n container: Container,\n): Promise<FastifyInstance> {\n // One log stream for the whole process: HTTP shares the container's pino root.\n // Widened to FastifyBaseLogger so the instance keeps the default logger\n // generic (a concrete pino type would ripple through every plugin signature).\n const app = Fastify({\n loggerInstance: container.loggerInstance as FastifyBaseLogger,\n // Fastify's own request/response pair per call — noisy in a dev console,\n // useful as an access log in production. Our own logs are unaffected.\n disableRequestLogging: config.requestLogging === false,\n // Adopt the caller's correlation id when it sends one, so one id joins\n // the client's log line to this process's.\n requestIdHeader: 'x-request-id',\n });\n\n // Enter the request-scoped log context: every line logged inside this\n // request's async scope carries its reqId (and orgId once resolveScope runs),\n // wherever it is emitted. The id is echoed on the response so callers can\n // quote it when reporting a failure.\n app.addHook('onRequest', (request, reply, done) => {\n reply.header('x-request-id', request.id);\n container.requestContext.run({ reqId: request.id }, done);\n });\n\n // Validate + serialize request/response bodies from the shared Zod contract.\n app.setValidatorCompiler(validatorCompiler);\n app.setSerializerCompiler(serializerCompiler);\n app.setErrorHandler(errorHandler);\n app.setNotFoundHandler(notFoundHandler);\n\n registerCors(app, config);\n registerOpenApi(app, config);\n registerAuth(app, container);\n if (container.mcp.oauthEnabled) {\n registerWellKnown(app, container);\n }\n\n registerRoutes(app, container);\n registerMcp(app, container);\n\n // Drain + stop the outbox relay
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../../src/http/server.ts"],"sourcesContent":["import Fastify, { type FastifyBaseLogger, type FastifyInstance } from 'fastify';\nimport { serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';\nimport type { Container } from '../app/container.js';\nimport type { ServerConfig } from './config.js';\nimport { registerCors } from './plugins/cors.js';\nimport { registerOpenApi } from './plugins/openapi.js';\nimport { registerAuth } from './plugins/auth.js';\nimport { registerWellKnown } from './well-known.js';\nimport { errorHandler, notFoundHandler } from './plugins/error-handler.js';\nimport { registerRoutes } from './routes.js';\nimport { registerMcp } from './mcp/route.js';\nimport { trackEventStreams } from './sse.js';\n\nconst STREAMS_CLOSE_MS = 2000;\n\nexport async function buildServer(\n config: ServerConfig,\n container: Container,\n): Promise<FastifyInstance> {\n // One log stream for the whole process: HTTP shares the container's pino root.\n // Widened to FastifyBaseLogger so the instance keeps the default logger\n // generic (a concrete pino type would ripple through every plugin signature).\n const app = Fastify({\n loggerInstance: container.loggerInstance as FastifyBaseLogger,\n // Fastify's own request/response pair per call — noisy in a dev console,\n // useful as an access log in production. Our own logs are unaffected.\n disableRequestLogging: config.requestLogging === false,\n // Adopt the caller's correlation id when it sends one, so one id joins\n // the client's log line to this process's.\n requestIdHeader: 'x-request-id',\n });\n\n // Enter the request-scoped log context: every line logged inside this\n // request's async scope carries its reqId (and orgId once resolveScope runs),\n // wherever it is emitted. The id is echoed on the response so callers can\n // quote it when reporting a failure.\n app.addHook('onRequest', (request, reply, done) => {\n reply.header('x-request-id', request.id);\n container.requestContext.run({ reqId: request.id }, done);\n });\n\n // Validate + serialize request/response bodies from the shared Zod contract.\n app.setValidatorCompiler(validatorCompiler);\n app.setSerializerCompiler(serializerCompiler);\n app.setErrorHandler(errorHandler);\n app.setNotFoundHandler(notFoundHandler);\n\n registerCors(app, config);\n registerOpenApi(app, config);\n registerAuth(app, container);\n if (container.mcp.oauthEnabled) {\n registerWellKnown(app, container);\n }\n\n registerRoutes(app, container);\n registerMcp(app, container);\n\n // Open chat streams, and stream requests still on their way to the hub, get\n // their closing frame before Fastify drops every connection.\n const eventStreams = trackEventStreams(app);\n app.addHook('preClose', async () => {\n const deadline = Date.now() + STREAMS_CLOSE_MS;\n await container.chatHub?.close();\n await eventStreams.settled(Math.max(0, deadline - Date.now()));\n });\n\n // Drain + stop the outbox relay, the automation engine and then PubSub on\n // shutdown, so events still in flight are forwarded. Harmless when none was\n // started (gen-openapi boots this app with ready() + close() only).\n app.addHook('onClose', async () => {\n await container.outboxRelay.stop();\n await container.automationEngine.stop();\n await container.pubsub?.stop();\n });\n\n return app;\n}\n"],"mappings":";;;;;;;;;;;AAaA,MAAM,mBAAmB;AAEzB,eAAsB,YACpB,QACA,WAC0B;CAI1B,MAAM,MAAM,QAAQ;EAClB,gBAAgB,UAAU;EAG1B,uBAAuB,OAAO,mBAAmB;EAGjD,iBAAiB;CACnB,CAAC;CAMD,IAAI,QAAQ,cAAc,SAAS,OAAO,SAAS;EACjD,MAAM,OAAO,gBAAgB,QAAQ,EAAE;EACvC,UAAU,eAAe,IAAI,EAAE,OAAO,QAAQ,GAAG,GAAG,IAAI;CAC1D,CAAC;CAGD,IAAI,qBAAqB,iBAAiB;CAC1C,IAAI,sBAAsB,kBAAkB;CAC5C,IAAI,gBAAgB,YAAY;CAChC,IAAI,mBAAmB,eAAe;CAEtC,aAAa,KAAK,MAAM;CACxB,gBAAgB,KAAK,MAAM;CAC3B,aAAa,KAAK,SAAS;CAC3B,IAAI,UAAU,IAAI,cAChB,kBAAkB,KAAK,SAAS;CAGlC,eAAe,KAAK,SAAS;CAC7B,YAAY,KAAK,SAAS;CAI1B,MAAM,eAAe,kBAAkB,GAAG;CAC1C,IAAI,QAAQ,YAAY,YAAY;EAClC,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,MAAM,UAAU,SAAS,MAAM;EAC/B,MAAM,aAAa,QAAQ,KAAK,IAAI,GAAG,WAAW,KAAK,IAAI,CAAC,CAAC;CAC/D,CAAC;CAKD,IAAI,QAAQ,WAAW,YAAY;EACjC,MAAM,UAAU,YAAY,KAAK;EACjC,MAAM,UAAU,iBAAiB,KAAK;EACtC,MAAM,UAAU,QAAQ,KAAK;CAC/B,CAAC;CAED,OAAO;AACT"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FastifyInstance } from "fastify";
|
|
2
|
+
//#region src/http/shutdown.d.ts
|
|
3
|
+
interface CloseOnSignalsOptions {
|
|
4
|
+
signals?: NodeJS.Signals[];
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
process?: Pick<NodeJS.Process, 'on' | 'exit'>;
|
|
7
|
+
}
|
|
8
|
+
declare function closeOnSignals(app: FastifyInstance, options?: CloseOnSignalsOptions): void;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { CloseOnSignalsOptions, closeOnSignals };
|
|
11
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/http/shutdown.ts
|
|
2
|
+
function closeOnSignals(app, options = {}) {
|
|
3
|
+
const proc = options.process ?? process;
|
|
4
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
5
|
+
let closing = false;
|
|
6
|
+
const onSignal = (signal) => {
|
|
7
|
+
if (closing) {
|
|
8
|
+
app.log.warn({ signal }, "second signal; exiting without finishing shutdown");
|
|
9
|
+
proc.exit(1);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
closing = true;
|
|
13
|
+
app.log.info({ signal }, "shutting down");
|
|
14
|
+
const timer = setTimeout(() => {
|
|
15
|
+
app.log.error({
|
|
16
|
+
signal,
|
|
17
|
+
timeoutMs
|
|
18
|
+
}, "shutdown timed out; exiting");
|
|
19
|
+
proc.exit(1);
|
|
20
|
+
}, timeoutMs);
|
|
21
|
+
timer.unref();
|
|
22
|
+
app.close().then(() => {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
app.log.info({ signal }, "shut down");
|
|
25
|
+
proc.exit(0);
|
|
26
|
+
}, (err) => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
app.log.error({
|
|
29
|
+
err,
|
|
30
|
+
signal
|
|
31
|
+
}, "shutdown failed");
|
|
32
|
+
proc.exit(1);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
for (const signal of options.signals ?? ["SIGTERM", "SIGINT"]) proc.on(signal, onSignal);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { closeOnSignals };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=shutdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.js","names":[],"sources":["../../src/http/shutdown.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\n\nexport interface CloseOnSignalsOptions {\n signals?: NodeJS.Signals[];\n timeoutMs?: number;\n process?: Pick<NodeJS.Process, 'on' | 'exit'>;\n}\n\nexport function closeOnSignals(app: FastifyInstance, options: CloseOnSignalsOptions = {}): void {\n const proc = options.process ?? process;\n const timeoutMs = options.timeoutMs ?? 10_000;\n let closing = false;\n const onSignal = (signal: NodeJS.Signals) => {\n if (closing) {\n app.log.warn({ signal }, 'second signal; exiting without finishing shutdown');\n proc.exit(1);\n return;\n }\n closing = true;\n app.log.info({ signal }, 'shutting down');\n const timer = setTimeout(() => {\n app.log.error({ signal, timeoutMs }, 'shutdown timed out; exiting');\n proc.exit(1);\n }, timeoutMs);\n timer.unref();\n app.close().then(\n () => {\n clearTimeout(timer);\n app.log.info({ signal }, 'shut down');\n proc.exit(0);\n },\n (err: unknown) => {\n clearTimeout(timer);\n app.log.error({ err, signal }, 'shutdown failed');\n proc.exit(1);\n },\n );\n };\n for (const signal of options.signals ?? ['SIGTERM', 'SIGINT']) {\n proc.on(signal, onSignal);\n }\n}\n"],"mappings":";AAQA,SAAgB,eAAe,KAAsB,UAAiC,CAAC,GAAS;CAC9F,MAAM,OAAO,QAAQ,WAAW;CAChC,MAAM,YAAY,QAAQ,aAAa;CACvC,IAAI,UAAU;CACd,MAAM,YAAY,WAA2B;EAC3C,IAAI,SAAS;GACX,IAAI,IAAI,KAAK,EAAE,OAAO,GAAG,mDAAmD;GAC5E,KAAK,KAAK,CAAC;GACX;EACF;EACA,UAAU;EACV,IAAI,IAAI,KAAK,EAAE,OAAO,GAAG,eAAe;EACxC,MAAM,QAAQ,iBAAiB;GAC7B,IAAI,IAAI,MAAM;IAAE;IAAQ;GAAU,GAAG,6BAA6B;GAClE,KAAK,KAAK,CAAC;EACb,GAAG,SAAS;EACZ,MAAM,MAAM;EACZ,IAAI,MAAM,CAAC,CAAC,WACJ;GACJ,aAAa,KAAK;GAClB,IAAI,IAAI,KAAK,EAAE,OAAO,GAAG,WAAW;GACpC,KAAK,KAAK,CAAC;EACb,IACC,QAAiB;GAChB,aAAa,KAAK;GAClB,IAAI,IAAI,MAAM;IAAE;IAAK;GAAO,GAAG,iBAAiB;GAChD,KAAK,KAAK,CAAC;EACb,CACF;CACF;CACA,KAAK,MAAM,UAAU,QAAQ,WAAW,CAAC,WAAW,QAAQ,GAC1D,KAAK,GAAG,QAAQ,QAAQ;AAE5B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyReply } from "fastify";
|
|
2
|
+
//#region src/http/sse.d.ts
|
|
3
|
+
interface EventStreams {
|
|
4
|
+
/** Resolves once every event stream request in flight has finished, or after `timeoutMs`. */
|
|
5
|
+
settled(timeoutMs: number): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
/** Tracks the requests to routes marked `config.eventStream`, from routing to
|
|
8
|
+
* the end of their response. */
|
|
9
|
+
declare function trackEventStreams(app: FastifyInstance): EventStreams;
|
|
10
|
+
interface SseOptions {
|
|
11
|
+
pingMs?: number;
|
|
12
|
+
drainTimeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
declare function sse<T extends {
|
|
15
|
+
type: string;
|
|
16
|
+
}>(reply: FastifyReply, events: AsyncIterable<T>, options?: SseOptions): Promise<void>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { EventStreams, SseOptions, sse, trackEventStreams };
|
|
19
|
+
//# sourceMappingURL=sse.d.ts.map
|
package/dist/http/sse.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
//#region src/http/sse.ts
|
|
2
|
+
const PING_MS = 25e3;
|
|
3
|
+
const DRAIN_TIMEOUT_MS = 3e4;
|
|
4
|
+
/** Tracks the requests to routes marked `config.eventStream`, from routing to
|
|
5
|
+
* the end of their response. */
|
|
6
|
+
function trackEventStreams(app) {
|
|
7
|
+
const open = /* @__PURE__ */ new Set();
|
|
8
|
+
app.addHook("onRequest", async (req, reply) => {
|
|
9
|
+
if (!req.routeOptions.config.eventStream) return;
|
|
10
|
+
const closed = new Promise((resolve) => reply.raw.once("close", () => resolve()));
|
|
11
|
+
open.add(closed);
|
|
12
|
+
closed.then(() => open.delete(closed));
|
|
13
|
+
});
|
|
14
|
+
return { async settled(timeoutMs) {
|
|
15
|
+
let timer;
|
|
16
|
+
await Promise.race([Promise.all(open), new Promise((resolve) => {
|
|
17
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
18
|
+
})]);
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
} };
|
|
21
|
+
}
|
|
22
|
+
async function sse(reply, events, options = {}) {
|
|
23
|
+
const pingMs = options.pingMs ?? PING_MS;
|
|
24
|
+
const drainTimeoutMs = options.drainTimeoutMs ?? DRAIN_TIMEOUT_MS;
|
|
25
|
+
const res = reply.raw;
|
|
26
|
+
const iterator = events[Symbol.asyncIterator]();
|
|
27
|
+
reply.hijack();
|
|
28
|
+
const set = Object.entries(reply.getHeaders()).filter((entry) => entry[1] !== void 0);
|
|
29
|
+
res.writeHead(200, {
|
|
30
|
+
...Object.fromEntries(set),
|
|
31
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
32
|
+
"cache-control": "no-cache, no-transform",
|
|
33
|
+
"x-accel-buffering": "no"
|
|
34
|
+
});
|
|
35
|
+
res.flushHeaders();
|
|
36
|
+
let closed = false;
|
|
37
|
+
let draining = false;
|
|
38
|
+
let ended = "client";
|
|
39
|
+
let sent = 0;
|
|
40
|
+
const close = () => {
|
|
41
|
+
if (closed) return;
|
|
42
|
+
closed = true;
|
|
43
|
+
clearInterval(ping);
|
|
44
|
+
iterator.return?.();
|
|
45
|
+
};
|
|
46
|
+
res.on("close", close);
|
|
47
|
+
const write = async (chunk) => {
|
|
48
|
+
if (closed) return false;
|
|
49
|
+
if (res.write(chunk)) return true;
|
|
50
|
+
draining = true;
|
|
51
|
+
const drained = await new Promise((resolve) => {
|
|
52
|
+
const done = (ok) => {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
res.off("drain", onDrain);
|
|
55
|
+
res.off("close", onClose);
|
|
56
|
+
resolve(ok);
|
|
57
|
+
};
|
|
58
|
+
const onDrain = () => done(true);
|
|
59
|
+
const onClose = () => done(false);
|
|
60
|
+
const timer = setTimeout(() => done(false), drainTimeoutMs);
|
|
61
|
+
res.on("drain", onDrain);
|
|
62
|
+
res.on("close", onClose);
|
|
63
|
+
});
|
|
64
|
+
draining = false;
|
|
65
|
+
if (!drained && !closed) {
|
|
66
|
+
ended = "undrained";
|
|
67
|
+
reply.log.warn("event stream not drained; closing it");
|
|
68
|
+
close();
|
|
69
|
+
res.destroy();
|
|
70
|
+
}
|
|
71
|
+
return drained;
|
|
72
|
+
};
|
|
73
|
+
const ping = setInterval(() => {
|
|
74
|
+
if (!draining) write(": ping\n\n");
|
|
75
|
+
}, pingMs);
|
|
76
|
+
ping.unref();
|
|
77
|
+
try {
|
|
78
|
+
while (!closed) {
|
|
79
|
+
const next = await iterator.next();
|
|
80
|
+
if (next.done) {
|
|
81
|
+
if (!closed) ended = "server";
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
const event = next.value;
|
|
85
|
+
if (!await write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`)) break;
|
|
86
|
+
sent += 1;
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
ended = "error";
|
|
90
|
+
reply.log.error({ err: error }, "event stream failed");
|
|
91
|
+
} finally {
|
|
92
|
+
close();
|
|
93
|
+
if (!res.destroyed && !res.writableEnded) res.end();
|
|
94
|
+
reply.log.info({
|
|
95
|
+
responseTime: reply.elapsedTime,
|
|
96
|
+
ended,
|
|
97
|
+
events: sent
|
|
98
|
+
}, "event stream ended");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { sse, trackEventStreams };
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","names":[],"sources":["../../src/http/sse.ts"],"sourcesContent":["import type { FastifyInstance, FastifyReply } from 'fastify';\n\nconst PING_MS = 25_000;\nconst DRAIN_TIMEOUT_MS = 30_000;\n\nexport interface EventStreams {\n /** Resolves once every event stream request in flight has finished, or after `timeoutMs`. */\n settled(timeoutMs: number): Promise<void>;\n}\n\n/** Tracks the requests to routes marked `config.eventStream`, from routing to\n * the end of their response. */\nexport function trackEventStreams(app: FastifyInstance): EventStreams {\n const open = new Set<Promise<void>>();\n app.addHook('onRequest', async (req, reply) => {\n if (!req.routeOptions.config.eventStream) {\n return;\n }\n const closed = new Promise<void>((resolve) => reply.raw.once('close', () => resolve()));\n open.add(closed);\n void closed.then(() => open.delete(closed));\n });\n return {\n async settled(timeoutMs) {\n let timer: NodeJS.Timeout | undefined;\n await Promise.race([\n Promise.all(open),\n new Promise((resolve) => {\n timer = setTimeout(resolve, timeoutMs);\n }),\n ]);\n clearTimeout(timer);\n },\n };\n}\n\nexport interface SseOptions {\n pingMs?: number;\n drainTimeoutMs?: number;\n}\n\nexport async function sse<T extends { type: string }>(\n reply: FastifyReply,\n events: AsyncIterable<T>,\n options: SseOptions = {},\n): Promise<void> {\n const pingMs = options.pingMs ?? PING_MS;\n const drainTimeoutMs = options.drainTimeoutMs ?? DRAIN_TIMEOUT_MS;\n const res = reply.raw;\n const iterator = events[Symbol.asyncIterator]();\n\n reply.hijack();\n const set = Object.entries(reply.getHeaders()).filter(\n (entry): entry is [string, string | number | string[]] => entry[1] !== undefined,\n );\n res.writeHead(200, {\n ...Object.fromEntries(set),\n 'content-type': 'text/event-stream; charset=utf-8',\n 'cache-control': 'no-cache, no-transform',\n 'x-accel-buffering': 'no',\n });\n res.flushHeaders();\n\n let closed = false;\n let draining = false;\n let ended: 'client' | 'server' | 'undrained' | 'error' = 'client';\n let sent = 0;\n const close = () => {\n if (closed) {\n return;\n }\n closed = true;\n clearInterval(ping);\n void iterator.return?.();\n };\n res.on('close', close);\n\n const write = async (chunk: string): Promise<boolean> => {\n if (closed) {\n return false;\n }\n if (res.write(chunk)) {\n return true;\n }\n draining = true;\n const drained = await new Promise<boolean>((resolve) => {\n const done = (ok: boolean) => {\n clearTimeout(timer);\n res.off('drain', onDrain);\n res.off('close', onClose);\n resolve(ok);\n };\n const onDrain = () => done(true);\n const onClose = () => done(false);\n const timer = setTimeout(() => done(false), drainTimeoutMs);\n res.on('drain', onDrain);\n res.on('close', onClose);\n });\n draining = false;\n if (!drained && !closed) {\n ended = 'undrained';\n reply.log.warn('event stream not drained; closing it');\n close();\n res.destroy();\n }\n return drained;\n };\n\n const ping = setInterval(() => {\n if (!draining) {\n void write(': ping\\n\\n');\n }\n }, pingMs);\n ping.unref();\n\n try {\n while (!closed) {\n const next = await iterator.next();\n if (next.done) {\n if (!closed) {\n ended = 'server';\n }\n break;\n }\n const event = next.value;\n // The generated SDK client yields only `data`, so it carries `type` too.\n if (!(await write(`event: ${event.type}\\ndata: ${JSON.stringify(event)}\\n\\n`))) {\n break;\n }\n sent += 1;\n }\n } catch (error) {\n ended = 'error';\n reply.log.error({ err: error }, 'event stream failed');\n } finally {\n close();\n if (!res.destroyed && !res.writableEnded) {\n res.end();\n }\n reply.log.info({ responseTime: reply.elapsedTime, ended, events: sent }, 'event stream ended');\n }\n}\n"],"mappings":";AAEA,MAAM,UAAU;AAChB,MAAM,mBAAmB;;;AASzB,SAAgB,kBAAkB,KAAoC;CACpE,MAAM,uBAAO,IAAI,IAAmB;CACpC,IAAI,QAAQ,aAAa,OAAO,KAAK,UAAU;EAC7C,IAAI,CAAC,IAAI,aAAa,OAAO,aAC3B;EAEF,MAAM,SAAS,IAAI,SAAe,YAAY,MAAM,IAAI,KAAK,eAAe,QAAQ,CAAC,CAAC;EACtF,KAAK,IAAI,MAAM;EACf,OAAY,WAAW,KAAK,OAAO,MAAM,CAAC;CAC5C,CAAC;CACD,OAAO,EACL,MAAM,QAAQ,WAAW;EACvB,IAAI;EACJ,MAAM,QAAQ,KAAK,CACjB,QAAQ,IAAI,IAAI,GAChB,IAAI,SAAS,YAAY;GACvB,QAAQ,WAAW,SAAS,SAAS;EACvC,CAAC,CACH,CAAC;EACD,aAAa,KAAK;CACpB,EACF;AACF;AAOA,eAAsB,IACpB,OACA,QACA,UAAsB,CAAC,GACR;CACf,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,MAAM,MAAM;CAClB,MAAM,WAAW,OAAO,OAAO,cAAc,CAAC;CAE9C,MAAM,OAAO;CACb,MAAM,MAAM,OAAO,QAAQ,MAAM,WAAW,CAAC,CAAC,CAAC,QAC5C,UAAyD,MAAM,OAAO,KAAA,CACzE;CACA,IAAI,UAAU,KAAK;EACjB,GAAG,OAAO,YAAY,GAAG;EACzB,gBAAgB;EAChB,iBAAiB;EACjB,qBAAqB;CACvB,CAAC;CACD,IAAI,aAAa;CAEjB,IAAI,SAAS;CACb,IAAI,WAAW;CACf,IAAI,QAAqD;CACzD,IAAI,OAAO;CACX,MAAM,cAAc;EAClB,IAAI,QACF;EAEF,SAAS;EACT,cAAc,IAAI;EAClB,SAAc,SAAS;CACzB;CACA,IAAI,GAAG,SAAS,KAAK;CAErB,MAAM,QAAQ,OAAO,UAAoC;EACvD,IAAI,QACF,OAAO;EAET,IAAI,IAAI,MAAM,KAAK,GACjB,OAAO;EAET,WAAW;EACX,MAAM,UAAU,MAAM,IAAI,SAAkB,YAAY;GACtD,MAAM,QAAQ,OAAgB;IAC5B,aAAa,KAAK;IAClB,IAAI,IAAI,SAAS,OAAO;IACxB,IAAI,IAAI,SAAS,OAAO;IACxB,QAAQ,EAAE;GACZ;GACA,MAAM,gBAAgB,KAAK,IAAI;GAC/B,MAAM,gBAAgB,KAAK,KAAK;GAChC,MAAM,QAAQ,iBAAiB,KAAK,KAAK,GAAG,cAAc;GAC1D,IAAI,GAAG,SAAS,OAAO;GACvB,IAAI,GAAG,SAAS,OAAO;EACzB,CAAC;EACD,WAAW;EACX,IAAI,CAAC,WAAW,CAAC,QAAQ;GACvB,QAAQ;GACR,MAAM,IAAI,KAAK,sCAAsC;GACrD,MAAM;GACN,IAAI,QAAQ;EACd;EACA,OAAO;CACT;CAEA,MAAM,OAAO,kBAAkB;EAC7B,IAAI,CAAC,UACH,MAAW,YAAY;CAE3B,GAAG,MAAM;CACT,KAAK,MAAM;CAEX,IAAI;EACF,OAAO,CAAC,QAAQ;GACd,MAAM,OAAO,MAAM,SAAS,KAAK;GACjC,IAAI,KAAK,MAAM;IACb,IAAI,CAAC,QACH,QAAQ;IAEV;GACF;GACA,MAAM,QAAQ,KAAK;GAEnB,IAAI,CAAE,MAAM,MAAM,UAAU,MAAM,KAAK,UAAU,KAAK,UAAU,KAAK,EAAE,KAAK,GAC1E;GAEF,QAAQ;EACV;CACF,SAAS,OAAO;EACd,QAAQ;EACR,MAAM,IAAI,MAAM,EAAE,KAAK,MAAM,GAAG,qBAAqB;CACvD,UAAU;EACR,MAAM;EACN,IAAI,CAAC,IAAI,aAAa,CAAC,IAAI,eACzB,IAAI,IAAI;EAEV,MAAM,IAAI,KAAK;GAAE,cAAc,MAAM;GAAa;GAAO,QAAQ;EAAK,GAAG,oBAAoB;CAC/F;AACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ResolvedCaller } from "@headless-lms/core/organizations";
|
|
2
|
+
import { Actor } from "@headless-lms/core/shared/actor";
|
|
3
|
+
//#region src/http/test-caller.d.ts
|
|
4
|
+
declare function resolvedCaller<O extends {
|
|
5
|
+
id: string;
|
|
6
|
+
}, U extends {
|
|
7
|
+
id: string;
|
|
8
|
+
}, M extends {
|
|
9
|
+
id: string;
|
|
10
|
+
}>(org: O | null, user: U | null, orgUser: M | null, actor: Actor | null): ResolvedCaller | null;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { resolvedCaller };
|
|
13
|
+
//# sourceMappingURL=test-caller.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/http/test-caller.ts
|
|
2
|
+
function resolvedCaller(org, user, orgUser, actor) {
|
|
3
|
+
if (!org) return null;
|
|
4
|
+
return {
|
|
5
|
+
orgId: org.id,
|
|
6
|
+
userId: user?.id ?? null,
|
|
7
|
+
orgUserId: user && orgUser ? orgUser.id : null,
|
|
8
|
+
actor: user && orgUser ? actor : null
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { resolvedCaller };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=test-caller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-caller.js","names":[],"sources":["../../src/http/test-caller.ts"],"sourcesContent":["import type { Actor } from '@headless-lms/core/shared/actor';\nimport type { ResolvedCaller } from '@headless-lms/core/organizations';\n\nexport function resolvedCaller<\n O extends { id: string },\n U extends { id: string },\n M extends { id: string },\n>(org: O | null, user: U | null, orgUser: M | null, actor: Actor | null): ResolvedCaller | null {\n if (!org) {\n return null;\n }\n return {\n orgId: org.id,\n userId: user?.id ?? null,\n orgUserId: user && orgUser ? orgUser.id : null,\n actor: user && orgUser ? actor : null,\n };\n}\n"],"mappings":";AAGA,SAAgB,eAId,KAAe,MAAgB,SAAmB,OAA4C;CAC9F,IAAI,CAAC,KACH,OAAO;CAET,OAAO;EACL,OAAO,IAAI;EACX,QAAQ,MAAM,MAAM;EACpB,WAAW,QAAQ,UAAU,QAAQ,KAAK;EAC1C,OAAO,QAAQ,UAAU,QAAQ;CACnC;AACF"}
|
package/dist/http/well-known.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { bridgeWebResponse, toWebRequest } from "./web-bridge.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ALL_PERMISSIONS } from "@headless-lms/core/organizations";
|
|
3
3
|
//#region src/http/well-known.ts
|
|
4
4
|
function registerWellKnown(app, container) {
|
|
5
5
|
const origin = new URL(container.mcp.issuer).origin;
|
|
6
6
|
const resourceMetadata = () => ({
|
|
7
7
|
resource: container.mcp.resource,
|
|
8
8
|
authorization_servers: [container.mcp.issuer],
|
|
9
|
-
scopes_supported: [...
|
|
9
|
+
scopes_supported: [...ALL_PERMISSIONS],
|
|
10
10
|
bearer_methods_supported: ["header"]
|
|
11
11
|
});
|
|
12
12
|
const hidden = { schema: { hide: true } };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"well-known.js","names":[],"sources":["../../src/http/well-known.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport {
|
|
1
|
+
{"version":3,"file":"well-known.js","names":[],"sources":["../../src/http/well-known.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport { ALL_PERMISSIONS } from '@headless-lms/core/organizations';\nimport type { Container } from '../app/container.js';\nimport { bridgeWebResponse, toWebRequest } from './web-bridge.js';\n\nexport function registerWellKnown(app: FastifyInstance, container: Container): void {\n const origin = new URL(container.mcp.issuer).origin;\n const resourceMetadata = () => ({\n resource: container.mcp.resource,\n authorization_servers: [container.mcp.issuer],\n scopes_supported: [...ALL_PERMISSIONS],\n bearer_methods_supported: ['header'],\n });\n const hidden = { schema: { hide: true } };\n\n app.get('/.well-known/oauth-protected-resource/mcp', hidden, async () => resourceMetadata());\n app.get('/.well-known/oauth-protected-resource', hidden, async () => resourceMetadata());\n app.get('/.well-known/oauth-authorization-server/api/auth', hidden, async (request, reply) => {\n await bridgeWebResponse(\n await container.auth.authorizationServerMetadata(toWebRequest(request, origin)),\n reply,\n );\n });\n}\n"],"mappings":";;;AAKA,SAAgB,kBAAkB,KAAsB,WAA4B;CAClF,MAAM,SAAS,IAAI,IAAI,UAAU,IAAI,MAAM,CAAC,CAAC;CAC7C,MAAM,0BAA0B;EAC9B,UAAU,UAAU,IAAI;EACxB,uBAAuB,CAAC,UAAU,IAAI,MAAM;EAC5C,kBAAkB,CAAC,GAAG,eAAe;EACrC,0BAA0B,CAAC,QAAQ;CACrC;CACA,MAAM,SAAS,EAAE,QAAQ,EAAE,MAAM,KAAK,EAAE;CAExC,IAAI,IAAI,6CAA6C,QAAQ,YAAY,iBAAiB,CAAC;CAC3F,IAAI,IAAI,yCAAyC,QAAQ,YAAY,iBAAiB,CAAC;CACvF,IAAI,IAAI,oDAAoD,QAAQ,OAAO,SAAS,UAAU;EAC5F,MAAM,kBACJ,MAAM,UAAU,KAAK,4BAA4B,aAAa,SAAS,MAAM,CAAC,GAC9E,KACF;CACF,CAAC;AACH"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { AuthUser } from "./http/fastify.js";
|
|
2
|
+
import { PlatformConfig } from "./app/platform.js";
|
|
2
3
|
import { AdapterOverrides, BuildContainerOptions, Config, Container, LoggingConfig } from "./app/container.js";
|
|
3
4
|
import { loadIntegrations } from "./app/integrations.js";
|
|
4
5
|
import { ServerConfig } from "./http/config.js";
|
|
5
|
-
import { CatalogueField,
|
|
6
|
+
import { CatalogueField, CataloguePermission, CatalogueTool, McpCatalogue, mcpCatalogue } from "./http/mcp/catalogue.js";
|
|
6
7
|
import { buildServer } from "./http/server.js";
|
|
8
|
+
import { CloseOnSignalsOptions, closeOnSignals } from "./http/shutdown.js";
|
|
7
9
|
import { runMigrations } from "@headless-lms/adapter-db";
|
|
8
10
|
import { InlineAutomationEngine } from "@headless-lms/adapter-defaults/workflows";
|
|
9
11
|
import { AutomationEngine, WebhookSender } from "@headless-lms/core/automations";
|
|
10
12
|
import { Mailer } from "@headless-lms/core/shared/mailer";
|
|
13
|
+
import { PushConfig } from "@headless-lms/core/types";
|
|
11
14
|
import { EmailMessage, EmailSender, ObjectStorage } from "@headless-lms/core/shared/ports";
|
|
12
15
|
//#region src/index.d.ts
|
|
13
16
|
declare function createContainer(config: ServerConfig, options?: BuildContainerOptions): Promise<Container>;
|
|
14
17
|
//#endregion
|
|
15
|
-
export { type AdapterOverrides, type AuthUser, type AutomationEngine, type BuildContainerOptions, type CatalogueField, type
|
|
18
|
+
export { type AdapterOverrides, type AuthUser, type AutomationEngine, type BuildContainerOptions, type CatalogueField, type CataloguePermission, type CatalogueTool, type CloseOnSignalsOptions, type Container, type Config as ContainerConfig, type EmailMessage, type EmailSender, InlineAutomationEngine, type LoggingConfig, type Mailer, type McpCatalogue, type ObjectStorage, type PlatformConfig, type PushConfig, type ServerConfig, type WebhookSender, buildServer, closeOnSignals, createContainer, loadIntegrations, mcpCatalogue, runMigrations };
|
|
16
19
|
//# sourceMappingURL=index.d.ts.map
|