@mastra/factory 0.2.2-alpha.0 → 0.2.2-alpha.2

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.
Files changed (174) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/auth.d.ts +2 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +401 -314
  5. package/dist/auth.js.map +1 -1
  6. package/dist/capabilities/connection.js +1 -1
  7. package/dist/capabilities/intake.js +1 -1
  8. package/dist/capabilities/version-control.js +1 -1
  9. package/dist/factory.d.ts.map +1 -1
  10. package/dist/factory.js +441 -39455
  11. package/dist/factory.js.map +1 -1
  12. package/dist/index.js +2 -39466
  13. package/dist/integrations/base.js +1 -1
  14. package/dist/integrations/github/config.js +36 -22
  15. package/dist/integrations/github/config.js.map +1 -1
  16. package/dist/integrations/github/factory-session.js +33 -37
  17. package/dist/integrations/github/factory-session.js.map +1 -1
  18. package/dist/integrations/github/integration.js +1037 -3624
  19. package/dist/integrations/github/integration.js.map +1 -1
  20. package/dist/integrations/github/issue-triage.js +71 -71
  21. package/dist/integrations/github/issue-triage.js.map +1 -1
  22. package/dist/integrations/github/pat.js +47 -36
  23. package/dist/integrations/github/pat.js.map +1 -1
  24. package/dist/integrations/github/provenance.js +72 -101
  25. package/dist/integrations/github/provenance.js.map +1 -1
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +1272 -1833
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/rules.js +203 -471
  30. package/dist/integrations/github/rules.js.map +1 -1
  31. package/dist/integrations/github/sandbox.d.ts +2 -0
  32. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  33. package/dist/integrations/github/sandbox.js +401 -326
  34. package/dist/integrations/github/sandbox.js.map +1 -1
  35. package/dist/integrations/github/session-subscriptions.js +165 -223
  36. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  37. package/dist/integrations/github/subscriptions.js +43 -51
  38. package/dist/integrations/github/subscriptions.js.map +1 -1
  39. package/dist/integrations/github/token-refresh.js +15 -18
  40. package/dist/integrations/github/token-refresh.js.map +1 -1
  41. package/dist/integrations/github/webhook.js +359 -343
  42. package/dist/integrations/github/webhook.js.map +1 -1
  43. package/dist/integrations/linear/agent-tools.js +77 -100
  44. package/dist/integrations/linear/agent-tools.js.map +1 -1
  45. package/dist/integrations/linear/integration.js +611 -1309
  46. package/dist/integrations/linear/integration.js.map +1 -1
  47. package/dist/integrations/linear/routes.js +227 -230
  48. package/dist/integrations/linear/routes.js.map +1 -1
  49. package/dist/integrations/linear/rules.js +126 -346
  50. package/dist/integrations/linear/rules.js.map +1 -1
  51. package/dist/integrations/linear/storage.js +1 -1
  52. package/dist/integrations/platform/api-client.js +131 -135
  53. package/dist/integrations/platform/api-client.js.map +1 -1
  54. package/dist/integrations/platform/github/event-worker.js +275 -573
  55. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  56. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  57. package/dist/integrations/platform/github/integration.js +845 -3942
  58. package/dist/integrations/platform/github/integration.js.map +1 -1
  59. package/dist/integrations/platform/linear/integration.js +457 -1309
  60. package/dist/integrations/platform/linear/integration.js.map +1 -1
  61. package/dist/integrations/workos/integration.js +80 -85
  62. package/dist/integrations/workos/integration.js.map +1 -1
  63. package/dist/routes/config.d.ts.map +1 -1
  64. package/dist/routes/config.js +933 -915
  65. package/dist/routes/config.js.map +1 -1
  66. package/dist/routes/custom-provider-source.js +110 -93
  67. package/dist/routes/custom-provider-source.js.map +1 -1
  68. package/dist/routes/fs.js +401 -349
  69. package/dist/routes/fs.js.map +1 -1
  70. package/dist/routes/intake.js +165 -165
  71. package/dist/routes/intake.js.map +1 -1
  72. package/dist/routes/oauth.js +447 -25166
  73. package/dist/routes/oauth.js.map +1 -1
  74. package/dist/routes/projects.js +437 -376
  75. package/dist/routes/projects.js.map +1 -1
  76. package/dist/routes/provider-credentials.js +76 -58
  77. package/dist/routes/provider-credentials.js.map +1 -1
  78. package/dist/routes/route.js +12 -8
  79. package/dist/routes/route.js.map +1 -1
  80. package/dist/routes/skills.js +160 -173
  81. package/dist/routes/skills.js.map +1 -1
  82. package/dist/routes/surface.js +250 -28618
  83. package/dist/routes/surface.js.map +1 -1
  84. package/dist/routes/tenant-credentials.js +141 -166
  85. package/dist/routes/tenant-credentials.js.map +1 -1
  86. package/dist/routes/work-items.js +654 -862
  87. package/dist/routes/work-items.js.map +1 -1
  88. package/dist/rules/binding-context.js +23 -35
  89. package/dist/rules/binding-context.js.map +1 -1
  90. package/dist/rules/defaults.js +200 -309
  91. package/dist/rules/defaults.js.map +1 -1
  92. package/dist/rules/dispatcher.d.ts.map +1 -1
  93. package/dist/rules/dispatcher.js +438 -677
  94. package/dist/rules/dispatcher.js.map +1 -1
  95. package/dist/rules/index.js +5 -599
  96. package/dist/rules/processor.js +344 -596
  97. package/dist/rules/processor.js.map +1 -1
  98. package/dist/rules/resolve.js +21 -18
  99. package/dist/rules/resolve.js.map +1 -1
  100. package/dist/rules/start-coordinator.js +168 -157
  101. package/dist/rules/start-coordinator.js.map +1 -1
  102. package/dist/rules/tools.js +50 -85
  103. package/dist/rules/tools.js.map +1 -1
  104. package/dist/rules/transition-service.js +183 -454
  105. package/dist/rules/transition-service.js.map +1 -1
  106. package/dist/rules/types.js +33 -29
  107. package/dist/rules/types.js.map +1 -1
  108. package/dist/rules/validation.js +266 -281
  109. package/dist/rules/validation.js.map +1 -1
  110. package/dist/sandbox/fleet.js +272 -250
  111. package/dist/sandbox/fleet.js.map +1 -1
  112. package/dist/sandbox/reattach.js +13 -6
  113. package/dist/sandbox/reattach.js.map +1 -1
  114. package/dist/server-error.js +12 -16
  115. package/dist/server-error.js.map +1 -1
  116. package/dist/skills/service.js +30 -35
  117. package/dist/skills/service.js.map +1 -1
  118. package/dist/spa-static.d.ts.map +1 -1
  119. package/dist/spa-static.js +91 -64
  120. package/dist/spa-static.js.map +1 -1
  121. package/dist/state-signing.js +73 -44
  122. package/dist/state-signing.js.map +1 -1
  123. package/dist/storage/domains/audit/agent-audit.js +60 -59
  124. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  125. package/dist/storage/domains/audit/base.js +169 -112
  126. package/dist/storage/domains/audit/base.js.map +1 -1
  127. package/dist/storage/domains/audit/domain.js +155 -153
  128. package/dist/storage/domains/audit/domain.js.map +1 -1
  129. package/dist/storage/domains/credentials/base.js +259 -243
  130. package/dist/storage/domains/credentials/base.js.map +1 -1
  131. package/dist/storage/domains/custom-providers/base.js +138 -143
  132. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  133. package/dist/storage/domains/intake/base.js +75 -57
  134. package/dist/storage/domains/intake/base.js.map +1 -1
  135. package/dist/storage/domains/integrations/base.js +310 -245
  136. package/dist/storage/domains/integrations/base.js.map +1 -1
  137. package/dist/storage/domains/memory-settings/base.js +117 -102
  138. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  139. package/dist/storage/domains/model-packs/base.js +89 -88
  140. package/dist/storage/domains/model-packs/base.js.map +1 -1
  141. package/dist/storage/domains/projects/base.js +97 -92
  142. package/dist/storage/domains/projects/base.js.map +1 -1
  143. package/dist/storage/domains/queue-health/base.js +111 -76
  144. package/dist/storage/domains/queue-health/base.js.map +1 -1
  145. package/dist/storage/domains/source-control/base.d.ts +9 -0
  146. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  147. package/dist/storage/domains/source-control/base.js +777 -702
  148. package/dist/storage/domains/source-control/base.js.map +1 -1
  149. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  150. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  151. package/dist/storage/domains/source-control/inmemory.js +322 -344
  152. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  153. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  154. package/dist/storage/domains/work-items/base.js +1266 -1132
  155. package/dist/storage/domains/work-items/base.js.map +1 -1
  156. package/dist/storage/domains/work-items/metrics.js +202 -184
  157. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  158. package/dist/timing.d.ts +15 -0
  159. package/dist/timing.d.ts.map +1 -0
  160. package/dist/timing.js +34 -0
  161. package/dist/timing.js.map +1 -0
  162. package/dist/workspace.d.ts +2 -3
  163. package/dist/workspace.d.ts.map +1 -1
  164. package/dist/workspace.js +236 -438
  165. package/dist/workspace.js.map +1 -1
  166. package/factory-skills/factory-review/SKILL.md +9 -2
  167. package/package.json +9 -9
  168. package/dist/capabilities/connection.js.map +0 -1
  169. package/dist/capabilities/intake.js.map +0 -1
  170. package/dist/capabilities/version-control.js.map +0 -1
  171. package/dist/index.js.map +0 -1
  172. package/dist/integrations/base.js.map +0 -1
  173. package/dist/integrations/linear/storage.js.map +0 -1
  174. package/dist/rules/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/projects.ts","../../src/routes/route.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type {\n CreateFactoryProjectInput,\n FactoryProjectsStorage,\n UpdateFactoryProjectInput,\n} from '../storage/domains/projects/base.js';\nimport type {\n ProjectRepository,\n SourceControlStorage,\n SourceControlStorageHandle,\n UpdateProjectRepositoryInput,\n} from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst MAX_NAME_LENGTH = 200;\nconst MAX_DESCRIPTION_LENGTH = 2_000;\nconst MAX_SETUP_COMMAND_LENGTH = 2_000;\nconst MAX_BRANCH_LENGTH = 255;\nconst MAX_SANDBOX_PROVIDER_LENGTH = 100;\nconst MAX_SANDBOX_WORKDIR_LENGTH = 1_000;\nconst CONTROL_CHAR_RE = /[\\0-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/;\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nasync function readJson(context: Context): Promise<unknown | undefined> {\n try {\n return await context.req.json();\n } catch {\n return undefined;\n }\n}\n\nfunction parseCreateInput(value: unknown): CreateFactoryProjectInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.name !== 'string') return null;\n const name = input.name.trim();\n if (!name || name.length > MAX_NAME_LENGTH) return null;\n if (input.description !== undefined && input.description !== null && typeof input.description !== 'string')\n return null;\n const description = typeof input.description === 'string' ? input.description.trim() || null : null;\n if (description && description.length > MAX_DESCRIPTION_LENGTH) return null;\n return { name, description };\n}\n\nfunction parseUpdateInput(value: unknown): UpdateFactoryProjectInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n const patch: UpdateFactoryProjectInput = {};\n if (input.name !== undefined) {\n if (typeof input.name !== 'string') return null;\n const name = input.name.trim();\n if (!name || name.length > MAX_NAME_LENGTH) return null;\n patch.name = name;\n }\n if (input.description !== undefined) {\n if (input.description !== null && typeof input.description !== 'string') return null;\n const description = typeof input.description === 'string' ? input.description.trim() || null : null;\n if (description && description.length > MAX_DESCRIPTION_LENGTH) return null;\n patch.description = description;\n }\n if (input.defaultModelId !== undefined) {\n const defaultModelId = parseOptionalString(input.defaultModelId, { maxLength: MAX_NAME_LENGTH, nullable: true });\n if (defaultModelId === false) return null;\n patch.defaultModelId = defaultModelId ?? null;\n }\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nfunction parseConnectionInput(value: unknown): { integrationId: string; installationId: string } | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.integrationId !== 'string' || !input.integrationId.trim()) return null;\n if (typeof input.installationId !== 'string' || !UUID_RE.test(input.installationId)) return null;\n return { integrationId: input.integrationId.trim(), installationId: input.installationId };\n}\n\nfunction parseOptionalString(\n value: unknown,\n { maxLength, nullable = false }: { maxLength: number; nullable?: boolean },\n): string | null | undefined | false {\n if (value === undefined) return undefined;\n if (value === null) return nullable ? null : false;\n if (typeof value !== 'string') return false;\n const normalized = value.trim();\n if (!normalized) return nullable ? null : false;\n if (normalized.length > maxLength || CONTROL_CHAR_RE.test(normalized)) return false;\n return normalized;\n}\n\nfunction parseRepositoryLinkInput(value: unknown): {\n repositoryId: string;\n branch: string | null;\n sandboxProvider: string;\n sandboxWorkdir: string;\n setupCommand: string | null;\n} | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.repositoryId !== 'string' || !UUID_RE.test(input.repositoryId)) return null;\n const branch = parseOptionalString(input.branch, { maxLength: MAX_BRANCH_LENGTH, nullable: true });\n const sandboxProvider = parseOptionalString(input.sandboxProvider, { maxLength: MAX_SANDBOX_PROVIDER_LENGTH });\n const sandboxWorkdir = parseOptionalString(input.sandboxWorkdir, { maxLength: MAX_SANDBOX_WORKDIR_LENGTH });\n const setupCommand = parseOptionalString(input.setupCommand, { maxLength: MAX_SETUP_COMMAND_LENGTH, nullable: true });\n if (\n branch === false ||\n typeof sandboxProvider !== 'string' ||\n typeof sandboxWorkdir !== 'string' ||\n setupCommand === false\n )\n return null;\n return {\n repositoryId: input.repositoryId,\n branch: branch ?? null,\n sandboxProvider,\n sandboxWorkdir,\n setupCommand: setupCommand ?? null,\n };\n}\n\nfunction parseRepositoryUpdateInput(value: unknown): UpdateProjectRepositoryInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n const patch: UpdateProjectRepositoryInput = {};\n const branch = parseOptionalString(input.branch, { maxLength: MAX_BRANCH_LENGTH, nullable: true });\n const sandboxProvider = parseOptionalString(input.sandboxProvider, { maxLength: MAX_SANDBOX_PROVIDER_LENGTH });\n const sandboxWorkdir = parseOptionalString(input.sandboxWorkdir, { maxLength: MAX_SANDBOX_WORKDIR_LENGTH });\n const setupCommand = parseOptionalString(input.setupCommand, { maxLength: MAX_SETUP_COMMAND_LENGTH, nullable: true });\n if (\n branch === false ||\n sandboxProvider === false ||\n sandboxProvider === null ||\n sandboxWorkdir === false ||\n sandboxWorkdir === null ||\n setupCommand === false\n )\n return null;\n if (branch !== undefined) patch.branch = branch;\n if (sandboxProvider !== undefined) patch.sandboxProvider = sandboxProvider;\n if (sandboxWorkdir !== undefined) patch.sandboxWorkdir = sandboxWorkdir;\n if (setupCommand !== undefined) patch.setupCommand = setupCommand;\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nexport interface ProjectRoutesDeps extends RouteDependencies {\n /** Factory projects domain backing the CRUD surface. */\n projects: FactoryProjectsStorage;\n /** Source-control domain the connection/repository routes fan out over. */\n sourceControl: SourceControlStorage;\n /** Integration ids allowed as source-control connection targets. */\n versionControlIntegrationIds?: string[];\n}\n\nexport class ProjectRoutes extends Route<ProjectRoutesDeps> {\n readonly #versionControlIntegrationIds: Set<string>;\n\n constructor(deps: ProjectRoutesDeps) {\n super(deps);\n this.#versionControlIntegrationIds = new Set(deps.versionControlIntegrationIds ?? []);\n }\n\n async #projects(): Promise<FactoryProjectsStorage> {\n await this.deps.projects.ensureReady();\n return this.deps.projects;\n }\n\n async #sourceControl(): Promise<SourceControlStorage> {\n await this.deps.sourceControl.ensureReady();\n return this.deps.sourceControl;\n }\n\n async #handles(): Promise<SourceControlStorageHandle[]> {\n const storage = await this.#sourceControl();\n return [...this.#versionControlIntegrationIds].map(integrationId => storage.forIntegration(integrationId));\n }\n\n async #project(orgId: string, id: string) {\n return (await this.#projects()).get({ orgId, id });\n }\n\n async #findConnection({ orgId, projectId, id }: { orgId: string; projectId: string; id: string }) {\n for (const handle of await this.#handles()) {\n const connection = await handle.connections.get({ orgId, id });\n if (connection?.factoryProjectId === projectId) return { handle, connection };\n }\n return null;\n }\n\n async #findProjectRepository({ orgId, projectId, id }: { orgId: string; projectId: string; id: string }) {\n for (const handle of await this.#handles()) {\n const projectRepository = await handle.projectRepositories.get({ orgId, id });\n if (!projectRepository) continue;\n const connection = await handle.connections.get({ orgId, id: projectRepository.connectionId });\n if (connection?.factoryProjectId === projectId) return { handle, connection, projectRepository };\n }\n return null;\n }\n\n async #repositoryPayload(handle: SourceControlStorageHandle, orgId: string, projectRepository: ProjectRepository) {\n const repository = await handle.repositories.get({ orgId, id: projectRepository.repositoryId });\n return { ...projectRepository, repository };\n }\n\n async #resolveTenant(context: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(context);\n const tenant = this.deps.auth.tenant(context);\n if (!tenant) return { response: context.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: context.json(\n { error: 'organization_required', message: 'Factory projects require an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n return [\n registerApiRoute('/web/factory/projects', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n return context.json({ projects: await (await this.#projects()).list({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/factory/projects', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const input = parseCreateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project' }, 400);\n const project = await (await this.#projects()).create({ orgId: tenant.orgId, userId: tenant.userId, input });\n return context.json({ project }, 201);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n const project = await this.#project(tenant.orgId, id);\n return project ? context.json({ project }) : context.json({ error: 'Project not found' }, 404);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n const input = parseUpdateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project' }, 400);\n const project = await (await this.#projects()).update({ orgId: tenant.orgId, id, input });\n return project ? context.json({ project }) : context.json({ error: 'Project not found' }, 404);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n if (!(await this.#project(tenant.orgId, id))) return context.json({ error: 'Project not found' }, 404);\n for (const handle of await this.#handles()) {\n for (const connection of await handle.connections.list({ orgId: tenant.orgId, factoryProjectId: id })) {\n await handle.connections.delete({ orgId: tenant.orgId, id: connection.id });\n }\n }\n await (await this.#projects()).delete({ orgId: tenant.orgId, id });\n return context.body(null, 204);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n if (!projectId || !UUID_RE.test(projectId) || !(await this.#project(tenant.orgId, projectId)))\n return context.json({ error: 'Project not found' }, 404);\n const connections = [];\n for (const handle of await this.#handles()) {\n for (const connection of await handle.connections.list({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n })) {\n const installation = await handle.installations.get({\n orgId: tenant.orgId,\n id: connection.installationId,\n });\n // Skip orphaned connections whose installation was pruned (e.g.\n // the user uninstalled the GitHub App). Otherwise\n // `projectRepositories.list` throws `requireConnection` and the\n // whole endpoint 500s, which hangs the web UI on the page\n // loader for every project. New code cascade-deletes these on\n // installation removal, but this defensive skip lets already-\n // orphaned rows in existing databases self-heal on read.\n if (!installation) continue;\n const links = await handle.projectRepositories.list({ orgId: tenant.orgId, connectionId: connection.id });\n connections.push({\n ...connection,\n installation,\n repositories: await Promise.all(links.map(link => this.#repositoryPayload(handle, tenant.orgId, link))),\n });\n }\n }\n return context.json({ connections });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n if (!projectId || !UUID_RE.test(projectId) || !(await this.#project(tenant.orgId, projectId)))\n return context.json({ error: 'Project not found' }, 404);\n const input = parseConnectionInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_source_control_connection' }, 400);\n if (!this.#versionControlIntegrationIds.has(input.integrationId))\n return context.json({ error: 'Source-control integration not found' }, 404);\n const handle = (await this.#sourceControl()).forIntegration(input.integrationId);\n if (!(await handle.installations.get({ orgId: tenant.orgId, id: input.installationId })))\n return context.json({ error: 'Source-control installation not found' }, 404);\n const connection = await handle.connections.create({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n installationId: input.installationId,\n createdByUserId: tenant.userId,\n });\n return context.json({ connection }, 201);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections/:connectionId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const connectionId = context.req.param('connectionId');\n if (!projectId || !UUID_RE.test(projectId) || !connectionId || !UUID_RE.test(connectionId))\n return context.json({ error: 'Source-control connection not found' }, 404);\n const found = await this.#findConnection({ orgId: tenant.orgId, projectId, id: connectionId });\n if (!found) return context.json({ error: 'Source-control connection not found' }, 404);\n await found.handle.connections.delete({ orgId: tenant.orgId, id: connectionId });\n return context.body(null, 204);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections/:connectionId/repositories', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const connectionId = context.req.param('connectionId');\n if (!projectId || !UUID_RE.test(projectId) || !connectionId || !UUID_RE.test(connectionId))\n return context.json({ error: 'Source-control connection not found' }, 404);\n const found = await this.#findConnection({ orgId: tenant.orgId, projectId, id: connectionId });\n if (!found) return context.json({ error: 'Source-control connection not found' }, 404);\n const input = parseRepositoryLinkInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project_repository' }, 400);\n const repository = await found.handle.repositories.get({ orgId: tenant.orgId, id: input.repositoryId });\n if (!repository || repository.installationId !== found.connection.installationId)\n return context.json({ error: 'Source-control repository not found' }, 404);\n const projectRepository = await found.handle.projectRepositories.link({\n orgId: tenant.orgId,\n connectionId,\n createdByUserId: tenant.userId,\n ...input,\n });\n return context.json(\n { projectRepository: await this.#repositoryPayload(found.handle, tenant.orgId, projectRepository) },\n 201,\n );\n },\n }),\n registerApiRoute('/web/factory/projects/:id/repositories/:projectRepositoryId', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const projectRepositoryId = context.req.param('projectRepositoryId');\n if (!projectId || !UUID_RE.test(projectId) || !projectRepositoryId || !UUID_RE.test(projectRepositoryId))\n return context.json({ error: 'Project repository not found' }, 404);\n const found = await this.#findProjectRepository({ orgId: tenant.orgId, projectId, id: projectRepositoryId });\n if (!found) return context.json({ error: 'Project repository not found' }, 404);\n const input = parseRepositoryUpdateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project_repository' }, 400);\n const projectRepository = await found.handle.projectRepositories.update({\n orgId: tenant.orgId,\n id: projectRepositoryId,\n input,\n });\n return context.json({\n projectRepository: await this.#repositoryPayload(found.handle, tenant.orgId, projectRepository!),\n });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/repositories/:projectRepositoryId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const projectRepositoryId = context.req.param('projectRepositoryId');\n if (!projectId || !UUID_RE.test(projectId) || !projectRepositoryId || !UUID_RE.test(projectRepositoryId))\n return context.json({ error: 'Project repository not found' }, 404);\n const found = await this.#findProjectRepository({ orgId: tenant.orgId, projectId, id: projectRepositoryId });\n if (!found) return context.json({ error: 'Project repository not found' }, 404);\n await found.handle.projectRepositories.unlink({ orgId: tenant.orgId, id: projectRepositoryId });\n return context.body(null, 204);\n },\n }),\n ];\n }\n}\n","/**\n * Base class for factory route modules.\n *\n * Route modules build Mastra `apiRoutes` from injected dependencies instead of\n * reaching into host globals. The host server (e.g. `mastracode/web`) supplies\n * the auth seam and storage domain handles at construction time, so the routes\n * stay portable and testable with fakes.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\n/**\n * The auth surface factory routes need, implemented by the host server.\n *\n * Local (no-auth) deployments implement this with a stub where `enabled()`\n * returns `false` and `tenant()` returns `undefined` — routes then take their\n * single-user local paths.\n */\nexport interface RouteAuth {\n /** Whether an auth provider is active (tenant mode). */\n enabled(): boolean;\n /**\n * Resolve (and cache) the signed-in user for the request, if any. Must be\n * called before `tenant()` so the request context is populated.\n */\n ensureUser(c: Context): Promise<unknown>;\n /** Tenant identity for the request, when signed in. */\n tenant(c: Context): { orgId?: string; userId: string } | undefined;\n /** Fail-closed check that the caller administers the given organization. */\n isOrganizationAdmin(c: Context, organizationId: string): Promise<boolean>;\n}\n\n/** Dependencies shared by every factory route module. */\nexport interface RouteDependencies {\n auth: RouteAuth;\n}\n\n/**\n * A route module: constructed once at boot with its dependencies, then asked\n * for the `ApiRoute[]` it serves.\n */\nexport abstract class Route<TDeps extends RouteDependencies = RouteDependencies> {\n protected readonly deps: TDeps;\n\n constructor(deps: TDeps) {\n this.deps = deps;\n }\n\n /** Build the Mastra `apiRoutes` served by this module. */\n abstract routes(): ApiRoute[];\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"projects.js","names":["#versionControlIntegrationIds","#projects","#sourceControl","#handles","#project","#findConnection","#findProjectRepository","#repositoryPayload","#resolveTenant"],"sources":["../../src/routes/projects.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type {\n CreateFactoryProjectInput,\n FactoryProjectsStorage,\n UpdateFactoryProjectInput,\n} from '../storage/domains/projects/base.js';\nimport type {\n ProjectRepository,\n SourceControlStorage,\n SourceControlStorageHandle,\n UpdateProjectRepositoryInput,\n} from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst MAX_NAME_LENGTH = 200;\nconst MAX_DESCRIPTION_LENGTH = 2_000;\nconst MAX_SETUP_COMMAND_LENGTH = 2_000;\nconst MAX_BRANCH_LENGTH = 255;\nconst MAX_SANDBOX_PROVIDER_LENGTH = 100;\nconst MAX_SANDBOX_WORKDIR_LENGTH = 1_000;\nconst CONTROL_CHAR_RE = /[\\0-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/;\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nasync function readJson(context: Context): Promise<unknown | undefined> {\n try {\n return await context.req.json();\n } catch {\n return undefined;\n }\n}\n\nfunction parseCreateInput(value: unknown): CreateFactoryProjectInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.name !== 'string') return null;\n const name = input.name.trim();\n if (!name || name.length > MAX_NAME_LENGTH) return null;\n if (input.description !== undefined && input.description !== null && typeof input.description !== 'string')\n return null;\n const description = typeof input.description === 'string' ? input.description.trim() || null : null;\n if (description && description.length > MAX_DESCRIPTION_LENGTH) return null;\n return { name, description };\n}\n\nfunction parseUpdateInput(value: unknown): UpdateFactoryProjectInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n const patch: UpdateFactoryProjectInput = {};\n if (input.name !== undefined) {\n if (typeof input.name !== 'string') return null;\n const name = input.name.trim();\n if (!name || name.length > MAX_NAME_LENGTH) return null;\n patch.name = name;\n }\n if (input.description !== undefined) {\n if (input.description !== null && typeof input.description !== 'string') return null;\n const description = typeof input.description === 'string' ? input.description.trim() || null : null;\n if (description && description.length > MAX_DESCRIPTION_LENGTH) return null;\n patch.description = description;\n }\n if (input.defaultModelId !== undefined) {\n const defaultModelId = parseOptionalString(input.defaultModelId, { maxLength: MAX_NAME_LENGTH, nullable: true });\n if (defaultModelId === false) return null;\n patch.defaultModelId = defaultModelId ?? null;\n }\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nfunction parseConnectionInput(value: unknown): { integrationId: string; installationId: string } | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.integrationId !== 'string' || !input.integrationId.trim()) return null;\n if (typeof input.installationId !== 'string' || !UUID_RE.test(input.installationId)) return null;\n return { integrationId: input.integrationId.trim(), installationId: input.installationId };\n}\n\nfunction parseOptionalString(\n value: unknown,\n { maxLength, nullable = false }: { maxLength: number; nullable?: boolean },\n): string | null | undefined | false {\n if (value === undefined) return undefined;\n if (value === null) return nullable ? null : false;\n if (typeof value !== 'string') return false;\n const normalized = value.trim();\n if (!normalized) return nullable ? null : false;\n if (normalized.length > maxLength || CONTROL_CHAR_RE.test(normalized)) return false;\n return normalized;\n}\n\nfunction parseRepositoryLinkInput(value: unknown): {\n repositoryId: string;\n branch: string | null;\n sandboxProvider: string;\n sandboxWorkdir: string;\n setupCommand: string | null;\n} | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.repositoryId !== 'string' || !UUID_RE.test(input.repositoryId)) return null;\n const branch = parseOptionalString(input.branch, { maxLength: MAX_BRANCH_LENGTH, nullable: true });\n const sandboxProvider = parseOptionalString(input.sandboxProvider, { maxLength: MAX_SANDBOX_PROVIDER_LENGTH });\n const sandboxWorkdir = parseOptionalString(input.sandboxWorkdir, { maxLength: MAX_SANDBOX_WORKDIR_LENGTH });\n const setupCommand = parseOptionalString(input.setupCommand, { maxLength: MAX_SETUP_COMMAND_LENGTH, nullable: true });\n if (\n branch === false ||\n typeof sandboxProvider !== 'string' ||\n typeof sandboxWorkdir !== 'string' ||\n setupCommand === false\n )\n return null;\n return {\n repositoryId: input.repositoryId,\n branch: branch ?? null,\n sandboxProvider,\n sandboxWorkdir,\n setupCommand: setupCommand ?? null,\n };\n}\n\nfunction parseRepositoryUpdateInput(value: unknown): UpdateProjectRepositoryInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n const patch: UpdateProjectRepositoryInput = {};\n const branch = parseOptionalString(input.branch, { maxLength: MAX_BRANCH_LENGTH, nullable: true });\n const sandboxProvider = parseOptionalString(input.sandboxProvider, { maxLength: MAX_SANDBOX_PROVIDER_LENGTH });\n const sandboxWorkdir = parseOptionalString(input.sandboxWorkdir, { maxLength: MAX_SANDBOX_WORKDIR_LENGTH });\n const setupCommand = parseOptionalString(input.setupCommand, { maxLength: MAX_SETUP_COMMAND_LENGTH, nullable: true });\n if (\n branch === false ||\n sandboxProvider === false ||\n sandboxProvider === null ||\n sandboxWorkdir === false ||\n sandboxWorkdir === null ||\n setupCommand === false\n )\n return null;\n if (branch !== undefined) patch.branch = branch;\n if (sandboxProvider !== undefined) patch.sandboxProvider = sandboxProvider;\n if (sandboxWorkdir !== undefined) patch.sandboxWorkdir = sandboxWorkdir;\n if (setupCommand !== undefined) patch.setupCommand = setupCommand;\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nexport interface ProjectRoutesDeps extends RouteDependencies {\n /** Factory projects domain backing the CRUD surface. */\n projects: FactoryProjectsStorage;\n /** Source-control domain the connection/repository routes fan out over. */\n sourceControl: SourceControlStorage;\n /** Integration ids allowed as source-control connection targets. */\n versionControlIntegrationIds?: string[];\n}\n\nexport class ProjectRoutes extends Route<ProjectRoutesDeps> {\n readonly #versionControlIntegrationIds: Set<string>;\n\n constructor(deps: ProjectRoutesDeps) {\n super(deps);\n this.#versionControlIntegrationIds = new Set(deps.versionControlIntegrationIds ?? []);\n }\n\n async #projects(): Promise<FactoryProjectsStorage> {\n await this.deps.projects.ensureReady();\n return this.deps.projects;\n }\n\n async #sourceControl(): Promise<SourceControlStorage> {\n await this.deps.sourceControl.ensureReady();\n return this.deps.sourceControl;\n }\n\n async #handles(): Promise<SourceControlStorageHandle[]> {\n const storage = await this.#sourceControl();\n return [...this.#versionControlIntegrationIds].map(integrationId => storage.forIntegration(integrationId));\n }\n\n async #project(orgId: string, id: string) {\n return (await this.#projects()).get({ orgId, id });\n }\n\n async #findConnection({ orgId, projectId, id }: { orgId: string; projectId: string; id: string }) {\n for (const handle of await this.#handles()) {\n const connection = await handle.connections.get({ orgId, id });\n if (connection?.factoryProjectId === projectId) return { handle, connection };\n }\n return null;\n }\n\n async #findProjectRepository({ orgId, projectId, id }: { orgId: string; projectId: string; id: string }) {\n for (const handle of await this.#handles()) {\n const projectRepository = await handle.projectRepositories.get({ orgId, id });\n if (!projectRepository) continue;\n const connection = await handle.connections.get({ orgId, id: projectRepository.connectionId });\n if (connection?.factoryProjectId === projectId) return { handle, connection, projectRepository };\n }\n return null;\n }\n\n async #repositoryPayload(handle: SourceControlStorageHandle, orgId: string, projectRepository: ProjectRepository) {\n const repository = await handle.repositories.get({ orgId, id: projectRepository.repositoryId });\n return { ...projectRepository, repository };\n }\n\n async #resolveTenant(context: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(context);\n const tenant = this.deps.auth.tenant(context);\n if (!tenant) return { response: context.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: context.json(\n { error: 'organization_required', message: 'Factory projects require an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n return [\n registerApiRoute('/web/factory/projects', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n return context.json({ projects: await (await this.#projects()).list({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/factory/projects', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const input = parseCreateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project' }, 400);\n const project = await (await this.#projects()).create({ orgId: tenant.orgId, userId: tenant.userId, input });\n return context.json({ project }, 201);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n const project = await this.#project(tenant.orgId, id);\n return project ? context.json({ project }) : context.json({ error: 'Project not found' }, 404);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n const input = parseUpdateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project' }, 400);\n const project = await (await this.#projects()).update({ orgId: tenant.orgId, id, input });\n return project ? context.json({ project }) : context.json({ error: 'Project not found' }, 404);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n if (!(await this.#project(tenant.orgId, id))) return context.json({ error: 'Project not found' }, 404);\n for (const handle of await this.#handles()) {\n for (const connection of await handle.connections.list({ orgId: tenant.orgId, factoryProjectId: id })) {\n await handle.connections.delete({ orgId: tenant.orgId, id: connection.id });\n }\n }\n await (await this.#projects()).delete({ orgId: tenant.orgId, id });\n return context.body(null, 204);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n if (!projectId || !UUID_RE.test(projectId) || !(await this.#project(tenant.orgId, projectId)))\n return context.json({ error: 'Project not found' }, 404);\n const connections = [];\n for (const handle of await this.#handles()) {\n for (const connection of await handle.connections.list({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n })) {\n const installation = await handle.installations.get({\n orgId: tenant.orgId,\n id: connection.installationId,\n });\n // Skip orphaned connections whose installation was pruned (e.g.\n // the user uninstalled the GitHub App). Otherwise\n // `projectRepositories.list` throws `requireConnection` and the\n // whole endpoint 500s, which hangs the web UI on the page\n // loader for every project. New code cascade-deletes these on\n // installation removal, but this defensive skip lets already-\n // orphaned rows in existing databases self-heal on read.\n if (!installation) continue;\n const links = await handle.projectRepositories.list({ orgId: tenant.orgId, connectionId: connection.id });\n connections.push({\n ...connection,\n installation,\n repositories: await Promise.all(links.map(link => this.#repositoryPayload(handle, tenant.orgId, link))),\n });\n }\n }\n return context.json({ connections });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n if (!projectId || !UUID_RE.test(projectId) || !(await this.#project(tenant.orgId, projectId)))\n return context.json({ error: 'Project not found' }, 404);\n const input = parseConnectionInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_source_control_connection' }, 400);\n if (!this.#versionControlIntegrationIds.has(input.integrationId))\n return context.json({ error: 'Source-control integration not found' }, 404);\n const handle = (await this.#sourceControl()).forIntegration(input.integrationId);\n if (!(await handle.installations.get({ orgId: tenant.orgId, id: input.installationId })))\n return context.json({ error: 'Source-control installation not found' }, 404);\n const connection = await handle.connections.create({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n installationId: input.installationId,\n createdByUserId: tenant.userId,\n });\n return context.json({ connection }, 201);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections/:connectionId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const connectionId = context.req.param('connectionId');\n if (!projectId || !UUID_RE.test(projectId) || !connectionId || !UUID_RE.test(connectionId))\n return context.json({ error: 'Source-control connection not found' }, 404);\n const found = await this.#findConnection({ orgId: tenant.orgId, projectId, id: connectionId });\n if (!found) return context.json({ error: 'Source-control connection not found' }, 404);\n await found.handle.connections.delete({ orgId: tenant.orgId, id: connectionId });\n return context.body(null, 204);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections/:connectionId/repositories', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const connectionId = context.req.param('connectionId');\n if (!projectId || !UUID_RE.test(projectId) || !connectionId || !UUID_RE.test(connectionId))\n return context.json({ error: 'Source-control connection not found' }, 404);\n const found = await this.#findConnection({ orgId: tenant.orgId, projectId, id: connectionId });\n if (!found) return context.json({ error: 'Source-control connection not found' }, 404);\n const input = parseRepositoryLinkInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project_repository' }, 400);\n const repository = await found.handle.repositories.get({ orgId: tenant.orgId, id: input.repositoryId });\n if (!repository || repository.installationId !== found.connection.installationId)\n return context.json({ error: 'Source-control repository not found' }, 404);\n const projectRepository = await found.handle.projectRepositories.link({\n orgId: tenant.orgId,\n connectionId,\n createdByUserId: tenant.userId,\n ...input,\n });\n return context.json(\n { projectRepository: await this.#repositoryPayload(found.handle, tenant.orgId, projectRepository) },\n 201,\n );\n },\n }),\n registerApiRoute('/web/factory/projects/:id/repositories/:projectRepositoryId', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const projectRepositoryId = context.req.param('projectRepositoryId');\n if (!projectId || !UUID_RE.test(projectId) || !projectRepositoryId || !UUID_RE.test(projectRepositoryId))\n return context.json({ error: 'Project repository not found' }, 404);\n const found = await this.#findProjectRepository({ orgId: tenant.orgId, projectId, id: projectRepositoryId });\n if (!found) return context.json({ error: 'Project repository not found' }, 404);\n const input = parseRepositoryUpdateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project_repository' }, 400);\n const projectRepository = await found.handle.projectRepositories.update({\n orgId: tenant.orgId,\n id: projectRepositoryId,\n input,\n });\n return context.json({\n projectRepository: await this.#repositoryPayload(found.handle, tenant.orgId, projectRepository!),\n });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/repositories/:projectRepositoryId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const projectRepositoryId = context.req.param('projectRepositoryId');\n if (!projectId || !UUID_RE.test(projectId) || !projectRepositoryId || !UUID_RE.test(projectRepositoryId))\n return context.json({ error: 'Project repository not found' }, 404);\n const found = await this.#findProjectRepository({ orgId: tenant.orgId, projectId, id: projectRepositoryId });\n if (!found) return context.json({ error: 'Project repository not found' }, 404);\n await found.handle.projectRepositories.unlink({ orgId: tenant.orgId, id: projectRepositoryId });\n return context.body(null, 204);\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
@@ -1,68 +1,86 @@
1
- // src/routes/provider-credentials.ts
1
+ //#region src/routes/provider-credentials.ts
2
+ /**
3
+ * OAuth credentials are stored under the auth provider id, which differs from
4
+ * the catalog provider id for OpenAI (stored as `openai-codex`). Tenant rows
5
+ * use the same keying so one lookup serves both OAuth tokens and API keys.
6
+ */
2
7
  function getAuthProviderId(provider) {
3
- return provider === "openai" ? "openai-codex" : provider;
8
+ return provider === "openai" ? "openai-codex" : provider;
4
9
  }
5
- var WEB_OAUTH_FLOW_KINDS = {
6
- anthropic: "paste-code",
7
- openai: "device-code",
8
- "github-copilot": "device-code",
9
- xai: "device-code"
10
+ /**
11
+ * Providers that support a browser-driven OAuth sign-in flow, keyed by
12
+ * *catalog* provider id (what the routes and settings UI use). All flows need
13
+ * no inbound connection to the server: Anthropic uses paste-code PKCE, the
14
+ * rest use RFC 8628-style device codes.
15
+ */
16
+ const WEB_OAUTH_FLOW_KINDS = {
17
+ anthropic: "paste-code",
18
+ openai: "device-code",
19
+ "github-copilot": "device-code",
20
+ xai: "device-code"
10
21
  };
22
+ /**
23
+ * The tenant credentials domain, when registered and ready. `undefined` means
24
+ * the factory never ran (no handle threaded in) or the domain's init failed
25
+ * (fail-soft — callers report the feature unavailable instead of crashing).
26
+ */
11
27
  async function getTenantCredentialsStorage(credentials) {
12
- if (!credentials) return void 0;
13
- try {
14
- await credentials.ensureReady();
15
- } catch {
16
- return void 0;
17
- }
18
- return credentials;
28
+ if (!credentials) return void 0;
29
+ try {
30
+ await credentials.ensureReady();
31
+ } catch {
32
+ return;
33
+ }
34
+ return credentials;
19
35
  }
36
+ /**
37
+ * Org key for tenant rows. Personal accounts get an org bootstrapped by the
38
+ * auth adapter (`ensureOrg`); if one still isn't present, scope rows under a
39
+ * per-user synthetic org so credentials never become server-global.
40
+ */
20
41
  function tenantOrgId(tenant) {
21
- return tenant.orgId ?? `user:${tenant.userId}`;
42
+ return tenant.orgId ?? `user:${tenant.userId}`;
22
43
  }
23
- async function resolveCredentialContext({
24
- c,
25
- auth,
26
- credentials
27
- }) {
28
- await auth.ensureUser(c);
29
- const tenant = auth.tenant(c);
30
- if (!tenant) {
31
- if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
32
- return { mode: "local" };
33
- }
34
- const storage = await getTenantCredentialsStorage(credentials);
35
- if (!storage) {
36
- return {
37
- response: c.json(
38
- {
39
- error: "credentials_unavailable",
40
- message: "Tenant credential storage is unavailable \u2014 the app database is not configured or failed to start."
41
- },
42
- 503
43
- )
44
- };
45
- }
46
- return { mode: "tenant", storage, orgId: tenantOrgId(tenant), userId: tenant.userId };
44
+ /**
45
+ * Resolve the credential context for a request, or a ready-to-return error
46
+ * response. Mutating credential routes call this and hard-fail (401/503)
47
+ * when the tenant path is required but unavailable.
48
+ */
49
+ async function resolveCredentialContext({ c, auth, credentials }) {
50
+ await auth.ensureUser(c);
51
+ const tenant = auth.tenant(c);
52
+ if (!tenant) {
53
+ if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
54
+ return { mode: "local" };
55
+ }
56
+ const storage = await getTenantCredentialsStorage(credentials);
57
+ if (!storage) return { response: c.json({
58
+ error: "credentials_unavailable",
59
+ message: "Tenant credential storage is unavailable — the app database is not configured or failed to start."
60
+ }, 503) };
61
+ return {
62
+ mode: "tenant",
63
+ storage,
64
+ orgId: tenantOrgId(tenant),
65
+ userId: tenant.userId
66
+ };
47
67
  }
48
- async function listTenantCredentialsForRequest({
49
- c,
50
- auth,
51
- credentials
52
- }) {
53
- await auth.ensureUser(c);
54
- const tenant = auth.tenant(c);
55
- if (!tenant) return auth.enabled() ? [] : void 0;
56
- const storage = await getTenantCredentialsStorage(credentials);
57
- if (!storage) return [];
58
- return storage.listCredentials(tenantOrgId(tenant), tenant.userId);
68
+ /**
69
+ * List the caller's tenant credential records for provider listing, or
70
+ * `undefined` in local mode (caller should fall back to `AuthStorage`).
71
+ * Read-only and degradation-friendly: an authenticated caller with the domain
72
+ * unavailable gets an empty list (sources show `env`/`none`) instead of a 503
73
+ * so the settings page still renders.
74
+ */
75
+ async function listTenantCredentialsForRequest({ c, auth, credentials }) {
76
+ await auth.ensureUser(c);
77
+ const tenant = auth.tenant(c);
78
+ if (!tenant) return auth.enabled() ? [] : void 0;
79
+ const storage = await getTenantCredentialsStorage(credentials);
80
+ if (!storage) return [];
81
+ return storage.listCredentials(tenantOrgId(tenant), tenant.userId);
59
82
  }
60
- export {
61
- WEB_OAUTH_FLOW_KINDS,
62
- getAuthProviderId,
63
- getTenantCredentialsStorage,
64
- listTenantCredentialsForRequest,
65
- resolveCredentialContext,
66
- tenantOrgId
67
- };
83
+ //#endregion
84
+ export { WEB_OAUTH_FLOW_KINDS, getAuthProviderId, getTenantCredentialsStorage, listTenantCredentialsForRequest, resolveCredentialContext, tenantOrgId };
85
+
68
86
  //# sourceMappingURL=provider-credentials.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/provider-credentials.ts"],"sourcesContent":["/**\n * Per-request model-provider credential context.\n *\n * The provider config/OAuth routes serve two deployment shapes:\n *\n * - **Local** (no web auth adapter): credentials live in the server-global\n * file-backed `AuthStorage` (`auth.json`) — unchanged TUI behavior.\n * - **Tenant** (auth adapter active): credentials live in the\n * `model-credentials` factory storage domain, scoped per `(orgId, userId)`\n * with optional org-wide rows (`userId` absent).\n *\n * `resolveCredentialContext` decides which shape a request is in: an\n * authenticated user always gets the tenant path; an unauthenticated request\n * is rejected when auth is enabled and falls back to local otherwise.\n */\n\nimport type { Context } from 'hono';\n\nimport type {\n CredentialRecord,\n LoginSessionKind,\n ModelCredentialsStorage,\n} from '../storage/domains/credentials/base.js';\nimport type { RouteAuth } from './route.js';\n\n/**\n * OAuth credentials are stored under the auth provider id, which differs from\n * the catalog provider id for OpenAI (stored as `openai-codex`). Tenant rows\n * use the same keying so one lookup serves both OAuth tokens and API keys.\n */\nexport function getAuthProviderId(provider: string): string {\n return provider === 'openai' ? 'openai-codex' : provider;\n}\n\n/**\n * Providers that support a browser-driven OAuth sign-in flow, keyed by\n * *catalog* provider id (what the routes and settings UI use). All flows need\n * no inbound connection to the server: Anthropic uses paste-code PKCE, the\n * rest use RFC 8628-style device codes.\n */\nexport const WEB_OAUTH_FLOW_KINDS: Readonly<Record<string, LoginSessionKind>> = {\n anthropic: 'paste-code',\n openai: 'device-code',\n 'github-copilot': 'device-code',\n xai: 'device-code',\n};\n\nexport type CredentialContext =\n | { mode: 'local' }\n | { mode: 'tenant'; storage: ModelCredentialsStorage; orgId: string; userId: string };\n\n/**\n * The tenant credentials domain, when registered and ready. `undefined` means\n * the factory never ran (no handle threaded in) or the domain's init failed\n * (fail-soft — callers report the feature unavailable instead of crashing).\n */\nexport async function getTenantCredentialsStorage(\n credentials: ModelCredentialsStorage | undefined,\n): Promise<ModelCredentialsStorage | undefined> {\n if (!credentials) return undefined;\n try {\n await credentials.ensureReady();\n } catch {\n return undefined;\n }\n return credentials;\n}\n\n/**\n * Org key for tenant rows. Personal accounts get an org bootstrapped by the\n * auth adapter (`ensureOrg`); if one still isn't present, scope rows under a\n * per-user synthetic org so credentials never become server-global.\n */\nexport function tenantOrgId(tenant: { orgId?: string; userId: string }): string {\n return tenant.orgId ?? `user:${tenant.userId}`;\n}\n\n/**\n * Resolve the credential context for a request, or a ready-to-return error\n * response. Mutating credential routes call this and hard-fail (401/503)\n * when the tenant path is required but unavailable.\n */\nexport async function resolveCredentialContext({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialContext | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) {\n // When an auth provider is active, credential operations always require a\n // signed-in caller — otherwise one anonymous request could write keys for\n // everyone. Without a provider this is a single-user local server.\n if (auth.enabled()) return { response: c.json({ error: 'unauthorized' }, 401) };\n return { mode: 'local' };\n }\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) {\n return {\n response: c.json(\n {\n error: 'credentials_unavailable',\n message: 'Tenant credential storage is unavailable — the app database is not configured or failed to start.',\n },\n 503,\n ),\n };\n }\n return { mode: 'tenant', storage, orgId: tenantOrgId(tenant), userId: tenant.userId };\n}\n\n/**\n * List the caller's tenant credential records for provider listing, or\n * `undefined` in local mode (caller should fall back to `AuthStorage`).\n * Read-only and degradation-friendly: an authenticated caller with the domain\n * unavailable gets an empty list (sources show `env`/`none`) instead of a 503\n * so the settings page still renders.\n */\nexport async function listTenantCredentialsForRequest({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialRecord[] | undefined> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return auth.enabled() ? [] : undefined;\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) return [];\n return storage.listCredentials(tenantOrgId(tenant), tenant.userId);\n}\n"],"mappings":";AA8BO,SAAS,kBAAkB,UAA0B;AAC1D,SAAO,aAAa,WAAW,iBAAiB;AAClD;AAQO,IAAM,uBAAmE;AAAA,EAC9E,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,kBAAkB;AAAA,EAClB,KAAK;AACP;AAWA,eAAsB,4BACpB,aAC8C;AAC9C,MAAI,CAAC,YAAa,QAAO;AACzB,MAAI;AACF,UAAM,YAAY,YAAY;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,YAAY,QAAoD;AAC9E,SAAO,OAAO,SAAS,QAAQ,OAAO,MAAM;AAC9C;AAOA,eAAsB,yBAAyB;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AACF,GAKwD;AACtD,QAAM,KAAK,WAAW,CAAC;AACvB,QAAM,SAAS,KAAK,OAAO,CAAC;AAC5B,MAAI,CAAC,QAAQ;AAIX,QAAI,KAAK,QAAQ,EAAG,QAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG,EAAE;AAC9E,WAAO,EAAE,MAAM,QAAQ;AAAA,EACzB;AACA,QAAM,UAAU,MAAM,4BAA4B,WAAW;AAC7D,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,UAAU,EAAE;AAAA,QACV;AAAA,UACE,OAAO;AAAA,UACP,SAAS;AAAA,QACX;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,MAAM,UAAU,SAAS,OAAO,YAAY,MAAM,GAAG,QAAQ,OAAO,OAAO;AACtF;AASA,eAAsB,gCAAgC;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AACF,GAK4C;AAC1C,QAAM,KAAK,WAAW,CAAC;AACvB,QAAM,SAAS,KAAK,OAAO,CAAC;AAC5B,MAAI,CAAC,OAAQ,QAAO,KAAK,QAAQ,IAAI,CAAC,IAAI;AAC1C,QAAM,UAAU,MAAM,4BAA4B,WAAW;AAC7D,MAAI,CAAC,QAAS,QAAO,CAAC;AACtB,SAAO,QAAQ,gBAAgB,YAAY,MAAM,GAAG,OAAO,MAAM;AACnE;","names":[]}
1
+ {"version":3,"file":"provider-credentials.js","names":[],"sources":["../../src/routes/provider-credentials.ts"],"sourcesContent":["/**\n * Per-request model-provider credential context.\n *\n * The provider config/OAuth routes serve two deployment shapes:\n *\n * - **Local** (no web auth adapter): credentials live in the server-global\n * file-backed `AuthStorage` (`auth.json`) — unchanged TUI behavior.\n * - **Tenant** (auth adapter active): credentials live in the\n * `model-credentials` factory storage domain, scoped per `(orgId, userId)`\n * with optional org-wide rows (`userId` absent).\n *\n * `resolveCredentialContext` decides which shape a request is in: an\n * authenticated user always gets the tenant path; an unauthenticated request\n * is rejected when auth is enabled and falls back to local otherwise.\n */\n\nimport type { Context } from 'hono';\n\nimport type {\n CredentialRecord,\n LoginSessionKind,\n ModelCredentialsStorage,\n} from '../storage/domains/credentials/base.js';\nimport type { RouteAuth } from './route.js';\n\n/**\n * OAuth credentials are stored under the auth provider id, which differs from\n * the catalog provider id for OpenAI (stored as `openai-codex`). Tenant rows\n * use the same keying so one lookup serves both OAuth tokens and API keys.\n */\nexport function getAuthProviderId(provider: string): string {\n return provider === 'openai' ? 'openai-codex' : provider;\n}\n\n/**\n * Providers that support a browser-driven OAuth sign-in flow, keyed by\n * *catalog* provider id (what the routes and settings UI use). All flows need\n * no inbound connection to the server: Anthropic uses paste-code PKCE, the\n * rest use RFC 8628-style device codes.\n */\nexport const WEB_OAUTH_FLOW_KINDS: Readonly<Record<string, LoginSessionKind>> = {\n anthropic: 'paste-code',\n openai: 'device-code',\n 'github-copilot': 'device-code',\n xai: 'device-code',\n};\n\nexport type CredentialContext =\n | { mode: 'local' }\n | { mode: 'tenant'; storage: ModelCredentialsStorage; orgId: string; userId: string };\n\n/**\n * The tenant credentials domain, when registered and ready. `undefined` means\n * the factory never ran (no handle threaded in) or the domain's init failed\n * (fail-soft — callers report the feature unavailable instead of crashing).\n */\nexport async function getTenantCredentialsStorage(\n credentials: ModelCredentialsStorage | undefined,\n): Promise<ModelCredentialsStorage | undefined> {\n if (!credentials) return undefined;\n try {\n await credentials.ensureReady();\n } catch {\n return undefined;\n }\n return credentials;\n}\n\n/**\n * Org key for tenant rows. Personal accounts get an org bootstrapped by the\n * auth adapter (`ensureOrg`); if one still isn't present, scope rows under a\n * per-user synthetic org so credentials never become server-global.\n */\nexport function tenantOrgId(tenant: { orgId?: string; userId: string }): string {\n return tenant.orgId ?? `user:${tenant.userId}`;\n}\n\n/**\n * Resolve the credential context for a request, or a ready-to-return error\n * response. Mutating credential routes call this and hard-fail (401/503)\n * when the tenant path is required but unavailable.\n */\nexport async function resolveCredentialContext({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialContext | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) {\n // When an auth provider is active, credential operations always require a\n // signed-in caller — otherwise one anonymous request could write keys for\n // everyone. Without a provider this is a single-user local server.\n if (auth.enabled()) return { response: c.json({ error: 'unauthorized' }, 401) };\n return { mode: 'local' };\n }\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) {\n return {\n response: c.json(\n {\n error: 'credentials_unavailable',\n message: 'Tenant credential storage is unavailable — the app database is not configured or failed to start.',\n },\n 503,\n ),\n };\n }\n return { mode: 'tenant', storage, orgId: tenantOrgId(tenant), userId: tenant.userId };\n}\n\n/**\n * List the caller's tenant credential records for provider listing, or\n * `undefined` in local mode (caller should fall back to `AuthStorage`).\n * Read-only and degradation-friendly: an authenticated caller with the domain\n * unavailable gets an empty list (sources show `env`/`none`) instead of a 503\n * so the settings page still renders.\n */\nexport async function listTenantCredentialsForRequest({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialRecord[] | undefined> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return auth.enabled() ? [] : undefined;\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) return [];\n return storage.listCredentials(tenantOrgId(tenant), tenant.userId);\n}\n"],"mappings":";;;;;;AA8BA,SAAgB,kBAAkB,UAA0B;CAC1D,OAAO,aAAa,WAAW,iBAAiB;AAClD;;;;;;;AAQA,MAAa,uBAAmE;CAC9E,WAAW;CACX,QAAQ;CACR,kBAAkB;CAClB,KAAK;AACP;;;;;;AAWA,eAAsB,4BACpB,aAC8C;CAC9C,IAAI,CAAC,aAAa,OAAO,KAAA;CACzB,IAAI;EACF,MAAM,YAAY,YAAY;CAChC,QAAQ;EACN;CACF;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,YAAY,QAAoD;CAC9E,OAAO,OAAO,SAAS,QAAQ,OAAO;AACxC;;;;;;AAOA,eAAsB,yBAAyB,EAC7C,GACA,MACA,eAMsD;CACtD,MAAM,KAAK,WAAW,CAAC;CACvB,MAAM,SAAS,KAAK,OAAO,CAAC;CAC5B,IAAI,CAAC,QAAQ;EAIX,IAAI,KAAK,QAAQ,GAAG,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG,EAAE;EAC9E,OAAO,EAAE,MAAM,QAAQ;CACzB;CACA,MAAM,UAAU,MAAM,4BAA4B,WAAW;CAC7D,IAAI,CAAC,SACH,OAAO,EACL,UAAU,EAAE,KACV;EACE,OAAO;EACP,SAAS;CACX,GACA,GACF,EACF;CAEF,OAAO;EAAE,MAAM;EAAU;EAAS,OAAO,YAAY,MAAM;EAAG,QAAQ,OAAO;CAAO;AACtF;;;;;;;;AASA,eAAsB,gCAAgC,EACpD,GACA,MACA,eAM0C;CAC1C,MAAM,KAAK,WAAW,CAAC;CACvB,MAAM,SAAS,KAAK,OAAO,CAAC;CAC5B,IAAI,CAAC,QAAQ,OAAO,KAAK,QAAQ,IAAI,CAAC,IAAI,KAAA;CAC1C,MAAM,UAAU,MAAM,4BAA4B,WAAW;CAC7D,IAAI,CAAC,SAAS,OAAO,CAAC;CACtB,OAAO,QAAQ,gBAAgB,YAAY,MAAM,GAAG,OAAO,MAAM;AACnE"}
@@ -1,11 +1,15 @@
1
- // src/routes/route.ts
1
+ //#region src/routes/route.ts
2
+ /**
3
+ * A route module: constructed once at boot with its dependencies, then asked
4
+ * for the `ApiRoute[]` it serves.
5
+ */
2
6
  var Route = class {
3
- deps;
4
- constructor(deps) {
5
- this.deps = deps;
6
- }
7
- };
8
- export {
9
- Route
7
+ deps;
8
+ constructor(deps) {
9
+ this.deps = deps;
10
+ }
10
11
  };
12
+ //#endregion
13
+ export { Route };
14
+
11
15
  //# sourceMappingURL=route.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/route.ts"],"sourcesContent":["/**\n * Base class for factory route modules.\n *\n * Route modules build Mastra `apiRoutes` from injected dependencies instead of\n * reaching into host globals. The host server (e.g. `mastracode/web`) supplies\n * the auth seam and storage domain handles at construction time, so the routes\n * stay portable and testable with fakes.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\n/**\n * The auth surface factory routes need, implemented by the host server.\n *\n * Local (no-auth) deployments implement this with a stub where `enabled()`\n * returns `false` and `tenant()` returns `undefined` — routes then take their\n * single-user local paths.\n */\nexport interface RouteAuth {\n /** Whether an auth provider is active (tenant mode). */\n enabled(): boolean;\n /**\n * Resolve (and cache) the signed-in user for the request, if any. Must be\n * called before `tenant()` so the request context is populated.\n */\n ensureUser(c: Context): Promise<unknown>;\n /** Tenant identity for the request, when signed in. */\n tenant(c: Context): { orgId?: string; userId: string } | undefined;\n /** Fail-closed check that the caller administers the given organization. */\n isOrganizationAdmin(c: Context, organizationId: string): Promise<boolean>;\n}\n\n/** Dependencies shared by every factory route module. */\nexport interface RouteDependencies {\n auth: RouteAuth;\n}\n\n/**\n * A route module: constructed once at boot with its dependencies, then asked\n * for the `ApiRoute[]` it serves.\n */\nexport abstract class Route<TDeps extends RouteDependencies = RouteDependencies> {\n protected readonly deps: TDeps;\n\n constructor(deps: TDeps) {\n this.deps = deps;\n }\n\n /** Build the Mastra `apiRoutes` served by this module. */\n abstract routes(): ApiRoute[];\n}\n"],"mappings":";AA0CO,IAAe,QAAf,MAA0E;AAAA,EAC5D;AAAA,EAEnB,YAAY,MAAa;AACvB,SAAK,OAAO;AAAA,EACd;AAIF;","names":[]}
1
+ {"version":3,"file":"route.js","names":[],"sources":["../../src/routes/route.ts"],"sourcesContent":["/**\n * Base class for factory route modules.\n *\n * Route modules build Mastra `apiRoutes` from injected dependencies instead of\n * reaching into host globals. The host server (e.g. `mastracode/web`) supplies\n * the auth seam and storage domain handles at construction time, so the routes\n * stay portable and testable with fakes.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\n/**\n * The auth surface factory routes need, implemented by the host server.\n *\n * Local (no-auth) deployments implement this with a stub where `enabled()`\n * returns `false` and `tenant()` returns `undefined` — routes then take their\n * single-user local paths.\n */\nexport interface RouteAuth {\n /** Whether an auth provider is active (tenant mode). */\n enabled(): boolean;\n /**\n * Resolve (and cache) the signed-in user for the request, if any. Must be\n * called before `tenant()` so the request context is populated.\n */\n ensureUser(c: Context): Promise<unknown>;\n /** Tenant identity for the request, when signed in. */\n tenant(c: Context): { orgId?: string; userId: string } | undefined;\n /** Fail-closed check that the caller administers the given organization. */\n isOrganizationAdmin(c: Context, organizationId: string): Promise<boolean>;\n}\n\n/** Dependencies shared by every factory route module. */\nexport interface RouteDependencies {\n auth: RouteAuth;\n}\n\n/**\n * A route module: constructed once at boot with its dependencies, then asked\n * for the `ApiRoute[]` it serves.\n */\nexport abstract class Route<TDeps extends RouteDependencies = RouteDependencies> {\n protected readonly deps: TDeps;\n\n constructor(deps: TDeps) {\n this.deps = deps;\n }\n\n /** Build the Mastra `apiRoutes` served by this module. */\n abstract routes(): ApiRoute[];\n}\n"],"mappings":";;;;;AA0CA,IAAsB,QAAtB,MAAiF;CAC/E;CAEA,YAAY,MAAa;EACvB,KAAK,OAAO;CACd;AAIF"}