@mastra/factory 0.26.0-alpha.2 → 0.26.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 +74 -7
- package/dist/boards/define-board.d.ts +19 -0
- package/dist/boards/define-board.d.ts.map +1 -1
- package/dist/boards/define-board.js +41 -9
- package/dist/boards/define-board.js.map +1 -1
- package/dist/boards/project-boards.d.ts +78 -0
- package/dist/boards/project-boards.d.ts.map +1 -0
- package/dist/boards/project-boards.js +194 -0
- package/dist/boards/project-boards.js.map +1 -0
- package/dist/boards/review.js +2 -2
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/stored-board.d.ts +101 -0
- package/dist/boards/stored-board.d.ts.map +1 -0
- package/dist/boards/stored-board.js +139 -0
- package/dist/boards/stored-board.js.map +1 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +6 -3
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +6 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/factory.d.ts +15 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +49 -10
- package/dist/factory.js.map +1 -1
- package/dist/integrations/jira/binding-scope.d.ts +30 -0
- package/dist/integrations/jira/binding-scope.d.ts.map +1 -0
- package/dist/integrations/jira/binding-scope.js +50 -0
- package/dist/integrations/jira/binding-scope.js.map +1 -0
- package/dist/integrations/jira/claim.d.ts +12 -0
- package/dist/integrations/jira/claim.d.ts.map +1 -1
- package/dist/integrations/jira/claim.js +24 -1
- package/dist/integrations/jira/claim.js.map +1 -1
- package/dist/integrations/jira/intake-jql.d.ts +32 -0
- package/dist/integrations/jira/intake-jql.d.ts.map +1 -0
- package/dist/integrations/jira/intake-jql.js +57 -0
- package/dist/integrations/jira/intake-jql.js.map +1 -0
- package/dist/integrations/jira/integration.d.ts +4 -3
- package/dist/integrations/jira/integration.d.ts.map +1 -1
- package/dist/integrations/jira/integration.js +14 -21
- package/dist/integrations/jira/integration.js.map +1 -1
- package/dist/integrations/jira/routes.d.ts +3 -0
- package/dist/integrations/jira/routes.d.ts.map +1 -1
- package/dist/integrations/jira/routes.js +16 -29
- package/dist/integrations/jira/routes.js.map +1 -1
- package/dist/integrations/jira/rules.d.ts.map +1 -1
- package/dist/integrations/jira/rules.js +8 -13
- package/dist/integrations/jira/rules.js.map +1 -1
- package/dist/integrations/platform/jira/integration.d.ts +7 -1
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
- package/dist/integrations/platform/jira/integration.js +26 -24
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/jira/routes.d.ts +3 -0
- package/dist/integrations/platform/jira/routes.d.ts.map +1 -1
- package/dist/integrations/platform/jira/routes.js +17 -28
- package/dist/integrations/platform/jira/routes.js.map +1 -1
- package/dist/routes/attention-providers.d.ts +2 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +3 -2
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/attention.d.ts +2 -1
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/boards.d.ts +30 -0
- package/dist/routes/boards.d.ts.map +1 -0
- package/dist/routes/boards.js +272 -0
- package/dist/routes/boards.js.map +1 -0
- package/dist/routes/contracts.d.ts +249 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +107 -35
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/intake.d.ts +15 -10
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +109 -22
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/projects.d.ts +3 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +10 -1
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/surface.d.ts +10 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +12 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.d.ts +9 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -1
- package/dist/routes/tenant-credentials.js +24 -2
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.d.ts +3 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +64 -42
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +7 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +70 -34
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts +3 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +11 -6
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/tools.d.ts +2 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +2 -1
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +3 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +12 -12
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/storage/domains/audit/actions.d.ts +1 -0
- package/dist/storage/domains/audit/actions.d.ts.map +1 -1
- package/dist/storage/domains/audit/actions.js +6 -1
- package/dist/storage/domains/audit/actions.js.map +1 -1
- package/dist/storage/domains/boards/base.d.ts +110 -0
- package/dist/storage/domains/boards/base.d.ts.map +1 -0
- package/dist/storage/domains/boards/base.js +292 -0
- package/dist/storage/domains/boards/base.js.map +1 -0
- package/dist/storage/domains/boards/schemas.d.ts +11 -0
- package/dist/storage/domains/boards/schemas.d.ts.map +1 -0
- package/dist/storage/domains/boards/schemas.js +106 -0
- package/dist/storage/domains/boards/schemas.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +4 -1
- package/dist/storage/domains/credentials/base.d.ts.map +1 -1
- package/dist/storage/domains/credentials/base.js +5 -2
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +86 -5
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +298 -59
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +2 -1
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +15 -10
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +14 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +39 -11
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/tools/read-tools.d.ts +3 -1
- package/dist/tools/read-tools.d.ts.map +1 -1
- package/dist/tools/read-tools.js +3 -2
- package/dist/tools/read-tools.js.map +1 -1
- package/dist/tools/session-tools.d.ts +3 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +2 -1
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/sessions-state-processor.d.ts +3 -1
- package/dist/tools/sessions-state-processor.d.ts.map +1 -1
- package/dist/tools/sessions-state-processor.js +2 -1
- package/dist/tools/sessions-state-processor.js.map +1 -1
- package/dist/tools/work-item-tools.d.ts +3 -1
- package/dist/tools/work-item-tools.d.ts.map +1 -1
- package/dist/tools/work-item-tools.js +3 -2
- package/dist/tools/work-item-tools.js.map +1 -1
- package/dist/workspace.js +1 -1
- package/factory-skills/factory-plan/SKILL.md +1 -1
- package/factory-skills/factory-triage/SKILL.md +18 -7
- package/package.json +12 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/boards/base.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { FactoryStorageOps } from '@mastra/core/storage';\n\nimport type { RuleGroup } from '../../../rules/rule-group.js';\nimport {\n FACTORY_BOARD_DEFINITION_VERSIONS_SCHEMA,\n FACTORY_BOARD_PHASES_SCHEMA,\n FACTORY_BOARDS_SCHEMA,\n} from './schemas.js';\n\n/** The project a board belongs to. */\nexport interface StoredBoardScope {\n orgId: string;\n factoryProjectId: string;\n}\n\nexport type StoredPhaseKind = 'resting' | 'working' | 'terminal';\n\n/** Who runs a working phase. Factory runs every phase with its code agent. */\nexport interface StoredPhaseRunner {\n skill?: string;\n context?: string;\n instructions?: string;\n}\n\nexport interface StoredPhaseExitTool {\n id: string;\n autoApprove?: boolean;\n autoApproveWhen?: RuleGroup;\n}\n\n/** A phase as it is written. `key` is the stage id cards sit in. */\nexport interface StoredPhaseInput {\n key: string;\n name: string;\n description?: string | null;\n kind: StoredPhaseKind;\n runner?: StoredPhaseRunner | null;\n /** Unset or `null` on a resting or working phase means every other phase; `[]` means none. */\n nextPhases?: string[] | null;\n autoStart?: boolean;\n autoStartWhen?: RuleGroup | null;\n exitTool?: StoredPhaseExitTool | null;\n}\n\n/** A board as it is written. Phase order is significant: the first phase is where cards start. */\nexport interface StoredBoardInput {\n key: string;\n name: string;\n description?: string | null;\n phases: StoredPhaseInput[];\n}\n\nexport interface StoredPhaseRecord {\n key: string;\n position: number;\n name: string;\n description: string | null;\n kind: StoredPhaseKind;\n runner: StoredPhaseRunner | null;\n nextPhases: string[] | null;\n autoStart: boolean;\n autoStartWhen: RuleGroup | null;\n exitTool: StoredPhaseExitTool | null;\n}\n\nexport interface StoredBoardRecord {\n id: string;\n orgId: string;\n factoryProjectId: string;\n key: string;\n name: string;\n description: string | null;\n position: number;\n revision: number;\n createdBy: string;\n createdAt: Date;\n updatedBy: string;\n updatedAt: Date;\n phases: StoredPhaseRecord[];\n}\n\nexport interface StoredBoardSnapshot {\n version: string | null;\n boards: StoredBoardRecord[];\n}\n\nexport type StoredBoardErrorCode = 'board_exists' | 'board_not_found' | 'revision_mismatch';\n\nexport class StoredBoardError extends Error {\n override name = 'StoredBoardError';\n constructor(\n readonly code: StoredBoardErrorCode,\n message: string,\n ) {\n super(message);\n }\n}\n\ninterface BoardRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n factory_project_id: string;\n board_key: string;\n name: string;\n description: string | null;\n position: number;\n revision: number;\n created_by: string;\n created_at: Date;\n updated_by: string;\n updated_at: Date;\n}\n\ninterface PhaseRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n factory_project_id: string;\n board_key: string;\n phase_key: string;\n position: number;\n name: string;\n description: string | null;\n kind: StoredPhaseKind;\n runner: StoredPhaseRunner | null;\n next_phases: string[] | null;\n auto_start: boolean;\n auto_start_when: RuleGroup | null;\n exit_tool: StoredPhaseExitTool | null;\n}\n\nconst BOARDS = 'factory_boards';\nconst PHASES = 'factory_board_phases';\nconst VERSIONS = 'factory_board_definition_versions';\nconst MAX_WRITE_ATTEMPTS = 3;\n\nfunction scopeWhere({ orgId, factoryProjectId }: StoredBoardScope) {\n return { org_id: orgId, factory_project_id: factoryProjectId };\n}\n\nfunction toPhaseRecord(row: PhaseRow): StoredPhaseRecord {\n return {\n key: row.phase_key,\n position: row.position,\n name: row.name,\n description: row.description,\n kind: row.kind,\n runner: row.runner,\n nextPhases: row.next_phases,\n autoStart: row.auto_start,\n autoStartWhen: row.auto_start_when,\n exitTool: row.exit_tool,\n };\n}\n\nfunction toRecord(row: BoardRow, phases: PhaseRow[]): StoredBoardRecord {\n return {\n id: row.id,\n orgId: row.org_id,\n factoryProjectId: row.factory_project_id,\n key: row.board_key,\n name: row.name,\n description: row.description,\n position: row.position,\n revision: row.revision,\n createdBy: row.created_by,\n createdAt: row.created_at,\n updatedBy: row.updated_by,\n updatedAt: row.updated_at,\n phases: phases.sort((left, right) => left.position - right.position).map(toPhaseRecord),\n };\n}\n\n/** A compare-and-set inside a write lost to a concurrent writer; the write is retried from fresh reads. */\nclass ConcurrentBoardWrite extends Error {}\n\n/**\n * Each project's boards and their ordered phases. Every write runs in one transaction that also\n * replaces the project's row in `factory_board_definition_versions`, so caches on other\n * replicas can detect it.\n */\nexport class StoredBoardsStorage extends FactoryStorageDomain {\n constructor() {\n super('boards');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([\n FACTORY_BOARDS_SCHEMA,\n FACTORY_BOARD_PHASES_SCHEMA,\n FACTORY_BOARD_DEFINITION_VERSIONS_SCHEMA,\n ]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany(PHASES, {});\n await this.ops.deleteMany(BOARDS, {});\n await this.ops.deleteMany(VERSIONS, {});\n }\n\n /** The project's current definition version; null before its first board write. */\n async readVersion(scope: StoredBoardScope): Promise<string | null> {\n return readVersion(this.ops, scope);\n }\n\n /** The project's boards in order. */\n async list(scope: StoredBoardScope): Promise<StoredBoardRecord[]> {\n return readBoards(this.ops, scope);\n }\n\n /**\n * The version and the boards, read in that order: a write landing between the two reads leaves a\n * version older than the rows, so a cache keyed on it rereads on its next check instead of\n * keeping the stale rows.\n */\n async snapshot(scope: StoredBoardScope): Promise<StoredBoardSnapshot> {\n const version = await readVersion(this.ops, scope);\n return { version, boards: await this.list(scope) };\n }\n\n async get(scope: StoredBoardScope, key: string): Promise<StoredBoardRecord | null> {\n const row = await this.ops.findOne<BoardRow>(BOARDS, { ...scopeWhere(scope), board_key: key });\n if (!row) return null;\n const phases = await this.ops.findMany<PhaseRow>(PHASES, { ...scopeWhere(scope), board_key: key });\n return toRecord(row, phases);\n }\n\n /** Creates a board through the API. */\n async create(scope: StoredBoardScope, input: StoredBoardInput, actor: string): Promise<StoredBoardRecord> {\n await ensureVersionRow(this.ops, scope);\n try {\n return await this.#write(scope, async ops => {\n const rows = await ops.findMany<BoardRow>(BOARDS, scopeWhere(scope));\n if (rows.some(row => row.board_key === input.key)) {\n throw new StoredBoardError('board_exists', `Board \"${input.key}\" already exists in this project.`);\n }\n await insertBoard(ops, scope, input, { position: nextPosition(rows), actor });\n return { result: undefined, changed: true };\n }).then(async () => (await this.get(scope, input.key))!);\n } catch (error) {\n // Only the board key's index means the board exists; a lost race on another table is not that.\n if (error instanceof UniqueViolationError && error.collection === BOARDS) {\n throw new StoredBoardError('board_exists', `Board \"${input.key}\" already exists in this project.`);\n }\n throw error;\n }\n }\n\n /** Replaces a board's name, description and phases. The board key never changes. */\n async replace(\n scope: StoredBoardScope,\n key: string,\n input: Omit<StoredBoardInput, 'key'>,\n options: { expectedRevision: number; actor: string },\n ): Promise<StoredBoardRecord> {\n await this.#write(scope, async ops => {\n const row = await findBoard(ops, scope, key);\n checkRevision(row, key, options.expectedRevision);\n await writeBoard(ops, scope, row, { key, ...input }, options.actor);\n return { result: undefined, changed: true };\n });\n return (await this.get(scope, key))!;\n }\n\n /** Deletes a board and its phases. */\n async delete(scope: StoredBoardScope, key: string, options: { expectedRevision: number }): Promise<void> {\n await this.#write(scope, async ops => {\n const row = await findBoard(ops, scope, key);\n checkRevision(row, key, options.expectedRevision);\n if ((await ops.deleteMany(BOARDS, { id: row.id, revision: row.revision })) === 0)\n throw new ConcurrentBoardWrite();\n await ops.deleteMany(PHASES, { ...scopeWhere(scope), board_key: key });\n return { result: undefined, changed: true };\n });\n }\n\n /**\n * Runs `fn` in a transaction and replaces the project's version when it changed anything.\n * Lost races (a compare-and-set miss, or a concurrent write that inserted the same board or the\n * project's first version row) retry from fresh reads.\n */\n async #write<T>(\n scope: StoredBoardScope,\n fn: (ops: FactoryStorageOps) => Promise<{ result: T; changed: boolean }>,\n ): Promise<T> {\n for (let attempt = 1; ; attempt++) {\n try {\n // Read committed is enough: every row change is a compare-and-set on `revision` and every\n // insert meets the unique index. Serializable isolation would also fail writes to\n // unrelated projects that replicas run side by side.\n return await this.storage.withTransaction(async ops => {\n const { result, changed } = await fn(ops);\n if (changed) await bumpVersion(ops, scope);\n return result;\n });\n } catch (error) {\n const retryable = error instanceof ConcurrentBoardWrite || error instanceof UniqueViolationError;\n if (!retryable || attempt >= MAX_WRITE_ATTEMPTS) throw error;\n }\n }\n }\n}\n\nasync function readVersion(ops: FactoryStorageOps, scope: StoredBoardScope): Promise<string | null> {\n const row = await ops.findOne<{ version: string }>(VERSIONS, scopeWhere(scope));\n return row?.version ?? null;\n}\n\nasync function readBoards(ops: FactoryStorageOps, scope: StoredBoardScope): Promise<StoredBoardRecord[]> {\n const [rows, phases] = await Promise.all([\n ops.findMany<BoardRow>(BOARDS, scopeWhere(scope)),\n ops.findMany<PhaseRow>(PHASES, scopeWhere(scope)),\n ]);\n const byBoard = new Map<string, PhaseRow[]>();\n for (const phase of phases) {\n const list = byBoard.get(phase.board_key) ?? [];\n list.push(phase);\n byBoard.set(phase.board_key, list);\n }\n return rows\n .sort((left, right) => left.position - right.position || left.board_key.localeCompare(right.board_key))\n .map(row => toRecord(row, byBoard.get(row.board_key) ?? []));\n}\n\n/**\n * Inserts the project's version row if it has none. This runs outside the write transaction: on\n * Postgres, two transactions racing to insert it would leave the loser's transaction aborted, so\n * the version bump inside a write must always be an update.\n */\nasync function ensureVersionRow(ops: FactoryStorageOps, scope: StoredBoardScope): Promise<void> {\n if (await ops.findOne(VERSIONS, scopeWhere(scope))) return;\n try {\n await ops.insertOne(VERSIONS, { ...scopeWhere(scope), version: randomUUID(), updated_at: new Date() });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n }\n}\n\nasync function bumpVersion(ops: FactoryStorageOps, scope: StoredBoardScope): Promise<void> {\n await ops.upsertOne(VERSIONS, ['org_id', 'factory_project_id'], {\n ...scopeWhere(scope),\n version: randomUUID(),\n updated_at: new Date(),\n });\n}\n\nasync function findBoard(ops: FactoryStorageOps, scope: StoredBoardScope, key: string): Promise<BoardRow> {\n const row = await ops.findOne<BoardRow>(BOARDS, { ...scopeWhere(scope), board_key: key });\n if (!row) {\n throw new StoredBoardError('board_not_found', `Board \"${key}\" does not exist in this project.`);\n }\n return row;\n}\n\nfunction checkRevision(row: BoardRow, key: string, expectedRevision: number): void {\n if (row.revision !== expectedRevision) {\n throw new StoredBoardError(\n 'revision_mismatch',\n `Board \"${key}\" is at revision ${row.revision}, not ${expectedRevision}; reload it and try again.`,\n );\n }\n}\n\nfunction nextPosition(rows: BoardRow[]): number {\n return rows.reduce((max, row) => Math.max(max, row.position), -1) + 1;\n}\n\n/** Sets columns on a board row and raises its revision, unless another writer changed it first. */\nasync function updateRow(ops: FactoryStorageOps, row: BoardRow, set: Partial<BoardRow>): Promise<void> {\n const updated = await ops.updateMany(\n BOARDS,\n { id: row.id, revision: row.revision },\n { ...set, revision: row.revision + 1, updated_at: new Date() },\n );\n if (updated !== 1) throw new ConcurrentBoardWrite();\n}\n\nasync function insertPhases(ops: FactoryStorageOps, scope: StoredBoardScope, input: StoredBoardInput): Promise<void> {\n for (const [position, phase] of input.phases.entries()) {\n await ops.insertOne<PhaseRow>(PHASES, {\n ...scopeWhere(scope),\n board_key: input.key,\n phase_key: phase.key,\n position,\n name: phase.name,\n description: phase.description ?? null,\n kind: phase.kind,\n runner: phase.runner ?? null,\n next_phases: phase.nextPhases ?? null,\n auto_start: phase.autoStart ?? false,\n auto_start_when: phase.autoStartWhen ?? null,\n exit_tool: phase.exitTool ?? null,\n });\n }\n}\n\nasync function insertBoard(\n ops: FactoryStorageOps,\n scope: StoredBoardScope,\n input: StoredBoardInput,\n options: { position: number; actor: string },\n): Promise<void> {\n const now = new Date();\n await ops.insertOne<BoardRow>(BOARDS, {\n ...scopeWhere(scope),\n board_key: input.key,\n name: input.name,\n description: input.description ?? null,\n position: options.position,\n revision: 1,\n created_by: options.actor,\n created_at: now,\n updated_by: options.actor,\n updated_at: now,\n });\n await insertPhases(ops, scope, input);\n}\n\n/** Replaces a board's definition and phases in place, keeping its key, position and history. */\nasync function writeBoard(\n ops: FactoryStorageOps,\n scope: StoredBoardScope,\n row: BoardRow,\n input: StoredBoardInput,\n actor: string,\n): Promise<void> {\n await updateRow(ops, row, { name: input.name, description: input.description ?? null, updated_by: actor });\n await ops.deleteMany(PHASES, { ...scopeWhere(scope), board_key: row.board_key });\n await insertPhases(ops, scope, { ...input, key: row.board_key });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CollectionSchema } from '@mastra/core/storage';
|
|
2
|
+
/** A project's boards. `board_key` is the id cards reference (`work_items.board`). */
|
|
3
|
+
export declare const FACTORY_BOARDS_SCHEMA: CollectionSchema;
|
|
4
|
+
/** A board's ordered phases. `phase_key` is the stage id cards sit in. */
|
|
5
|
+
export declare const FACTORY_BOARD_PHASES_SCHEMA: CollectionSchema;
|
|
6
|
+
/**
|
|
7
|
+
* One row per project whose `version` is replaced inside every board write, so other replicas
|
|
8
|
+
* can tell their cached boards are stale with one indexed read.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FACTORY_BOARD_DEFINITION_VERSIONS_SCHEMA: CollectionSchema;
|
|
11
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/boards/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,sFAAsF;AACtF,eAAO,MAAM,qBAAqB,EAAE,gBAmBnC,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,2BAA2B,EAAE,gBA0BzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wCAAwC,EAAE,gBAUtD,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/storage/domains/boards/schemas.ts
|
|
2
|
+
/** A project's boards. `board_key` is the id cards reference (`work_items.board`). */
|
|
3
|
+
const FACTORY_BOARDS_SCHEMA = {
|
|
4
|
+
name: "factory_boards",
|
|
5
|
+
columns: {
|
|
6
|
+
id: { type: "uuid-pk" },
|
|
7
|
+
org_id: { type: "text" },
|
|
8
|
+
factory_project_id: { type: "text" },
|
|
9
|
+
board_key: { type: "text" },
|
|
10
|
+
name: { type: "text" },
|
|
11
|
+
description: {
|
|
12
|
+
type: "text",
|
|
13
|
+
nullable: true
|
|
14
|
+
},
|
|
15
|
+
position: { type: "integer" },
|
|
16
|
+
revision: { type: "integer" },
|
|
17
|
+
created_by: { type: "text" },
|
|
18
|
+
created_at: { type: "timestamp" },
|
|
19
|
+
updated_by: { type: "text" },
|
|
20
|
+
updated_at: { type: "timestamp" }
|
|
21
|
+
},
|
|
22
|
+
uniqueIndexes: [{
|
|
23
|
+
name: "factory_boards_project_key",
|
|
24
|
+
columns: [
|
|
25
|
+
"org_id",
|
|
26
|
+
"factory_project_id",
|
|
27
|
+
"board_key"
|
|
28
|
+
]
|
|
29
|
+
}],
|
|
30
|
+
indexes: [{
|
|
31
|
+
name: "factory_boards_project",
|
|
32
|
+
columns: ["org_id", "factory_project_id"]
|
|
33
|
+
}]
|
|
34
|
+
};
|
|
35
|
+
/** A board's ordered phases. `phase_key` is the stage id cards sit in. */
|
|
36
|
+
const FACTORY_BOARD_PHASES_SCHEMA = {
|
|
37
|
+
name: "factory_board_phases",
|
|
38
|
+
columns: {
|
|
39
|
+
id: { type: "uuid-pk" },
|
|
40
|
+
org_id: { type: "text" },
|
|
41
|
+
factory_project_id: { type: "text" },
|
|
42
|
+
board_key: { type: "text" },
|
|
43
|
+
phase_key: { type: "text" },
|
|
44
|
+
position: { type: "integer" },
|
|
45
|
+
name: { type: "text" },
|
|
46
|
+
description: {
|
|
47
|
+
type: "text",
|
|
48
|
+
nullable: true
|
|
49
|
+
},
|
|
50
|
+
kind: { type: "text" },
|
|
51
|
+
runner: {
|
|
52
|
+
type: "json",
|
|
53
|
+
nullable: true
|
|
54
|
+
},
|
|
55
|
+
next_phases: {
|
|
56
|
+
type: "json",
|
|
57
|
+
nullable: true
|
|
58
|
+
},
|
|
59
|
+
auto_start: {
|
|
60
|
+
type: "boolean",
|
|
61
|
+
default: false
|
|
62
|
+
},
|
|
63
|
+
auto_start_when: {
|
|
64
|
+
type: "json",
|
|
65
|
+
nullable: true
|
|
66
|
+
},
|
|
67
|
+
exit_tool: {
|
|
68
|
+
type: "json",
|
|
69
|
+
nullable: true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
uniqueIndexes: [{
|
|
73
|
+
name: "factory_board_phases_project_board_phase",
|
|
74
|
+
columns: [
|
|
75
|
+
"org_id",
|
|
76
|
+
"factory_project_id",
|
|
77
|
+
"board_key",
|
|
78
|
+
"phase_key"
|
|
79
|
+
]
|
|
80
|
+
}],
|
|
81
|
+
indexes: [{
|
|
82
|
+
name: "factory_board_phases_project",
|
|
83
|
+
columns: ["org_id", "factory_project_id"]
|
|
84
|
+
}]
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* One row per project whose `version` is replaced inside every board write, so other replicas
|
|
88
|
+
* can tell their cached boards are stale with one indexed read.
|
|
89
|
+
*/
|
|
90
|
+
const FACTORY_BOARD_DEFINITION_VERSIONS_SCHEMA = {
|
|
91
|
+
name: "factory_board_definition_versions",
|
|
92
|
+
columns: {
|
|
93
|
+
id: { type: "uuid-pk" },
|
|
94
|
+
org_id: { type: "text" },
|
|
95
|
+
factory_project_id: { type: "text" },
|
|
96
|
+
version: { type: "text" },
|
|
97
|
+
updated_at: { type: "timestamp" }
|
|
98
|
+
},
|
|
99
|
+
uniqueIndexes: [{
|
|
100
|
+
name: "factory_board_definition_versions_project",
|
|
101
|
+
columns: ["org_id", "factory_project_id"]
|
|
102
|
+
}]
|
|
103
|
+
};
|
|
104
|
+
export { FACTORY_BOARDS_SCHEMA, FACTORY_BOARD_DEFINITION_VERSIONS_SCHEMA, FACTORY_BOARD_PHASES_SCHEMA };
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","names":[],"sources":["../../../../src/storage/domains/boards/schemas.ts"],"sourcesContent":["import type { CollectionSchema } from '@mastra/core/storage';\n\n/** A project's boards. `board_key` is the id cards reference (`work_items.board`). */\nexport const FACTORY_BOARDS_SCHEMA: CollectionSchema = {\n name: 'factory_boards',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n board_key: { type: 'text' },\n name: { type: 'text' },\n description: { type: 'text', nullable: true },\n position: { type: 'integer' },\n // Rises on every write to the board or its phases; the compare-and-set token for edits.\n revision: { type: 'integer' },\n created_by: { type: 'text' },\n created_at: { type: 'timestamp' },\n updated_by: { type: 'text' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'factory_boards_project_key', columns: ['org_id', 'factory_project_id', 'board_key'] }],\n indexes: [{ name: 'factory_boards_project', columns: ['org_id', 'factory_project_id'] }],\n};\n\n/** A board's ordered phases. `phase_key` is the stage id cards sit in. */\nexport const FACTORY_BOARD_PHASES_SCHEMA: CollectionSchema = {\n name: 'factory_board_phases',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n board_key: { type: 'text' },\n phase_key: { type: 'text' },\n position: { type: 'integer' },\n name: { type: 'text' },\n description: { type: 'text', nullable: true },\n kind: { type: 'text' },\n // { skill?, context?, instructions? }; room for other runner types later.\n runner: { type: 'json', nullable: true },\n next_phases: { type: 'json', nullable: true },\n auto_start: { type: 'boolean', default: false },\n auto_start_when: { type: 'json', nullable: true },\n exit_tool: { type: 'json', nullable: true },\n },\n uniqueIndexes: [\n {\n name: 'factory_board_phases_project_board_phase',\n columns: ['org_id', 'factory_project_id', 'board_key', 'phase_key'],\n },\n ],\n indexes: [{ name: 'factory_board_phases_project', columns: ['org_id', 'factory_project_id'] }],\n};\n\n/**\n * One row per project whose `version` is replaced inside every board write, so other replicas\n * can tell their cached boards are stale with one indexed read.\n */\nexport const FACTORY_BOARD_DEFINITION_VERSIONS_SCHEMA: CollectionSchema = {\n name: 'factory_board_definition_versions',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n version: { type: 'text' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'factory_board_definition_versions_project', columns: ['org_id', 'factory_project_id'] }],\n};\n"],"mappings":";;AAGA,MAAa,wBAA0C;CACrD,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,QAAQ,EAAE,MAAM,OAAO;EACvB,oBAAoB,EAAE,MAAM,OAAO;EACnC,WAAW,EAAE,MAAM,OAAO;EAC1B,MAAM,EAAE,MAAM,OAAO;EACrB,aAAa;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC5C,UAAU,EAAE,MAAM,UAAU;EAE5B,UAAU,EAAE,MAAM,UAAU;EAC5B,YAAY,EAAE,MAAM,OAAO;EAC3B,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,OAAO;EAC3B,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,eAAe,CAAC;EAAE,MAAM;EAA8B,SAAS;GAAC;GAAU;GAAsB;EAAW;CAAE,CAAC;CAC9G,SAAS,CAAC;EAAE,MAAM;EAA0B,SAAS,CAAC,UAAU,oBAAoB;CAAE,CAAC;AACzF;;AAGA,MAAa,8BAAgD;CAC3D,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,QAAQ,EAAE,MAAM,OAAO;EACvB,oBAAoB,EAAE,MAAM,OAAO;EACnC,WAAW,EAAE,MAAM,OAAO;EAC1B,WAAW,EAAE,MAAM,OAAO;EAC1B,UAAU,EAAE,MAAM,UAAU;EAC5B,MAAM,EAAE,MAAM,OAAO;EACrB,aAAa;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC5C,MAAM,EAAE,MAAM,OAAO;EAErB,QAAQ;GAAE,MAAM;GAAQ,UAAU;EAAK;EACvC,aAAa;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC5C,YAAY;GAAE,MAAM;GAAW,SAAS;EAAM;EAC9C,iBAAiB;GAAE,MAAM;GAAQ,UAAU;EAAK;EAChD,WAAW;GAAE,MAAM;GAAQ,UAAU;EAAK;CAC5C;CACA,eAAe,CACb;EACE,MAAM;EACN,SAAS;GAAC;GAAU;GAAsB;GAAa;EAAW;CACpE,CACF;CACA,SAAS,CAAC;EAAE,MAAM;EAAgC,SAAS,CAAC,UAAU,oBAAoB;CAAE,CAAC;AAC/F;;;;;AAMA,MAAa,2CAA6D;CACxE,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,QAAQ,EAAE,MAAM,OAAO;EACvB,oBAAoB,EAAE,MAAM,OAAO;EACnC,SAAS,EAAE,MAAM,OAAO;EACxB,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,eAAe,CAAC;EAAE,MAAM;EAA6C,SAAS,CAAC,UAAU,oBAAoB;CAAE,CAAC;AAClH"}
|
|
@@ -113,8 +113,11 @@ export declare class ModelCredentialsStorage extends FactoryStorageDomain {
|
|
|
113
113
|
* skipped and the fresh credential is returned. Returns `undefined` when no
|
|
114
114
|
* OAuth row exists for the tenant. A failed `refreshFn` is recorded on the
|
|
115
115
|
* row (surfaced as `CredentialRecord.refreshFailedAt`) and rethrown.
|
|
116
|
+
*
|
|
117
|
+
* `needsRefresh` replaces the expiry check, e.g. to replace an access token
|
|
118
|
+
* the provider rejected before its recorded expiry.
|
|
116
119
|
*/
|
|
117
|
-
refreshOAuth(tenant: CredentialTenant, provider: string, refreshFn: (current: OAuthCredential) => Promise<OAuthCredential
|
|
120
|
+
refreshOAuth(tenant: CredentialTenant, provider: string, refreshFn: (current: OAuthCredential) => Promise<OAuthCredential>, needsRefresh?: (current: OAuthCredential) => boolean): Promise<OAuthCredential | undefined>;
|
|
118
121
|
/** Persist a pending login flow started by a web route. */
|
|
119
122
|
createLoginSession(input: CreateLoginSessionInput): Promise<LoginSessionRow>;
|
|
120
123
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/credentials/base.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/credentials/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -200,15 +200,18 @@ var ModelCredentialsStorage = class extends FactoryStorageDomain {
|
|
|
200
200
|
* skipped and the fresh credential is returned. Returns `undefined` when no
|
|
201
201
|
* OAuth row exists for the tenant. A failed `refreshFn` is recorded on the
|
|
202
202
|
* row (surfaced as `CredentialRecord.refreshFailedAt`) and rethrown.
|
|
203
|
+
*
|
|
204
|
+
* `needsRefresh` replaces the expiry check, e.g. to replace an access token
|
|
205
|
+
* the provider rejected before its recorded expiry.
|
|
203
206
|
*/
|
|
204
|
-
async refreshOAuth(tenant, provider, refreshFn) {
|
|
207
|
+
async refreshOAuth(tenant, provider, refreshFn, needsRefresh = (current) => isOAuthCredentialExpired(current)) {
|
|
205
208
|
let result;
|
|
206
209
|
let failure;
|
|
207
210
|
await this.#db.updateAtomic("model_provider_credentials", tenantWhere(tenant, provider), async (row) => {
|
|
208
211
|
const decrypted = await this.encryption.decrypt(row.data);
|
|
209
212
|
if (decrypted.value.type !== "oauth") return null;
|
|
210
213
|
const current = decrypted.value;
|
|
211
|
-
if (!
|
|
214
|
+
if (!needsRefresh(current)) {
|
|
212
215
|
result = current;
|
|
213
216
|
return decrypted.needsReencryption ? { data: await this.encryption.encrypt(current) } : null;
|
|
214
217
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/credentials/base.ts"],"sourcesContent":["/**\n * Model provider credentials domain — tenant-scoped OAuth tokens and API keys\n * for model providers (Anthropic/Claude Max, OpenAI Codex, GitHub Copilot,\n * xAI, plus plain API-key providers).\n *\n * Tenancy is two-level:\n * - **user-scoped** rows (`userId` present): personal plan OAuth tokens and\n * personal API keys. OAuth plan tokens are ONLY ever user-scoped — they are\n * personal subscriptions, and sharing one would bill/rate-limit a single\n * account for the whole org.\n * - **org-scoped** rows (`userId` absent): shared API keys set by an org\n * admin, inherited by every member.\n *\n * Model calls resolve exactly one scope: user or org. Deployed tenant stores\n * never fall back to the other scope or implicitly borrow server environment keys.\n *\n * Secrets posture: credentials are stored server-side only and never returned\n * to the client (same as `linear_connections.access_token`). Encryption at\n * rest is out of scope, matching the existing posture.\n *\n * The domain also owns `oauth_login_sessions`: pending web login flows\n * (paste-code PKCE verifiers, device-code poll state) persisted server-side so\n * any replica can complete or poll a flow started on another.\n */\n\nimport type { AuthCredential, OAuthCredential } from '@mastra/code-sdk/auth/types';\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\nimport { createPlaintextFactorySecretEncryption } from '../../../secret-encryption.js';\nimport type { FactorySecretEncryption } from '../../../secret-encryption.js';\n\n/** Owning tenant of a credential row. `userId` absent = org-scoped row. */\nexport interface CredentialTenant {\n orgId: string;\n userId?: string;\n}\n\n/** Which tenancy level a credential row lives at. */\nexport type CredentialScope = 'user' | 'org';\n\n/** One stored credential, annotated with its scope. */\nexport interface CredentialRecord {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n updatedAt: Date;\n /** When the last OAuth token refresh failed; cleared by the next successful refresh or a new sign-in. */\n refreshFailedAt?: Date;\n}\n\n/** Result of per-caller resolution: the winning credential and its scope. */\nexport interface ResolvedCredential {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n}\n\n/** Kind of pending web login flow a session row tracks. */\nexport type LoginSessionKind = 'paste-code' | 'device-code';\n\n/** Scope the completed credential should be written to. */\nexport type LoginCredentialScope = 'user' | 'org';\n\n/** One pending OAuth login flow, persisted so any replica can continue it. */\nexport interface LoginSessionRow {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n /** Where the completed credential lands; absent rows default to `'user'`. */\n credentialScope?: LoginCredentialScope;\n /** Serialized flow state (PKCE verifier, device-code pending state, ...). */\n pending: Record<string, unknown>;\n expiresAt: Date;\n /** Earliest time the next upstream poll is allowed (device-code flows). */\n nextPollAt: Date | null;\n createdAt: Date;\n}\n\nexport interface CreateLoginSessionInput {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n credentialScope?: LoginCredentialScope;\n pending: Record<string, unknown>;\n expiresAt: Date;\n nextPollAt?: Date | null;\n}\n\n/** Mirror of `AuthStorage.getApiKey()`'s expiry check. */\nexport function isOAuthCredentialExpired(credential: OAuthCredential, now = Date.now()): boolean {\n return now >= credential.expires;\n}\n\nexport const MODEL_CREDENTIALS_SCHEMA: CollectionSchema = {\n name: 'model_provider_credentials',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text', nullable: true },\n provider: { type: 'text' },\n type: { type: 'text' },\n data: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n refresh_failed_at: { type: 'timestamp', nullable: true },\n },\n uniqueIndexes: [\n // Scope is encoded in `user_id` nullability (NULL = org-scoped shared\n // row), enforced by two partial unique indexes.\n {\n name: 'model_provider_credentials_user_unique',\n columns: ['org_id', 'user_id', 'provider'],\n whereNotNull: 'user_id',\n },\n { name: 'model_provider_credentials_org_unique', columns: ['org_id', 'provider'], whereNull: 'user_id' },\n ],\n};\n\nexport const OAUTH_LOGIN_SESSIONS_SCHEMA: CollectionSchema = {\n name: 'oauth_login_sessions',\n columns: {\n session_id: { type: 'text', primaryKey: true },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n provider: { type: 'text' },\n kind: { type: 'text' },\n credential_scope: { type: 'text', nullable: true },\n pending: { type: 'json' },\n expires_at: { type: 'timestamp' },\n next_poll_at: { type: 'timestamp', nullable: true },\n created_at: { type: 'timestamp' },\n },\n};\n\n/** Column shape of one `model_provider_credentials` row as returned by ops. */\ninterface CredentialDbRow extends Record<string, unknown> {\n id: string;\n provider: string;\n user_id: string | null;\n data: unknown;\n updated_at: Date;\n refresh_failed_at: Date | null;\n}\n\n/** Column shape of one `oauth_login_sessions` row as returned by ops. */\ninterface LoginSessionDbRow extends Record<string, unknown> {\n session_id: string;\n org_id: string;\n user_id: string;\n provider: string;\n kind: LoginSessionKind;\n credential_scope: LoginCredentialScope | null;\n pending: Record<string, unknown>;\n expires_at: Date;\n next_poll_at: Date | null;\n created_at: Date;\n}\n\nfunction toSessionRow(db: LoginSessionDbRow): LoginSessionRow {\n return {\n sessionId: db.session_id,\n orgId: db.org_id,\n userId: db.user_id,\n provider: db.provider,\n kind: db.kind,\n ...(db.credential_scope ? { credentialScope: db.credential_scope } : {}),\n pending: db.pending,\n expiresAt: db.expires_at,\n nextPollAt: db.next_poll_at,\n createdAt: db.created_at,\n };\n}\n\n/** Filter selecting exactly the tenant's row for a provider (`null` = org row). */\nfunction tenantWhere(tenant: CredentialTenant, provider: string): CollectionWhere {\n return { org_id: tenant.orgId, provider, user_id: tenant.userId ?? null };\n}\n\n/**\n * Model-credentials storage, written once against the generic\n * `FactoryStorageOps` surface. `refreshOAuth()` and `claimLoginSession()`\n * ride `updateAtomic` so concurrent replicas serialize instead of\n * invalidating each other's rotating tokens / double-claiming a flow.\n */\nexport class ModelCredentialsStorage extends FactoryStorageDomain {\n constructor(private readonly encryption: FactorySecretEncryption = createPlaintextFactorySecretEncryption()) {\n super('model-credentials');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MODEL_CREDENTIALS_SCHEMA, OAUTH_LOGIN_SESSIONS_SCHEMA]);\n const rows = await this.ops.findMany<CredentialDbRow>('model_provider_credentials', {});\n await Promise.all(rows.map(row => this.#migrateCredential(row.id)));\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('oauth_login_sessions', {});\n await this.ops.deleteMany('model_provider_credentials', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async #decryptCredential(value: unknown): Promise<AuthCredential> {\n return (await this.encryption.decrypt<AuthCredential>(value)).value;\n }\n\n async #toRecord(row: CredentialDbRow, scope: CredentialScope): Promise<CredentialRecord> {\n return {\n provider: row.provider,\n scope,\n credential: await this.#decryptCredential(row.data),\n updatedAt: row.updated_at,\n refreshFailedAt: row.refresh_failed_at ?? undefined,\n };\n }\n\n async #migrateCredential(id: string): Promise<void> {\n await this.#db.updateAtomic<CredentialDbRow>('model_provider_credentials', { id }, async row => {\n const decrypted = await this.encryption.decrypt<AuthCredential>(row.data);\n if (!decrypted.needsReencryption) return null;\n return { data: await this.encryption.encrypt(decrypted.value) };\n });\n }\n\n /** Read the tenant's credential for a provider at exactly that scope. */\n async getCredential(tenant: CredentialTenant, provider: string): Promise<AuthCredential | undefined> {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', tenantWhere(tenant, provider));\n return row ? this.#decryptCredential(row.data) : undefined;\n }\n\n /** Upsert the tenant's credential (`created_at` is preserved on update). */\n async setCredential(tenant: CredentialTenant, provider: string, credential: AuthCredential): Promise<void> {\n const where = tenantWhere(tenant, provider);\n const encrypted = await this.encryption.encrypt(credential);\n const update = async () =>\n this.#db.updateMany('model_provider_credentials', where, {\n type: credential.type,\n data: encrypted,\n updated_at: new Date(),\n refresh_failed_at: null,\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await this.#db.insertOne<CredentialDbRow>('model_provider_credentials', {\n org_id: tenant.orgId,\n user_id: tenant.userId ?? null,\n provider,\n type: credential.type,\n data: encrypted,\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n }\n\n /** Delete the tenant's credential at exactly that scope. True when a row was removed. */\n async removeCredential(tenant: CredentialTenant, provider: string): Promise<boolean> {\n const deleted = await this.#db.deleteMany('model_provider_credentials', tenantWhere(tenant, provider));\n return deleted > 0;\n }\n\n /** All credentials visible to a user: their own rows plus the org's shared rows. */\n async listCredentials(orgId: string, userId: string): Promise<CredentialRecord[]> {\n // SQL `IN` never matches NULL, so the org rows need their own query.\n const [userRows, orgRows] = await Promise.all([\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: userId }),\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: null }),\n ]);\n return Promise.all([\n ...userRows.map(row => this.#toRecord(row, 'user')),\n ...orgRows.map(row => this.#toRecord(row, 'org')),\n ]);\n }\n\n /** Resolve a provider credential from exactly one tenancy scope. */\n async resolveCredential(\n orgId: string,\n userId: string,\n provider: string,\n scope: CredentialScope,\n ): Promise<ResolvedCredential | undefined> {\n const tenant = scope === 'org' ? { orgId } : { orgId, userId };\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', tenantWhere(tenant, provider));\n return row ? { provider: row.provider, scope, credential: await this.#decryptCredential(row.data) } : undefined;\n }\n\n /**\n * Refresh the tenant's OAuth credential under the backend's atomic\n * read-modify-write so concurrent replicas don't invalidate each other's\n * rotating refresh tokens. The expiry is re-checked under the lock —\n * another replica may have refreshed already, in which case `refreshFn` is\n * skipped and the fresh credential is returned. Returns `undefined` when no\n * OAuth row exists for the tenant. A failed `refreshFn` is recorded on the\n * row (surfaced as `CredentialRecord.refreshFailedAt`) and rethrown.\n */\n async refreshOAuth(\n tenant: CredentialTenant,\n provider: string,\n refreshFn: (current: OAuthCredential) => Promise<OAuthCredential>,\n ): Promise<OAuthCredential | undefined> {\n let result: OAuthCredential | undefined;\n let failure: { error: unknown } | undefined;\n await this.#db.updateAtomic<CredentialDbRow>(\n 'model_provider_credentials',\n tenantWhere(tenant, provider),\n async row => {\n const decrypted = await this.encryption.decrypt<AuthCredential>(row.data);\n if (decrypted.value.type !== 'oauth') return null;\n const current = decrypted.value;\n // Re-check under the lock: another replica may have refreshed while we waited.\n if (!isOAuthCredentialExpired(current)) {\n result = current;\n return decrypted.needsReencryption ? { data: await this.encryption.encrypt(current) } : null;\n }\n try {\n result = await refreshFn(current);\n } catch (error) {\n failure = { error };\n return { refresh_failed_at: new Date() };\n }\n return { data: await this.encryption.encrypt(result), updated_at: new Date(), refresh_failed_at: null };\n },\n );\n if (failure) throw failure.error;\n return result;\n }\n\n /** Persist a pending login flow started by a web route. */\n async createLoginSession(input: CreateLoginSessionInput): Promise<LoginSessionRow> {\n const inserted = await this.#db.insertOne<LoginSessionDbRow>('oauth_login_sessions', {\n session_id: input.sessionId,\n org_id: input.orgId,\n user_id: input.userId,\n provider: input.provider,\n kind: input.kind,\n credential_scope: input.credentialScope ?? null,\n pending: input.pending,\n expires_at: input.expiresAt,\n next_poll_at: input.nextPollAt ?? null,\n created_at: new Date(),\n });\n return toSessionRow(inserted);\n }\n\n /**\n * Read a pending login flow. An expired session is deleted on read (TTL\n * cleanup) and reported as absent.\n */\n async getLoginSession(sessionId: string): Promise<LoginSessionRow | undefined> {\n const row = await this.#db.findOne<LoginSessionDbRow>('oauth_login_sessions', { session_id: sessionId });\n if (!row) return undefined;\n if (row.expires_at.getTime() <= Date.now()) {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n return undefined;\n }\n return toSessionRow(row);\n }\n\n /**\n * Atomically claim a due login session for one upstream completion/poll attempt.\n * Returns undefined when the session is missing, expired, owned by another\n * tenant/provider, or already claimed by a concurrent request.\n */\n async claimLoginSession(\n sessionId: string,\n owner: Pick<LoginSessionRow, 'orgId' | 'userId' | 'provider' | 'kind'>,\n ): Promise<LoginSessionRow | undefined> {\n const now = Date.now();\n let claimed = false;\n const updated = await this.#db.updateAtomic<LoginSessionDbRow>(\n 'oauth_login_sessions',\n { session_id: sessionId },\n row => {\n if (\n row.org_id !== owner.orgId ||\n row.user_id !== owner.userId ||\n row.provider !== owner.provider ||\n row.kind !== owner.kind\n ) {\n return null;\n }\n if (row.expires_at.getTime() <= now) return null;\n if (row.next_poll_at && row.next_poll_at.getTime() > now) return null;\n claimed = true;\n // Park the next poll at expiry so a concurrent claim loses until the\n // claimer either touches the session forward or deletes it.\n return { next_poll_at: row.expires_at };\n },\n );\n return claimed && updated ? toSessionRow(updated) : undefined;\n }\n\n /** Update a pending flow's serialized state and/or next allowed poll time. */\n async touchLoginSession(\n sessionId: string,\n updates: { pending?: Record<string, unknown>; nextPollAt?: Date | null },\n ): Promise<void> {\n const set: Record<string, unknown> = {};\n if (updates.pending !== undefined) set.pending = updates.pending;\n if (updates.nextPollAt !== undefined) set.next_poll_at = updates.nextPollAt;\n if (Object.keys(set).length === 0) return;\n await this.#db.updateMany('oauth_login_sessions', { session_id: sessionId }, set);\n }\n\n /** Remove a pending login flow (completed, cancelled, or failed). */\n async deleteLoginSession(sessionId: string): Promise<void> {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/credentials/base.ts"],"sourcesContent":["/**\n * Model provider credentials domain — tenant-scoped OAuth tokens and API keys\n * for model providers (Anthropic/Claude Max, OpenAI Codex, GitHub Copilot,\n * xAI, plus plain API-key providers).\n *\n * Tenancy is two-level:\n * - **user-scoped** rows (`userId` present): personal plan OAuth tokens and\n * personal API keys. OAuth plan tokens are ONLY ever user-scoped — they are\n * personal subscriptions, and sharing one would bill/rate-limit a single\n * account for the whole org.\n * - **org-scoped** rows (`userId` absent): shared API keys set by an org\n * admin, inherited by every member.\n *\n * Model calls resolve exactly one scope: user or org. Deployed tenant stores\n * never fall back to the other scope or implicitly borrow server environment keys.\n *\n * Secrets posture: credentials are stored server-side only and never returned\n * to the client (same as `linear_connections.access_token`). Encryption at\n * rest is out of scope, matching the existing posture.\n *\n * The domain also owns `oauth_login_sessions`: pending web login flows\n * (paste-code PKCE verifiers, device-code poll state) persisted server-side so\n * any replica can complete or poll a flow started on another.\n */\n\nimport type { AuthCredential, OAuthCredential } from '@mastra/code-sdk/auth/types';\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\nimport { createPlaintextFactorySecretEncryption } from '../../../secret-encryption.js';\nimport type { FactorySecretEncryption } from '../../../secret-encryption.js';\n\n/** Owning tenant of a credential row. `userId` absent = org-scoped row. */\nexport interface CredentialTenant {\n orgId: string;\n userId?: string;\n}\n\n/** Which tenancy level a credential row lives at. */\nexport type CredentialScope = 'user' | 'org';\n\n/** One stored credential, annotated with its scope. */\nexport interface CredentialRecord {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n updatedAt: Date;\n /** When the last OAuth token refresh failed; cleared by the next successful refresh or a new sign-in. */\n refreshFailedAt?: Date;\n}\n\n/** Result of per-caller resolution: the winning credential and its scope. */\nexport interface ResolvedCredential {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n}\n\n/** Kind of pending web login flow a session row tracks. */\nexport type LoginSessionKind = 'paste-code' | 'device-code';\n\n/** Scope the completed credential should be written to. */\nexport type LoginCredentialScope = 'user' | 'org';\n\n/** One pending OAuth login flow, persisted so any replica can continue it. */\nexport interface LoginSessionRow {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n /** Where the completed credential lands; absent rows default to `'user'`. */\n credentialScope?: LoginCredentialScope;\n /** Serialized flow state (PKCE verifier, device-code pending state, ...). */\n pending: Record<string, unknown>;\n expiresAt: Date;\n /** Earliest time the next upstream poll is allowed (device-code flows). */\n nextPollAt: Date | null;\n createdAt: Date;\n}\n\nexport interface CreateLoginSessionInput {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n credentialScope?: LoginCredentialScope;\n pending: Record<string, unknown>;\n expiresAt: Date;\n nextPollAt?: Date | null;\n}\n\n/** Mirror of `AuthStorage.getApiKey()`'s expiry check. */\nexport function isOAuthCredentialExpired(credential: OAuthCredential, now = Date.now()): boolean {\n return now >= credential.expires;\n}\n\nexport const MODEL_CREDENTIALS_SCHEMA: CollectionSchema = {\n name: 'model_provider_credentials',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text', nullable: true },\n provider: { type: 'text' },\n type: { type: 'text' },\n data: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n refresh_failed_at: { type: 'timestamp', nullable: true },\n },\n uniqueIndexes: [\n // Scope is encoded in `user_id` nullability (NULL = org-scoped shared\n // row), enforced by two partial unique indexes.\n {\n name: 'model_provider_credentials_user_unique',\n columns: ['org_id', 'user_id', 'provider'],\n whereNotNull: 'user_id',\n },\n { name: 'model_provider_credentials_org_unique', columns: ['org_id', 'provider'], whereNull: 'user_id' },\n ],\n};\n\nexport const OAUTH_LOGIN_SESSIONS_SCHEMA: CollectionSchema = {\n name: 'oauth_login_sessions',\n columns: {\n session_id: { type: 'text', primaryKey: true },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n provider: { type: 'text' },\n kind: { type: 'text' },\n credential_scope: { type: 'text', nullable: true },\n pending: { type: 'json' },\n expires_at: { type: 'timestamp' },\n next_poll_at: { type: 'timestamp', nullable: true },\n created_at: { type: 'timestamp' },\n },\n};\n\n/** Column shape of one `model_provider_credentials` row as returned by ops. */\ninterface CredentialDbRow extends Record<string, unknown> {\n id: string;\n provider: string;\n user_id: string | null;\n data: unknown;\n updated_at: Date;\n refresh_failed_at: Date | null;\n}\n\n/** Column shape of one `oauth_login_sessions` row as returned by ops. */\ninterface LoginSessionDbRow extends Record<string, unknown> {\n session_id: string;\n org_id: string;\n user_id: string;\n provider: string;\n kind: LoginSessionKind;\n credential_scope: LoginCredentialScope | null;\n pending: Record<string, unknown>;\n expires_at: Date;\n next_poll_at: Date | null;\n created_at: Date;\n}\n\nfunction toSessionRow(db: LoginSessionDbRow): LoginSessionRow {\n return {\n sessionId: db.session_id,\n orgId: db.org_id,\n userId: db.user_id,\n provider: db.provider,\n kind: db.kind,\n ...(db.credential_scope ? { credentialScope: db.credential_scope } : {}),\n pending: db.pending,\n expiresAt: db.expires_at,\n nextPollAt: db.next_poll_at,\n createdAt: db.created_at,\n };\n}\n\n/** Filter selecting exactly the tenant's row for a provider (`null` = org row). */\nfunction tenantWhere(tenant: CredentialTenant, provider: string): CollectionWhere {\n return { org_id: tenant.orgId, provider, user_id: tenant.userId ?? null };\n}\n\n/**\n * Model-credentials storage, written once against the generic\n * `FactoryStorageOps` surface. `refreshOAuth()` and `claimLoginSession()`\n * ride `updateAtomic` so concurrent replicas serialize instead of\n * invalidating each other's rotating tokens / double-claiming a flow.\n */\nexport class ModelCredentialsStorage extends FactoryStorageDomain {\n constructor(private readonly encryption: FactorySecretEncryption = createPlaintextFactorySecretEncryption()) {\n super('model-credentials');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MODEL_CREDENTIALS_SCHEMA, OAUTH_LOGIN_SESSIONS_SCHEMA]);\n const rows = await this.ops.findMany<CredentialDbRow>('model_provider_credentials', {});\n await Promise.all(rows.map(row => this.#migrateCredential(row.id)));\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('oauth_login_sessions', {});\n await this.ops.deleteMany('model_provider_credentials', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async #decryptCredential(value: unknown): Promise<AuthCredential> {\n return (await this.encryption.decrypt<AuthCredential>(value)).value;\n }\n\n async #toRecord(row: CredentialDbRow, scope: CredentialScope): Promise<CredentialRecord> {\n return {\n provider: row.provider,\n scope,\n credential: await this.#decryptCredential(row.data),\n updatedAt: row.updated_at,\n refreshFailedAt: row.refresh_failed_at ?? undefined,\n };\n }\n\n async #migrateCredential(id: string): Promise<void> {\n await this.#db.updateAtomic<CredentialDbRow>('model_provider_credentials', { id }, async row => {\n const decrypted = await this.encryption.decrypt<AuthCredential>(row.data);\n if (!decrypted.needsReencryption) return null;\n return { data: await this.encryption.encrypt(decrypted.value) };\n });\n }\n\n /** Read the tenant's credential for a provider at exactly that scope. */\n async getCredential(tenant: CredentialTenant, provider: string): Promise<AuthCredential | undefined> {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', tenantWhere(tenant, provider));\n return row ? this.#decryptCredential(row.data) : undefined;\n }\n\n /** Upsert the tenant's credential (`created_at` is preserved on update). */\n async setCredential(tenant: CredentialTenant, provider: string, credential: AuthCredential): Promise<void> {\n const where = tenantWhere(tenant, provider);\n const encrypted = await this.encryption.encrypt(credential);\n const update = async () =>\n this.#db.updateMany('model_provider_credentials', where, {\n type: credential.type,\n data: encrypted,\n updated_at: new Date(),\n refresh_failed_at: null,\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await this.#db.insertOne<CredentialDbRow>('model_provider_credentials', {\n org_id: tenant.orgId,\n user_id: tenant.userId ?? null,\n provider,\n type: credential.type,\n data: encrypted,\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n }\n\n /** Delete the tenant's credential at exactly that scope. True when a row was removed. */\n async removeCredential(tenant: CredentialTenant, provider: string): Promise<boolean> {\n const deleted = await this.#db.deleteMany('model_provider_credentials', tenantWhere(tenant, provider));\n return deleted > 0;\n }\n\n /** All credentials visible to a user: their own rows plus the org's shared rows. */\n async listCredentials(orgId: string, userId: string): Promise<CredentialRecord[]> {\n // SQL `IN` never matches NULL, so the org rows need their own query.\n const [userRows, orgRows] = await Promise.all([\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: userId }),\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: null }),\n ]);\n return Promise.all([\n ...userRows.map(row => this.#toRecord(row, 'user')),\n ...orgRows.map(row => this.#toRecord(row, 'org')),\n ]);\n }\n\n /** Resolve a provider credential from exactly one tenancy scope. */\n async resolveCredential(\n orgId: string,\n userId: string,\n provider: string,\n scope: CredentialScope,\n ): Promise<ResolvedCredential | undefined> {\n const tenant = scope === 'org' ? { orgId } : { orgId, userId };\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', tenantWhere(tenant, provider));\n return row ? { provider: row.provider, scope, credential: await this.#decryptCredential(row.data) } : undefined;\n }\n\n /**\n * Refresh the tenant's OAuth credential under the backend's atomic\n * read-modify-write so concurrent replicas don't invalidate each other's\n * rotating refresh tokens. The expiry is re-checked under the lock —\n * another replica may have refreshed already, in which case `refreshFn` is\n * skipped and the fresh credential is returned. Returns `undefined` when no\n * OAuth row exists for the tenant. A failed `refreshFn` is recorded on the\n * row (surfaced as `CredentialRecord.refreshFailedAt`) and rethrown.\n *\n * `needsRefresh` replaces the expiry check, e.g. to replace an access token\n * the provider rejected before its recorded expiry.\n */\n async refreshOAuth(\n tenant: CredentialTenant,\n provider: string,\n refreshFn: (current: OAuthCredential) => Promise<OAuthCredential>,\n needsRefresh: (current: OAuthCredential) => boolean = current => isOAuthCredentialExpired(current),\n ): Promise<OAuthCredential | undefined> {\n let result: OAuthCredential | undefined;\n let failure: { error: unknown } | undefined;\n await this.#db.updateAtomic<CredentialDbRow>(\n 'model_provider_credentials',\n tenantWhere(tenant, provider),\n async row => {\n const decrypted = await this.encryption.decrypt<AuthCredential>(row.data);\n if (decrypted.value.type !== 'oauth') return null;\n const current = decrypted.value;\n // Re-check under the lock: another replica may have refreshed while we waited.\n if (!needsRefresh(current)) {\n result = current;\n return decrypted.needsReencryption ? { data: await this.encryption.encrypt(current) } : null;\n }\n try {\n result = await refreshFn(current);\n } catch (error) {\n failure = { error };\n return { refresh_failed_at: new Date() };\n }\n return { data: await this.encryption.encrypt(result), updated_at: new Date(), refresh_failed_at: null };\n },\n );\n if (failure) throw failure.error;\n return result;\n }\n\n /** Persist a pending login flow started by a web route. */\n async createLoginSession(input: CreateLoginSessionInput): Promise<LoginSessionRow> {\n const inserted = await this.#db.insertOne<LoginSessionDbRow>('oauth_login_sessions', {\n session_id: input.sessionId,\n org_id: input.orgId,\n user_id: input.userId,\n provider: input.provider,\n kind: input.kind,\n credential_scope: input.credentialScope ?? null,\n pending: input.pending,\n expires_at: input.expiresAt,\n next_poll_at: input.nextPollAt ?? null,\n created_at: new Date(),\n });\n return toSessionRow(inserted);\n }\n\n /**\n * Read a pending login flow. An expired session is deleted on read (TTL\n * cleanup) and reported as absent.\n */\n async getLoginSession(sessionId: string): Promise<LoginSessionRow | undefined> {\n const row = await this.#db.findOne<LoginSessionDbRow>('oauth_login_sessions', { session_id: sessionId });\n if (!row) return undefined;\n if (row.expires_at.getTime() <= Date.now()) {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n return undefined;\n }\n return toSessionRow(row);\n }\n\n /**\n * Atomically claim a due login session for one upstream completion/poll attempt.\n * Returns undefined when the session is missing, expired, owned by another\n * tenant/provider, or already claimed by a concurrent request.\n */\n async claimLoginSession(\n sessionId: string,\n owner: Pick<LoginSessionRow, 'orgId' | 'userId' | 'provider' | 'kind'>,\n ): Promise<LoginSessionRow | undefined> {\n const now = Date.now();\n let claimed = false;\n const updated = await this.#db.updateAtomic<LoginSessionDbRow>(\n 'oauth_login_sessions',\n { session_id: sessionId },\n row => {\n if (\n row.org_id !== owner.orgId ||\n row.user_id !== owner.userId ||\n row.provider !== owner.provider ||\n row.kind !== owner.kind\n ) {\n return null;\n }\n if (row.expires_at.getTime() <= now) return null;\n if (row.next_poll_at && row.next_poll_at.getTime() > now) return null;\n claimed = true;\n // Park the next poll at expiry so a concurrent claim loses until the\n // claimer either touches the session forward or deletes it.\n return { next_poll_at: row.expires_at };\n },\n );\n return claimed && updated ? toSessionRow(updated) : undefined;\n }\n\n /** Update a pending flow's serialized state and/or next allowed poll time. */\n async touchLoginSession(\n sessionId: string,\n updates: { pending?: Record<string, unknown>; nextPollAt?: Date | null },\n ): Promise<void> {\n const set: Record<string, unknown> = {};\n if (updates.pending !== undefined) set.pending = updates.pending;\n if (updates.nextPollAt !== undefined) set.next_poll_at = updates.nextPollAt;\n if (Object.keys(set).length === 0) return;\n await this.#db.updateMany('oauth_login_sessions', { session_id: sessionId }, set);\n }\n\n /** Remove a pending login flow (completed, cancelled, or failed). */\n async deleteLoginSession(sessionId: string): Promise<void> {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n }\n}\n"],"mappings":"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"}
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* every member reads the same one.
|
|
8
8
|
*/
|
|
9
9
|
import { FactoryStorageDomain } from '@mastra/core/storage';
|
|
10
|
-
import type { CollectionSchema } from '@mastra/core/storage';
|
|
10
|
+
import type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';
|
|
11
|
+
export declare class FactoryProjectNotFoundError extends Error {
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
11
14
|
export interface IntakeSelection {
|
|
12
15
|
enabled: boolean;
|
|
13
16
|
/** Provider-owned source ids; `null` means nothing is selected. */
|
|
@@ -28,15 +31,52 @@ export declare function mergeIntakeSelections(configs: readonly IntakeConfig[]):
|
|
|
28
31
|
* ingested item should land. GitHub items are naturally scoped by their linked
|
|
29
32
|
* repository; providers without that link (Linear) need this explicit binding
|
|
30
33
|
* so viewing one project's board cannot materialize another project's items.
|
|
34
|
+
*
|
|
35
|
+
* Frozen: its unique index allows one binding per source, and indexes cannot be
|
|
36
|
+
* narrowed, so bindings moved to {@link INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA}. Rows
|
|
37
|
+
* are copied once at boot and never written again; the collection stays
|
|
38
|
+
* registered so an earlier build that is rolled back to still finds them.
|
|
31
39
|
*/
|
|
32
40
|
export declare const INTAKE_SOURCE_BINDINGS_SCHEMA: CollectionSchema;
|
|
41
|
+
/**
|
|
42
|
+
* Binds an intake source to a Factory project, one row per (source, Factory
|
|
43
|
+
* project). Most integrations keep one binding per source and rebinding moves
|
|
44
|
+
* it; integrations with `Intake.bindingLabelFilters` let several Factory
|
|
45
|
+
* projects share a source, each taking the issues carrying its own labels.
|
|
46
|
+
*/
|
|
47
|
+
export declare const INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA: CollectionSchema;
|
|
48
|
+
/** One-time data migrations that have run, so none re-runs over rows users have changed since. */
|
|
49
|
+
export declare const INTAKE_DATA_MIGRATIONS_SCHEMA: CollectionSchema;
|
|
33
50
|
export interface IntakeSourceBinding {
|
|
34
51
|
integrationId: string;
|
|
35
52
|
sourceId: string;
|
|
36
53
|
factoryProjectId: string;
|
|
37
54
|
/** Installed board the source feeds, or `null` for legacy source-type routing. */
|
|
38
55
|
board: string | null;
|
|
56
|
+
/** Labels whose issues this binding takes; `null` takes every issue no other binding's labels claim. */
|
|
57
|
+
labels: string[] | null;
|
|
58
|
+
createdAt: Date;
|
|
39
59
|
}
|
|
60
|
+
/** Another Factory project's binding that already takes some of the requested issues. */
|
|
61
|
+
export interface IntakeBindingConflict {
|
|
62
|
+
factoryProjectId: string;
|
|
63
|
+
/** The overlapping labels, or `null` when both bindings take all unclaimed issues. */
|
|
64
|
+
labels: string[] | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The slice of a shared source's issues `binding` takes, given every binding
|
|
68
|
+
* on that source (`siblings` may include `binding` itself). Label bindings beat
|
|
69
|
+
* the all-issues binding, and an issue carrying labels of two label bindings
|
|
70
|
+
* goes to the older one.
|
|
71
|
+
*/
|
|
72
|
+
export declare function bindingLabelSlice(binding: Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>, siblings: readonly Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>[]): {
|
|
73
|
+
include: string[] | null;
|
|
74
|
+
exclude: string[];
|
|
75
|
+
};
|
|
76
|
+
/** Whether an issue carrying `labels` falls in `binding`'s slice of its source. */
|
|
77
|
+
export declare function bindingTakesIssue(binding: Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>, siblings: readonly Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>[], labels: readonly string[]): boolean;
|
|
78
|
+
/** Trim and de-duplicate case-insensitively, keeping the first spelling the user typed. */
|
|
79
|
+
export declare function normalizeBindingLabels(labels: readonly string[]): string[];
|
|
40
80
|
/**
|
|
41
81
|
* Routes items carrying a label to an installed board, per Factory project.
|
|
42
82
|
*
|
|
@@ -82,7 +122,7 @@ export declare class IntakeStorage extends FactoryStorageDomain {
|
|
|
82
122
|
orgId: string;
|
|
83
123
|
config: IntakeConfig;
|
|
84
124
|
}): Promise<IntakeConfig>;
|
|
85
|
-
/** Every source binding in the org, optionally narrowed to one integration. */
|
|
125
|
+
/** Every source binding in the org, optionally narrowed to one integration, oldest first. */
|
|
86
126
|
listBindings({ orgId, integrationId, }: {
|
|
87
127
|
orgId: string;
|
|
88
128
|
integrationId?: string;
|
|
@@ -93,17 +133,40 @@ export declare class IntakeStorage extends FactoryStorageDomain {
|
|
|
93
133
|
integrationId: string;
|
|
94
134
|
factoryProjectId: string;
|
|
95
135
|
}): Promise<string[]>;
|
|
96
|
-
/**
|
|
97
|
-
|
|
136
|
+
/**
|
|
137
|
+
* One Factory project's binding of a source, or `null` when it has none.
|
|
138
|
+
* Without `factoryProjectId`, the source's oldest binding — for integrations
|
|
139
|
+
* that keep one binding per source.
|
|
140
|
+
*/
|
|
141
|
+
getBinding({ orgId, integrationId, sourceId, factoryProjectId, }: {
|
|
98
142
|
orgId: string;
|
|
99
143
|
integrationId: string;
|
|
100
144
|
sourceId: string;
|
|
145
|
+
factoryProjectId?: string;
|
|
101
146
|
}): Promise<IntakeSourceBinding | null>;
|
|
102
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Remove one Factory project's binding of a source, or — without
|
|
149
|
+
* `factoryProjectId` — every binding of it. Returns the (oldest) removed
|
|
150
|
+
* binding, or `null` when there was none.
|
|
151
|
+
*/
|
|
152
|
+
clearBinding({ orgId, integrationId, sourceId, factoryProjectId, }: {
|
|
103
153
|
orgId: string;
|
|
104
154
|
integrationId: string;
|
|
105
155
|
sourceId: string;
|
|
156
|
+
factoryProjectId?: string;
|
|
106
157
|
}): Promise<IntakeSourceBinding | null>;
|
|
158
|
+
/**
|
|
159
|
+
* Release every source a Factory project was bound to, so the others can take
|
|
160
|
+
* them. Pass the transaction deleting the project, so no binding lands in between.
|
|
161
|
+
*/
|
|
162
|
+
clearProjectBindings({ orgId, factoryProjectId }: {
|
|
163
|
+
orgId: string;
|
|
164
|
+
factoryProjectId: string;
|
|
165
|
+
}, ops?: FactoryStorageOps): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Bind a source to one Factory project, moving it away from any other: the
|
|
168
|
+
* source keeps a single binding, which takes all of its issues.
|
|
169
|
+
*/
|
|
107
170
|
setBinding({ orgId, integrationId, sourceId, factoryProjectId, board, userId, }: {
|
|
108
171
|
orgId: string;
|
|
109
172
|
integrationId: string;
|
|
@@ -112,6 +175,24 @@ export declare class IntakeStorage extends FactoryStorageDomain {
|
|
|
112
175
|
board?: string | null;
|
|
113
176
|
userId?: string;
|
|
114
177
|
}): Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Bind a source to one Factory project alongside other Factory projects'
|
|
180
|
+
* bindings of it, taking the issues carrying one of `labels` — or, with
|
|
181
|
+
* `labels: null`, every issue no label binding claims. Rejected with the
|
|
182
|
+
* overlapping bindings when another Factory project already takes some of
|
|
183
|
+
* those labels, or already takes all unclaimed issues.
|
|
184
|
+
*/
|
|
185
|
+
setSharedBinding({ orgId, integrationId, sourceId, factoryProjectId, board, labels, userId, }: {
|
|
186
|
+
orgId: string;
|
|
187
|
+
integrationId: string;
|
|
188
|
+
sourceId: string;
|
|
189
|
+
factoryProjectId: string;
|
|
190
|
+
board?: string | null;
|
|
191
|
+
labels: string[] | null;
|
|
192
|
+
userId?: string;
|
|
193
|
+
}): Promise<{
|
|
194
|
+
conflicts: IntakeBindingConflict[];
|
|
195
|
+
}>;
|
|
115
196
|
/** Atomically replace legacy provider source ids in both selection and routing state. */
|
|
116
197
|
migrateSourceIds({ orgId, integrationId, migrations, }: {
|
|
117
198
|
orgId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/intake/base.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/intake/base.ts"],"names":[],"mappings":"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"}
|