@mastra/factory 0.9.0-alpha.5 → 0.10.0-alpha.1
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/CHANGELOG.md +113 -0
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +11 -2
- package/dist/routes/intake.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +6 -5
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +21 -13
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,118 @@
|
|
|
1
1
|
# @mastra/factory
|
|
2
2
|
|
|
3
|
+
## 0.10.0-alpha.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`f95f468`](https://github.com/mastra-ai/mastra/commit/f95f468cf1e7c2b924a13826494f98b8f2ccd581)]:
|
|
8
|
+
- @mastra/core@1.61.1-alpha.1
|
|
9
|
+
- @mastra/code-sdk@1.4.1-alpha.1
|
|
10
|
+
|
|
11
|
+
## 0.10.0-alpha.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- Improved the Factory audit log with a density timeline, category filters, responsive rows, and automatic history loading. Intake binding changes now appear in the affected project's audit history. ([#22023](https://github.com/mastra-ai/mastra/pull/22023))
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
const response = await fetch(`/web/factory/projects/${factoryProjectId}/audit?actions=factory.run.started&limit=50`);
|
|
19
|
+
const page = await response.json();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Factory skill playbooks in Settings › Agent › Skills now render as formatted markdown instead of a wall of plain text, with a toggle on hover to read the raw SKILL.md source. Long skills scroll inside the card rather than stretching the page. ([#22018](https://github.com/mastra-ai/mastra/pull/22018))
|
|
25
|
+
|
|
26
|
+
- Updated dependencies [[`1e47b75`](https://github.com/mastra-ai/mastra/commit/1e47b7520cab4cfaa8daed52f17e2e6d14ff7539)]:
|
|
27
|
+
- @mastra/core@1.61.1-alpha.0
|
|
28
|
+
- @mastra/code-sdk@1.4.1-alpha.0
|
|
29
|
+
|
|
30
|
+
## 0.9.0
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- Added a `/login` command to the web chat composer. Credential errors used to name a command the web UI did not have, leaving no way to act on them from the browser. Typing `/login` now opens Settings → Models, where providers are connected. ([#21860](https://github.com/mastra-ai/mastra/pull/21860))
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Fixed the chat jumping every time a session's stream hiccuped. Losing the connection used to push a banner above the transcript and shove every message down; the reconnect state now lives only in the status line under the composer, where the model and token readouts already are. ([#21850](https://github.com/mastra-ai/mastra/pull/21850))
|
|
39
|
+
|
|
40
|
+
The state is also honest during a run: a drop while the agent works used to stay hidden behind the working indicator, and now shows as `Reconnecting…`. A connection lost for good reads as `Disconnected` in the alert color.
|
|
41
|
+
|
|
42
|
+
- Improved slash commands with a composer-integrated menu and consistent workspace panel elevation. ([#21980](https://github.com/mastra-ai/mastra/pull/21980))
|
|
43
|
+
|
|
44
|
+
- Improved loaded Factory conversations with a smooth staggered reveal. ([#21937](https://github.com/mastra-ai/mastra/pull/21937))
|
|
45
|
+
|
|
46
|
+
- Fixed assistant turns showing up twice in the chat transcript, with the first copy stripped of the tool cards that belong to it. ([#21851](https://github.com/mastra-ai/mastra/pull/21851))
|
|
47
|
+
|
|
48
|
+
Tool cards stay attached to the text they ran under. The double came from the same turn arriving under a second identity after a stream gap; the transcript now recognises that copy as the turn it is already drawing and updates it in place.
|
|
49
|
+
|
|
50
|
+
- Improved model selection in Factory chats. The status line now shows one combined picker with the effective model for the current mode. ([#21871](https://github.com/mastra-ai/mastra/pull/21871))
|
|
51
|
+
|
|
52
|
+
The picker offers:
|
|
53
|
+
|
|
54
|
+
- Model packs as presets, with your personal default marked.
|
|
55
|
+
- Models grouped by provider, to override the model for the current mode.
|
|
56
|
+
- A reset action that returns the chat to your default pack.
|
|
57
|
+
- A link to pack management in settings.
|
|
58
|
+
- Search across packs and models.
|
|
59
|
+
|
|
60
|
+
The picker works in draft chats and in active user chats. A pack chosen in a draft applies before the first prompt runs. Live user chats can now switch models directly from the status line.
|
|
61
|
+
|
|
62
|
+
- Trimmed what the Factory sidebar fetches while it polls. ([#21862](https://github.com/mastra-ai/mastra/pull/21862))
|
|
63
|
+
|
|
64
|
+
The activity dots used to cost one request per user session every five seconds. They now share a single request whatever the sidebar holds, so ten sessions poll once instead of eleven times.
|
|
65
|
+
|
|
66
|
+
Work item responses also stop carrying `factoryRuleMaterializationKey`, an internal field no client reads and the heaviest one on a large board.
|
|
67
|
+
|
|
68
|
+
- Fixed Platform GitHub/Linear integrations and the Platform API client ignoring `MASTRA_PLATFORM_ACCESS_TOKEN`, the credential Mastra Platform injects into deployed projects. Integration auto-detection and the API client now accept `MASTRA_PLATFORM_ACCESS_TOKEN` (checked first) or `MASTRA_PLATFORM_SECRET_KEY`, so platform deployments work without manually copying the secret key into the environment. ([#21982](https://github.com/mastra-ai/mastra/pull/21982))
|
|
69
|
+
|
|
70
|
+
- Creating a new Factory no longer takes over the whole screen once you already have one. The flow now runs inline at `/factories/:factoryId/new-factory`, so the sidebar stays in place and you keep the context of the Factory you were in. The full-screen version is still what you get on first run, when no Factory exists yet. ([#21932](https://github.com/mastra-ai/mastra/pull/21932))
|
|
71
|
+
|
|
72
|
+
Each step is now a searchable list you type into instead of a form: name the Factory, pick a repository, pick the Linear project that feeds its board (or skip Linear entirely), then choose the provider and model your runs start on. Picking a Linear project routes it to the new Factory and turns its issue sync on, so the board fills up without a detour through Settings. Repository search hits GitHub directly, so large accounts are usable, and keyboard navigation works throughout (arrows to move, Enter to select, Esc to leave).
|
|
73
|
+
|
|
74
|
+
Nothing is written to the server until the last step: the name, the repository and the Linear choice stay in the draft, and the Factory is created with all of them at once when you pick its model. Quitting the wizard halfway leaves nothing behind. Back walks the steps in reverse and only leaves the wizard from the first one.
|
|
75
|
+
|
|
76
|
+
- Factory projects now have their own configurable observational-memory settings. Board runs and channel sessions hydrate from the factory project's shared settings row (falling back to built-in defaults) instead of any individual user's personal configuration, and the OM config routes accept a `factoryId` to read and update the factory-scoped row. In settings, a dedicated Memory page shows the factory-wide and personal observational-memory configuration side by side, so factory defaults and personal chat settings are edited separately. ([#21899](https://github.com/mastra-ai/mastra/pull/21899))
|
|
77
|
+
|
|
78
|
+
To read or update the factory-scoped configuration, pass the factory project id:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
await fetch(`/web/config/om?factoryId=${factoryId}`);
|
|
82
|
+
await fetch(`/web/config/om/observer/model`, {
|
|
83
|
+
method: 'PUT',
|
|
84
|
+
body: JSON.stringify({ modelId: 'anthropic/claude-haiku-4-5', factoryId }),
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Requests without `factoryId` keep operating on the caller's personal settings.
|
|
89
|
+
|
|
90
|
+
- Provider OAuth sign-in can now be shared with the whole organization. Org admins get a "Just me" / "Everyone in org" toggle on the OAuth provider list; org-scoped sign-ins are stored as shared org credentials, reported with an "Org sign-in" badge, and can be removed at org scope (admin-gated). ([#21899](https://github.com/mastra-ai/mastra/pull/21899))
|
|
91
|
+
|
|
92
|
+
- Factory runs now resolve provider credentials with org > user precedence, so an org-wide "Everyone in org" key takes priority over a run's acting user's personal key. This means factory automation always bills against the org's shared credentials when they exist, regardless of who triggered the run. Interactive (non-factory) sessions keep the existing user > org precedence, so personal plan subscriptions and keys still take priority there. ([#21899](https://github.com/mastra-ai/mastra/pull/21899))
|
|
93
|
+
|
|
94
|
+
- Provider credentials can now be managed per scope after initial setup. The provider listing reports the caller's personal and org credentials independently (`userCredential`/`orgCredential` on `ProviderInfo`), so the settings UI shows separate sign-out actions for each scope and lets org admins add an org-wide OAuth sign-in while personally signed in (and vice versa) without signing out first. ([#21899](https://github.com/mastra-ai/mastra/pull/21899))
|
|
95
|
+
|
|
96
|
+
- Provider-aware observational-memory defaults for factories. The factory creation wizard now fills the factory-scoped OM row (POST /web/config/om/provider-defaults accepts factoryId), and factory session hydration derives the OM fallback model from the factory's default model provider (e.g. anthropic/claude-haiku-4-5 when the default model is anthropic) instead of always using google/gemini-3.5-flash. GET/PUT OM routes report the same derived fallback so the settings UI no longer shows "Model credentials required" for factories whose default model provider is credentialed. ([#21899](https://github.com/mastra-ai/mastra/pull/21899))
|
|
97
|
+
|
|
98
|
+
- Interactive messages and model switches on factory sessions now resolve provider credentials org-first (org > user), matching board-run kickoff. The credential resolver keys off the session's `factoryProjectId` in controller state, so any run on a factory-owned session rides the org's shared keys with the caller's personal credentials as fallback — switching to a personal-only model still works through that fallback. Repo-backed Slack channel sessions now stamp the owning factory project onto session state so they get the same behavior. ([#21899](https://github.com/mastra-ai/mastra/pull/21899))
|
|
99
|
+
|
|
100
|
+
- Fixed factory board runs and Slack channel sessions inheriting the GitHub connection owner's personal observational-memory model settings. Factory sessions now always use the project's default model and the built-in observational-memory defaults, so runs no longer fail when the connection owner has a model configured that the workspace has no API key for. Web chat sessions still use each user's own memory settings. ([#21899](https://github.com/mastra-ai/mastra/pull/21899))
|
|
101
|
+
|
|
102
|
+
Note: sessions created before this change keep the settings they were hydrated with. Recreate existing factory sessions after deploying to pick up the corrected defaults.
|
|
103
|
+
|
|
104
|
+
- Fixed Factory steering messages so they no longer interrupt active work. Pending steering messages now show their delivery state and use the same neutral style as other user messages. ([#21983](https://github.com/mastra-ai/mastra/pull/21983))
|
|
105
|
+
|
|
106
|
+
- Fixed shared threads running with a stale model in multi-server deployments. The model selected for a mode is now re-read from the thread's persisted settings at the start of every run, so a model switch made in one browser session or server replica is picked up by all others instead of silently diverging until the next mode switch. ([#21899](https://github.com/mastra-ai/mastra/pull/21899))
|
|
107
|
+
|
|
108
|
+
- Fixed pull request cards that stayed marked as open after an approving review. A card that an approving review moved to `done` was dropped from the GitHub reconcile sweep, so a merge landing afterwards never reached it — the board card kept saying `open` and the merged marker never appeared on its review session in the sidebar. Cards now stay in the sweep until their pull request is actually closed. ([#21870](https://github.com/mastra-ai/mastra/pull/21870))
|
|
109
|
+
|
|
110
|
+
- Split thinking defaults on the Models settings page: the factory defaults section now has a base thinking level widget, and per-mode thinking defaults moved into the personal "Your defaults" section. ([#21899](https://github.com/mastra-ai/mastra/pull/21899))
|
|
111
|
+
|
|
112
|
+
- Updated dependencies [[`88d14ca`](https://github.com/mastra-ai/mastra/commit/88d14cac008582a618fecc3d5c7fd3bdf4f6ddc3), [`480e491`](https://github.com/mastra-ai/mastra/commit/480e491588bd6a7a1c9ee4407590ad625dd33952), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`acc3471`](https://github.com/mastra-ai/mastra/commit/acc3471de5f3fde8027ee4e355af292b2bc1bc30), [`b6a771e`](https://github.com/mastra-ai/mastra/commit/b6a771ef23d203ddb348efca8065eff65def8191), [`84a5b69`](https://github.com/mastra-ai/mastra/commit/84a5b699f84d6bae0a34efe5a970d891090b9f41), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`3bb88dd`](https://github.com/mastra-ai/mastra/commit/3bb88ddf07fb98f3cd16d3bff94e51cd3b45d011), [`d23e75d`](https://github.com/mastra-ai/mastra/commit/d23e75d57cc7cf5b9bfdbee896bf5a6a2484fed7), [`c8faa4e`](https://github.com/mastra-ai/mastra/commit/c8faa4e1cfebaec56b65e754e90b9fe46d153359), [`d378d75`](https://github.com/mastra-ai/mastra/commit/d378d7511f71309ed61a8f6b93cd0361dc6cb70f), [`84a5b69`](https://github.com/mastra-ai/mastra/commit/84a5b699f84d6bae0a34efe5a970d891090b9f41), [`26d4016`](https://github.com/mastra-ai/mastra/commit/26d40160ff7f7d8bf95fee2039a52cbc83863533), [`7c60df5`](https://github.com/mastra-ai/mastra/commit/7c60df5c7872343fbac5c3e5b1175c8076a5abfd), [`57c5103`](https://github.com/mastra-ai/mastra/commit/57c51035a2a36e3df3c4f32f46bb789a66ed5946), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`10de311`](https://github.com/mastra-ai/mastra/commit/10de311e93baea36468463d25bf0f97046239d5e), [`84a5b69`](https://github.com/mastra-ai/mastra/commit/84a5b699f84d6bae0a34efe5a970d891090b9f41), [`f2031a4`](https://github.com/mastra-ai/mastra/commit/f2031a47445e8f67a89ba1309036816f97ab7a65), [`4c2b973`](https://github.com/mastra-ai/mastra/commit/4c2b97396066e97c95c3d0429b2f63a92e6af127), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`cad4208`](https://github.com/mastra-ai/mastra/commit/cad42082e6aa1776168a94914f523334be45d929), [`57c5103`](https://github.com/mastra-ai/mastra/commit/57c51035a2a36e3df3c4f32f46bb789a66ed5946), [`8e529d4`](https://github.com/mastra-ai/mastra/commit/8e529d4ac754efef04b225841349e0da9edf89a6), [`57c5103`](https://github.com/mastra-ai/mastra/commit/57c51035a2a36e3df3c4f32f46bb789a66ed5946), [`038b7b4`](https://github.com/mastra-ai/mastra/commit/038b7b405cb4ac25ab3f3031334111b1f87ac112), [`4132d61`](https://github.com/mastra-ai/mastra/commit/4132d61f8367077120ee9e6420d3224dffd93c93), [`d378d75`](https://github.com/mastra-ai/mastra/commit/d378d7511f71309ed61a8f6b93cd0361dc6cb70f)]:
|
|
113
|
+
- @mastra/core@1.61.0
|
|
114
|
+
- @mastra/code-sdk@1.4.0
|
|
115
|
+
|
|
3
116
|
## 0.9.0-alpha.5
|
|
4
117
|
|
|
5
118
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intake.d.ts","sourceRoot":"","sources":["../../src/routes/intake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAAC,CAAC;CACnD;AAYD,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,KAAK,EAAE,YAAY,CAAC;IACpB,sCAAsC;IACtC,MAAM,EAAE,aAAa,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;IAClF,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACpC;AAED,UAAU,aAAa;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAWtE;AAaD,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAyBpE;AAmBD,qBAAa,YAAa,SAAQ,KAAK,CAAC,gBAAgB,CAAC;;IAgBvD,MAAM,IAAI,QAAQ,EAAE;
|
|
1
|
+
{"version":3,"file":"intake.d.ts","sourceRoot":"","sources":["../../src/routes/intake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAAC,CAAC;CACnD;AAYD,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,KAAK,EAAE,YAAY,CAAC;IACpB,sCAAsC;IACtC,MAAM,EAAE,aAAa,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;IAClF,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACpC;AAED,UAAU,aAAa;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAWtE;AAaD,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAyBpE;AAmBD,qBAAa,YAAa,SAAQ,KAAK,CAAC,gBAAgB,CAAC;;IAgBvD,MAAM,IAAI,QAAQ,EAAE;CAsLrB"}
|
package/dist/routes/intake.js
CHANGED
|
@@ -164,15 +164,24 @@ var IntakeRoutes = class extends Route {
|
|
|
164
164
|
})) return c.json({ error: "factory_project_not_found" }, 404);
|
|
165
165
|
}
|
|
166
166
|
await intake.ensureReady();
|
|
167
|
-
|
|
167
|
+
let auditFactoryProjectId = binding.factoryProjectId;
|
|
168
|
+
if (binding.factoryProjectId === null) auditFactoryProjectId = (await intake.clearBinding({
|
|
169
|
+
orgId: tenant.orgId,
|
|
170
|
+
integrationId: binding.integrationId,
|
|
171
|
+
sourceId: binding.sourceId
|
|
172
|
+
}))?.factoryProjectId ?? null;
|
|
173
|
+
else await intake.setBinding({
|
|
168
174
|
orgId: tenant.orgId,
|
|
169
175
|
userId: tenant.userId,
|
|
170
|
-
|
|
176
|
+
integrationId: binding.integrationId,
|
|
177
|
+
sourceId: binding.sourceId,
|
|
178
|
+
factoryProjectId: binding.factoryProjectId
|
|
171
179
|
});
|
|
172
180
|
await audit.emit({
|
|
173
181
|
context: loose(c),
|
|
174
182
|
input: {
|
|
175
183
|
action: "factory.intake.binding_updated",
|
|
184
|
+
...auditFactoryProjectId ? { factoryProjectId: auditFactoryProjectId } : {},
|
|
176
185
|
targets: [{
|
|
177
186
|
type: "intake_source",
|
|
178
187
|
id: `${binding.integrationId}:${binding.sourceId}`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intake.js","names":["#resolveTenant"],"sources":["../../src/routes/intake.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { Intake, IntakeItem } from '../capabilities/intake.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { IntakeConfig, IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nexport interface IntakeIntegration {\n id: string;\n intake: Pick<Intake, 'listSources' | 'listItems'>;\n}\n\ninterface AggregatedIntakeItem extends Omit<IntakeItem, 'source'> {\n integrationId: string;\n externalSource: {\n integrationId: string;\n type: string;\n externalId: string;\n url?: string;\n };\n}\n\nexport interface IntakeRoutesDeps extends RouteDependencies {\n audit: AuditEmitter;\n /** Intake selection domain handle. */\n intake: IntakeStorage;\n /** Factory project domain handle, used to validate binding targets. */\n projects?: { get(input: { orgId: string; id: string }): Promise<unknown | null> };\n integrations?: IntakeIntegration[];\n}\n\ninterface ParsedBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string | null;\n}\n\n/** Validate a binding request body, rejecting unknown shapes. */\nexport function parseIntakeBinding(body: unknown): ParsedBinding | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const { integrationId, sourceId, factoryProjectId } = body as Record<string, unknown>;\n const isId = (value: unknown) => typeof value === 'string' && value.length > 0 && value.length <= 256;\n if (!isId(integrationId) || !isId(sourceId)) return null;\n if (factoryProjectId !== null && !isId(factoryProjectId)) return null;\n return {\n integrationId: integrationId as string,\n sourceId: sourceId as string,\n factoryProjectId: factoryProjectId as string | null,\n };\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction sanitizeIdList(value: unknown): string[] | null | undefined {\n if (value === null) return null;\n if (!Array.isArray(value) || value.length > 200) return undefined;\n const ids = value.filter((item): item is string => typeof item === 'string' && item.length > 0 && item.length <= 256);\n return ids.length === value.length && new Set(ids).size === ids.length ? ids : undefined;\n}\n\n/** Validate a request body into an intake config, rejecting unknown shapes. */\nexport function parseIntakeConfig(body: unknown): IntakeConfig | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const entries = Object.entries(body);\n if (entries.length > 50) return null;\n\n // Null-prototype so an `__proto__` key lands as a real entry instead of silently\n // reassigning the prototype and disappearing from the validation below.\n const config: IntakeConfig = Object.create(null);\n for (const [integrationId, value] of entries) {\n if (\n !integrationId ||\n integrationId.length > 128 ||\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value)\n ) {\n return null;\n }\n const selection = value as { enabled?: unknown; sourceIds?: unknown };\n if (typeof selection.enabled !== 'boolean') return null;\n const sourceIds = sanitizeIdList(selection.sourceIds ?? null);\n if (sourceIds === undefined) return null;\n config[integrationId] = { enabled: selection.enabled, sourceIds };\n }\n return config;\n}\n\nfunction encodeCursor(cursors: Record<string, string>): string | null {\n return Object.keys(cursors).length > 0 ? Buffer.from(JSON.stringify(cursors)).toString('base64url') : null;\n}\n\nfunction decodeCursor(value: string | undefined): Record<string, string> | null {\n if (!value) return {};\n try {\n const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8')) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n const entries = Object.entries(parsed);\n if (entries.some(([key, cursor]) => !key || typeof cursor !== 'string')) return null;\n return Object.fromEntries(entries) as Record<string, string>;\n } catch {\n return null;\n }\n}\n\nexport class IntakeRoutes extends Route<IntakeRoutesDeps> {\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'Intake configuration requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n const { audit, intake, projects, integrations = [] } = this.deps;\n const integrationIds = integrations.map(integration => integration.id);\n\n return [\n registerApiRoute('/web/intake/config', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n const config = await intake.getConfig({ ...tenant, integrationIds });\n return c.json({ config });\n },\n }),\n registerApiRoute('/web/intake/config', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const config = parseIntakeConfig(body);\n if (!config) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n\n const registeredConfig: IntakeConfig = Object.create(null);\n for (const [integrationId, selection] of Object.entries(config)) {\n if (integrationIds.includes(integrationId)) {\n registeredConfig[integrationId] = selection;\n continue;\n }\n if (selection.enabled || selection.sourceIds?.length) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n }\n\n await intake.ensureReady();\n await intake.saveConfig({ ...tenant, config: registeredConfig });\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.config_updated',\n targets: [{ type: 'intake_config', id: tenant.orgId }],\n metadata: Object.fromEntries(\n Object.entries(registeredConfig).map(([integrationId, selection]) => [\n integrationId,\n { enabled: selection.enabled, sources: selection.sourceIds?.length ?? null },\n ]),\n ),\n },\n });\n return c.json({ config: registeredConfig });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n return c.json({ bindings: await intake.listBindings({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const binding = parseIntakeBinding(body);\n if (!binding || !integrationIds.includes(binding.integrationId)) {\n return c.json({ error: 'invalid_binding' }, 400);\n }\n if (binding.factoryProjectId && projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: binding.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n\n await intake.ensureReady();\n await intake.setBinding({ orgId: tenant.orgId, userId: tenant.userId, ...binding });\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.binding_updated',\n targets: [{ type: 'intake_source', id: `${binding.integrationId}:${binding.sourceId}` }],\n metadata: { factoryProjectId: binding.factoryProjectId },\n },\n });\n return c.json({ bindings: await intake.listBindings({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/intake/sources', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const pages = await Promise.all(\n integrations.map(async integration => ({\n integrationId: integration.id,\n sources: await integration.intake.listSources(tenant),\n })),\n );\n return c.json({\n sources: pages.flatMap(page =>\n page.sources.map(source => ({ integrationId: page.integrationId, ...source })),\n ),\n });\n },\n }),\n registerApiRoute('/web/intake/items', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const cursors = decodeCursor(c.req.query('cursor'));\n if (!cursors) return c.json({ error: 'invalid_cursor' }, 400);\n\n await intake.ensureReady();\n const config = await intake.getConfig({ ...tenant, integrationIds });\n const items: AggregatedIntakeItem[] = [];\n const nextCursors: Record<string, string> = {};\n for (const integration of integrations) {\n const selection = config[integration.id];\n if (!selection?.enabled || !selection.sourceIds?.length) continue;\n const page = await integration.intake.listItems({\n ...tenant,\n sourceIds: selection.sourceIds,\n ...(cursors[integration.id] ? { cursor: cursors[integration.id] } : {}),\n });\n items.push(\n ...page.items.map(item => {\n const { source, ...candidate } = item;\n return {\n ...candidate,\n integrationId: integration.id,\n externalSource: { integrationId: integration.id, ...source },\n };\n }),\n );\n if (page.nextCursor) nextCursors[integration.id] = page.nextCursor;\n }\n return c.json({ items, nextCursor: encodeCursor(nextCursors) });\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"intake.js","names":["#resolveTenant"],"sources":["../../src/routes/intake.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { Intake, IntakeItem } from '../capabilities/intake.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { IntakeConfig, IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nexport interface IntakeIntegration {\n id: string;\n intake: Pick<Intake, 'listSources' | 'listItems'>;\n}\n\ninterface AggregatedIntakeItem extends Omit<IntakeItem, 'source'> {\n integrationId: string;\n externalSource: {\n integrationId: string;\n type: string;\n externalId: string;\n url?: string;\n };\n}\n\nexport interface IntakeRoutesDeps extends RouteDependencies {\n audit: AuditEmitter;\n /** Intake selection domain handle. */\n intake: IntakeStorage;\n /** Factory project domain handle, used to validate binding targets. */\n projects?: { get(input: { orgId: string; id: string }): Promise<unknown | null> };\n integrations?: IntakeIntegration[];\n}\n\ninterface ParsedBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string | null;\n}\n\n/** Validate a binding request body, rejecting unknown shapes. */\nexport function parseIntakeBinding(body: unknown): ParsedBinding | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const { integrationId, sourceId, factoryProjectId } = body as Record<string, unknown>;\n const isId = (value: unknown) => typeof value === 'string' && value.length > 0 && value.length <= 256;\n if (!isId(integrationId) || !isId(sourceId)) return null;\n if (factoryProjectId !== null && !isId(factoryProjectId)) return null;\n return {\n integrationId: integrationId as string,\n sourceId: sourceId as string,\n factoryProjectId: factoryProjectId as string | null,\n };\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction sanitizeIdList(value: unknown): string[] | null | undefined {\n if (value === null) return null;\n if (!Array.isArray(value) || value.length > 200) return undefined;\n const ids = value.filter((item): item is string => typeof item === 'string' && item.length > 0 && item.length <= 256);\n return ids.length === value.length && new Set(ids).size === ids.length ? ids : undefined;\n}\n\n/** Validate a request body into an intake config, rejecting unknown shapes. */\nexport function parseIntakeConfig(body: unknown): IntakeConfig | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const entries = Object.entries(body);\n if (entries.length > 50) return null;\n\n // Null-prototype so an `__proto__` key lands as a real entry instead of silently\n // reassigning the prototype and disappearing from the validation below.\n const config: IntakeConfig = Object.create(null);\n for (const [integrationId, value] of entries) {\n if (\n !integrationId ||\n integrationId.length > 128 ||\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value)\n ) {\n return null;\n }\n const selection = value as { enabled?: unknown; sourceIds?: unknown };\n if (typeof selection.enabled !== 'boolean') return null;\n const sourceIds = sanitizeIdList(selection.sourceIds ?? null);\n if (sourceIds === undefined) return null;\n config[integrationId] = { enabled: selection.enabled, sourceIds };\n }\n return config;\n}\n\nfunction encodeCursor(cursors: Record<string, string>): string | null {\n return Object.keys(cursors).length > 0 ? Buffer.from(JSON.stringify(cursors)).toString('base64url') : null;\n}\n\nfunction decodeCursor(value: string | undefined): Record<string, string> | null {\n if (!value) return {};\n try {\n const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8')) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n const entries = Object.entries(parsed);\n if (entries.some(([key, cursor]) => !key || typeof cursor !== 'string')) return null;\n return Object.fromEntries(entries) as Record<string, string>;\n } catch {\n return null;\n }\n}\n\nexport class IntakeRoutes extends Route<IntakeRoutesDeps> {\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'Intake configuration requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n const { audit, intake, projects, integrations = [] } = this.deps;\n const integrationIds = integrations.map(integration => integration.id);\n\n return [\n registerApiRoute('/web/intake/config', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n const config = await intake.getConfig({ ...tenant, integrationIds });\n return c.json({ config });\n },\n }),\n registerApiRoute('/web/intake/config', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const config = parseIntakeConfig(body);\n if (!config) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n\n const registeredConfig: IntakeConfig = Object.create(null);\n for (const [integrationId, selection] of Object.entries(config)) {\n if (integrationIds.includes(integrationId)) {\n registeredConfig[integrationId] = selection;\n continue;\n }\n if (selection.enabled || selection.sourceIds?.length) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n }\n\n await intake.ensureReady();\n await intake.saveConfig({ ...tenant, config: registeredConfig });\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.config_updated',\n targets: [{ type: 'intake_config', id: tenant.orgId }],\n metadata: Object.fromEntries(\n Object.entries(registeredConfig).map(([integrationId, selection]) => [\n integrationId,\n { enabled: selection.enabled, sources: selection.sourceIds?.length ?? null },\n ]),\n ),\n },\n });\n return c.json({ config: registeredConfig });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n return c.json({ bindings: await intake.listBindings({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const binding = parseIntakeBinding(body);\n if (!binding || !integrationIds.includes(binding.integrationId)) {\n return c.json({ error: 'invalid_binding' }, 400);\n }\n if (binding.factoryProjectId && projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: binding.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n\n await intake.ensureReady();\n let auditFactoryProjectId = binding.factoryProjectId;\n if (binding.factoryProjectId === null) {\n const previousBinding = await intake.clearBinding({\n orgId: tenant.orgId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n });\n auditFactoryProjectId = previousBinding?.factoryProjectId ?? null;\n } else {\n await intake.setBinding({\n orgId: tenant.orgId,\n userId: tenant.userId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n factoryProjectId: binding.factoryProjectId,\n });\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.binding_updated',\n ...(auditFactoryProjectId ? { factoryProjectId: auditFactoryProjectId } : {}),\n targets: [{ type: 'intake_source', id: `${binding.integrationId}:${binding.sourceId}` }],\n metadata: { factoryProjectId: binding.factoryProjectId },\n },\n });\n return c.json({ bindings: await intake.listBindings({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/intake/sources', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const pages = await Promise.all(\n integrations.map(async integration => ({\n integrationId: integration.id,\n sources: await integration.intake.listSources(tenant),\n })),\n );\n return c.json({\n sources: pages.flatMap(page =>\n page.sources.map(source => ({ integrationId: page.integrationId, ...source })),\n ),\n });\n },\n }),\n registerApiRoute('/web/intake/items', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const cursors = decodeCursor(c.req.query('cursor'));\n if (!cursors) return c.json({ error: 'invalid_cursor' }, 400);\n\n await intake.ensureReady();\n const config = await intake.getConfig({ ...tenant, integrationIds });\n const items: AggregatedIntakeItem[] = [];\n const nextCursors: Record<string, string> = {};\n for (const integration of integrations) {\n const selection = config[integration.id];\n if (!selection?.enabled || !selection.sourceIds?.length) continue;\n const page = await integration.intake.listItems({\n ...tenant,\n sourceIds: selection.sourceIds,\n ...(cursors[integration.id] ? { cursor: cursors[integration.id] } : {}),\n });\n items.push(\n ...page.items.map(item => {\n const { source, ...candidate } = item;\n return {\n ...candidate,\n integrationId: integration.id,\n externalSource: { integrationId: integration.id, ...source },\n };\n }),\n );\n if (page.nextCursor) nextCursors[integration.id] = page.nextCursor;\n }\n return c.json({ items, nextCursor: encodeCursor(nextCursors) });\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
|
|
@@ -59,15 +59,16 @@ export declare class IntakeStorage extends FactoryStorageDomain {
|
|
|
59
59
|
integrationId: string;
|
|
60
60
|
factoryProjectId: string;
|
|
61
61
|
}): Promise<string[]>;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
clearBinding({ orgId, integrationId, sourceId, }: {
|
|
63
|
+
orgId: string;
|
|
64
|
+
integrationId: string;
|
|
65
|
+
sourceId: string;
|
|
66
|
+
}): Promise<IntakeSourceBinding | null>;
|
|
66
67
|
setBinding({ orgId, integrationId, sourceId, factoryProjectId, userId, }: {
|
|
67
68
|
orgId: string;
|
|
68
69
|
integrationId: string;
|
|
69
70
|
sourceId: string;
|
|
70
|
-
factoryProjectId: string
|
|
71
|
+
factoryProjectId: string;
|
|
71
72
|
userId?: string;
|
|
72
73
|
}): Promise<void>;
|
|
73
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/intake/base.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAqB,MAAM,sBAAsB,CAAC;AAEhF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAE3D,eAAO,MAAM,qBAAqB,EAAE,YAAiB,CAAC;AAEtD,eAAO,MAAM,sBAAsB,EAAE,gBAWpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,EAAE,gBAmB3C,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/intake/base.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAqB,MAAM,sBAAsB,CAAC;AAEhF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAE3D,eAAO,MAAM,qBAAqB,EAAE,YAAiB,CAAC;AAEtD,eAAO,MAAM,sBAAsB,EAAE,gBAWpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,EAAE,gBAmB3C,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAeD,qBAAa,aAAc,SAAQ,oBAAoB;;;IAK/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IASpC,SAAS,CAAC,EACd,KAAK,EACL,MAAM,EACN,cAAc,GACf,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf;;;WAGG;QACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;KAC3B,GAAG,OAAO,CAAC,YAAY,CAAC;IAYnB,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAanH,+EAA+E;IACzE,YAAY,CAAC,EACjB,KAAK,EACL,aAAa,GACd,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAQlC,4EAA4E;IACtE,kBAAkB,CAAC,EACvB,KAAK,EACL,aAAa,EACb,gBAAgB,GACjB,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IASf,YAAY,CAAC,EACjB,KAAK,EACL,aAAa,EACb,QAAQ,GACT,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAajC,UAAU,CAAC,EACf,KAAK,EACL,aAAa,EACb,QAAQ,EACR,gBAAgB,EAChB,MAAM,GACP,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;QACzB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,IAAI,CAAC;CAmBlB"}
|
|
@@ -59,6 +59,13 @@ const INTAKE_SOURCE_BINDINGS_SCHEMA = {
|
|
|
59
59
|
]
|
|
60
60
|
}]
|
|
61
61
|
};
|
|
62
|
+
function toIntakeSourceBinding(row) {
|
|
63
|
+
return {
|
|
64
|
+
integrationId: row.integration_id,
|
|
65
|
+
sourceId: row.source_id,
|
|
66
|
+
factoryProjectId: row.factory_project_id
|
|
67
|
+
};
|
|
68
|
+
}
|
|
62
69
|
var IntakeStorage = class extends FactoryStorageDomain {
|
|
63
70
|
constructor() {
|
|
64
71
|
super("intake");
|
|
@@ -115,11 +122,7 @@ var IntakeStorage = class extends FactoryStorageDomain {
|
|
|
115
122
|
return (await this.#db.findMany("intake_source_bindings", {
|
|
116
123
|
org_id: orgId,
|
|
117
124
|
...integrationId ? { integration_id: integrationId } : {}
|
|
118
|
-
})).map(
|
|
119
|
-
integrationId: row.integration_id,
|
|
120
|
-
sourceId: row.source_id,
|
|
121
|
-
factoryProjectId: row.factory_project_id
|
|
122
|
-
}));
|
|
125
|
+
})).map(toIntakeSourceBinding);
|
|
123
126
|
}
|
|
124
127
|
/** Source ids bound to one Factory project. Empty means "nothing bound". */
|
|
125
128
|
async listBoundSourceIds({ orgId, integrationId, factoryProjectId }) {
|
|
@@ -129,20 +132,25 @@ var IntakeStorage = class extends FactoryStorageDomain {
|
|
|
129
132
|
factory_project_id: factoryProjectId
|
|
130
133
|
})).map((row) => row.source_id);
|
|
131
134
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
async clearBinding({ orgId, integrationId, sourceId }) {
|
|
136
|
+
const where = {
|
|
137
|
+
org_id: orgId,
|
|
138
|
+
integration_id: integrationId,
|
|
139
|
+
source_id: sourceId
|
|
140
|
+
};
|
|
141
|
+
return this.storage.withTransaction(async (ops) => {
|
|
142
|
+
const row = await ops.findOne("intake_source_bindings", where);
|
|
143
|
+
if (!row) return null;
|
|
144
|
+
await ops.deleteMany("intake_source_bindings", where);
|
|
145
|
+
return toIntakeSourceBinding(row);
|
|
146
|
+
}, { isolationLevel: "serializable" });
|
|
147
|
+
}
|
|
136
148
|
async setBinding({ orgId, integrationId, sourceId, factoryProjectId, userId }) {
|
|
137
149
|
const where = {
|
|
138
150
|
org_id: orgId,
|
|
139
151
|
integration_id: integrationId,
|
|
140
152
|
source_id: sourceId
|
|
141
153
|
};
|
|
142
|
-
if (factoryProjectId === null) {
|
|
143
|
-
await this.#db.deleteMany("intake_source_bindings", where);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
154
|
const now = /* @__PURE__ */ new Date();
|
|
147
155
|
const patch = {
|
|
148
156
|
factory_project_id: factoryProjectId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","names":["#db"],"sources":["../../../../src/storage/domains/intake/base.ts"],"sourcesContent":["/**\n * Per-user intake selections for every configured intake integration.\n *\n * Integration ids are dynamic. Each integration contributes provider-neutral\n * sources through `FactoryIntegration.intake`; this domain only persists which\n * source ids the user selected.\n */\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\n\nexport interface IntakeSelection {\n enabled: boolean;\n /** Provider-owned source ids; `null` means nothing is selected. */\n sourceIds: string[] | null;\n}\n\nexport type IntakeConfig = Record<string, IntakeSelection>;\n\nexport const DEFAULT_INTAKE_CONFIG: IntakeConfig = {};\n\nexport const INTAKE_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'intake_settings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n config: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'intake_settings_org_user_unique', columns: ['org_id', 'user_id'] }],\n};\n\n/**\n * Binds an intake source to the Factory project its items belong to.\n *\n * Intake selections are per user and org-wide, so they cannot say *where* an\n * ingested item should land. GitHub items are naturally scoped by their linked\n * repository; providers without that link (Linear) need this explicit binding\n * so viewing one project's board cannot materialize another project's items.\n */\nexport const INTAKE_SOURCE_BINDINGS_SCHEMA: CollectionSchema = {\n name: 'intake_source_bindings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n integration_id: { type: 'text' },\n source_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n created_by_user_id: { type: 'text', nullable: true },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n indexes: [{ name: 'intake_source_bindings_project_idx', columns: ['org_id', 'factory_project_id'] }],\n uniqueIndexes: [\n {\n name: 'intake_source_bindings_org_integration_source_unique',\n columns: ['org_id', 'integration_id', 'source_id'],\n },\n ],\n};\n\nexport interface IntakeSourceBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string;\n}\n\ntype IntakeSourceBindingRow = {\n integration_id: string;\n source_id: string;\n factory_project_id: string;\n};\n\nexport class IntakeStorage extends FactoryStorageDomain {\n constructor() {\n super('intake');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([INTAKE_SETTINGS_SCHEMA, INTAKE_SOURCE_BINDINGS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('intake_settings', {});\n await this.ops.deleteMany('intake_source_bindings', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async getConfig({\n orgId,\n userId,\n integrationIds,\n }: {\n orgId: string;\n userId: string;\n /**\n * When provided, the result contains exactly these integration ids, with\n * unset integrations defaulting to `{ enabled: true, sourceIds: null }`.\n */\n integrationIds?: string[];\n }): Promise<IntakeConfig> {\n const row = await this.#db.findOne<{ config: IntakeConfig }>('intake_settings', {\n org_id: orgId,\n user_id: userId,\n });\n const saved = structuredClone(row?.config ?? DEFAULT_INTAKE_CONFIG);\n if (!integrationIds) return saved;\n return Object.fromEntries(\n integrationIds.map(integrationId => [integrationId, saved[integrationId] ?? { enabled: true, sourceIds: null }]),\n );\n }\n\n async saveConfig({ orgId, userId, config }: { orgId: string; userId: string; config: IntakeConfig }): Promise<void> {\n const now = new Date();\n const where = { org_id: orgId, user_id: userId };\n const updated = await this.#db.updateMany('intake_settings', where, { config, updated_at: now });\n if (updated > 0) return;\n try {\n await this.#db.insertOne('intake_settings', { ...where, config, created_at: now, updated_at: now });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n await this.#db.updateMany('intake_settings', where, { config, updated_at: now });\n }\n }\n\n /** Every source binding in the org, optionally narrowed to one integration. */\n async listBindings({\n orgId,\n integrationId,\n }: {\n orgId: string;\n integrationId?: string;\n }): Promise<IntakeSourceBinding[]> {\n const rows = await this.#db.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n ...(integrationId ? { integration_id: integrationId } : {}),\n });\n return rows.map(row => ({\n integrationId: row.integration_id,\n sourceId: row.source_id,\n factoryProjectId: row.factory_project_id,\n }));\n }\n\n /** Source ids bound to one Factory project. Empty means \"nothing bound\". */\n async listBoundSourceIds({\n orgId,\n integrationId,\n factoryProjectId,\n }: {\n orgId: string;\n integrationId: string;\n factoryProjectId: string;\n }): Promise<string[]> {\n const rows = await this.#db.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n factory_project_id: factoryProjectId,\n });\n return rows.map(row => row.source_id);\n }\n\n /**\n * Points a source at a Factory project, replacing any existing binding.\n * A `null` project clears the binding.\n */\n async setBinding({\n orgId,\n integrationId,\n sourceId,\n factoryProjectId,\n userId,\n }: {\n orgId: string;\n integrationId: string;\n sourceId: string;\n factoryProjectId: string | null;\n userId?: string;\n }): Promise<void> {\n const where = { org_id: orgId, integration_id: integrationId, source_id: sourceId };\n if (factoryProjectId === null) {\n await this.#db.deleteMany('intake_source_bindings', where);\n return;\n }\n const now = new Date();\n const patch = { factory_project_id: factoryProjectId, updated_at: now };\n const updated = await this.#db.updateMany('intake_source_bindings', where, patch);\n if (updated > 0) return;\n try {\n await this.#db.insertOne('intake_source_bindings', {\n ...where,\n factory_project_id: factoryProjectId,\n created_by_user_id: userId ?? null,\n created_at: now,\n updated_at: now,\n });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n await this.#db.updateMany('intake_source_bindings', where, patch);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base.js","names":["#db"],"sources":["../../../../src/storage/domains/intake/base.ts"],"sourcesContent":["/**\n * Per-user intake selections for every configured intake integration.\n *\n * Integration ids are dynamic. Each integration contributes provider-neutral\n * sources through `FactoryIntegration.intake`; this domain only persists which\n * source ids the user selected.\n */\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\n\nexport interface IntakeSelection {\n enabled: boolean;\n /** Provider-owned source ids; `null` means nothing is selected. */\n sourceIds: string[] | null;\n}\n\nexport type IntakeConfig = Record<string, IntakeSelection>;\n\nexport const DEFAULT_INTAKE_CONFIG: IntakeConfig = {};\n\nexport const INTAKE_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'intake_settings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n config: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'intake_settings_org_user_unique', columns: ['org_id', 'user_id'] }],\n};\n\n/**\n * Binds an intake source to the Factory project its items belong to.\n *\n * Intake selections are per user and org-wide, so they cannot say *where* an\n * ingested item should land. GitHub items are naturally scoped by their linked\n * repository; providers without that link (Linear) need this explicit binding\n * so viewing one project's board cannot materialize another project's items.\n */\nexport const INTAKE_SOURCE_BINDINGS_SCHEMA: CollectionSchema = {\n name: 'intake_source_bindings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n integration_id: { type: 'text' },\n source_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n created_by_user_id: { type: 'text', nullable: true },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n indexes: [{ name: 'intake_source_bindings_project_idx', columns: ['org_id', 'factory_project_id'] }],\n uniqueIndexes: [\n {\n name: 'intake_source_bindings_org_integration_source_unique',\n columns: ['org_id', 'integration_id', 'source_id'],\n },\n ],\n};\n\nexport interface IntakeSourceBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string;\n}\n\ntype IntakeSourceBindingRow = {\n integration_id: string;\n source_id: string;\n factory_project_id: string;\n};\nfunction toIntakeSourceBinding(row: IntakeSourceBindingRow): IntakeSourceBinding {\n return {\n integrationId: row.integration_id,\n sourceId: row.source_id,\n factoryProjectId: row.factory_project_id,\n };\n}\n\nexport class IntakeStorage extends FactoryStorageDomain {\n constructor() {\n super('intake');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([INTAKE_SETTINGS_SCHEMA, INTAKE_SOURCE_BINDINGS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('intake_settings', {});\n await this.ops.deleteMany('intake_source_bindings', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async getConfig({\n orgId,\n userId,\n integrationIds,\n }: {\n orgId: string;\n userId: string;\n /**\n * When provided, the result contains exactly these integration ids, with\n * unset integrations defaulting to `{ enabled: true, sourceIds: null }`.\n */\n integrationIds?: string[];\n }): Promise<IntakeConfig> {\n const row = await this.#db.findOne<{ config: IntakeConfig }>('intake_settings', {\n org_id: orgId,\n user_id: userId,\n });\n const saved = structuredClone(row?.config ?? DEFAULT_INTAKE_CONFIG);\n if (!integrationIds) return saved;\n return Object.fromEntries(\n integrationIds.map(integrationId => [integrationId, saved[integrationId] ?? { enabled: true, sourceIds: null }]),\n );\n }\n\n async saveConfig({ orgId, userId, config }: { orgId: string; userId: string; config: IntakeConfig }): Promise<void> {\n const now = new Date();\n const where = { org_id: orgId, user_id: userId };\n const updated = await this.#db.updateMany('intake_settings', where, { config, updated_at: now });\n if (updated > 0) return;\n try {\n await this.#db.insertOne('intake_settings', { ...where, config, created_at: now, updated_at: now });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n await this.#db.updateMany('intake_settings', where, { config, updated_at: now });\n }\n }\n\n /** Every source binding in the org, optionally narrowed to one integration. */\n async listBindings({\n orgId,\n integrationId,\n }: {\n orgId: string;\n integrationId?: string;\n }): Promise<IntakeSourceBinding[]> {\n const rows = await this.#db.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n ...(integrationId ? { integration_id: integrationId } : {}),\n });\n return rows.map(toIntakeSourceBinding);\n }\n\n /** Source ids bound to one Factory project. Empty means \"nothing bound\". */\n async listBoundSourceIds({\n orgId,\n integrationId,\n factoryProjectId,\n }: {\n orgId: string;\n integrationId: string;\n factoryProjectId: string;\n }): Promise<string[]> {\n const rows = await this.#db.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n factory_project_id: factoryProjectId,\n });\n return rows.map(row => row.source_id);\n }\n\n async clearBinding({\n orgId,\n integrationId,\n sourceId,\n }: {\n orgId: string;\n integrationId: string;\n sourceId: string;\n }): Promise<IntakeSourceBinding | null> {\n const where = { org_id: orgId, integration_id: integrationId, source_id: sourceId };\n return this.storage.withTransaction(\n async ops => {\n const row = await ops.findOne<IntakeSourceBindingRow>('intake_source_bindings', where);\n if (!row) return null;\n await ops.deleteMany('intake_source_bindings', where);\n return toIntakeSourceBinding(row);\n },\n { isolationLevel: 'serializable' },\n );\n }\n\n async setBinding({\n orgId,\n integrationId,\n sourceId,\n factoryProjectId,\n userId,\n }: {\n orgId: string;\n integrationId: string;\n sourceId: string;\n factoryProjectId: string;\n userId?: string;\n }): Promise<void> {\n const where = { org_id: orgId, integration_id: integrationId, source_id: sourceId };\n const now = new Date();\n const patch = { factory_project_id: factoryProjectId, updated_at: now };\n const updated = await this.#db.updateMany('intake_source_bindings', where, patch);\n if (updated > 0) return;\n try {\n await this.#db.insertOne('intake_source_bindings', {\n ...where,\n factory_project_id: factoryProjectId,\n created_by_user_id: userId ?? null,\n created_at: now,\n updated_at: now,\n });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n await this.#db.updateMany('intake_source_bindings', where, patch);\n }\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/factory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0-alpha.1",
|
|
4
4
|
"description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"hono": "^4.12.8",
|
|
53
53
|
"zod": "^4.3.6",
|
|
54
54
|
"@mastra/auth-studio": "1.3.4",
|
|
55
|
-
"@mastra/
|
|
56
|
-
"@mastra/core": "1.61.
|
|
57
|
-
"@mastra/code-sdk": "1.4.
|
|
58
|
-
"@mastra/
|
|
55
|
+
"@mastra/slack": "1.6.1",
|
|
56
|
+
"@mastra/core": "1.61.1-alpha.1",
|
|
57
|
+
"@mastra/code-sdk": "1.4.1-alpha.1",
|
|
58
|
+
"@mastra/auth-workos": "1.6.4"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/node": "22.20.1",
|
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
"typescript": "^6.0.3",
|
|
65
65
|
"typescript-eslint": "^8.57.0",
|
|
66
66
|
"vitest": "4.1.10",
|
|
67
|
-
"@internal/lint": "0.0.
|
|
68
|
-
"@mastra/libsql": "1.21.1
|
|
69
|
-
"@mastra/pg": "1.21.1
|
|
70
|
-
"@internal/types-builder": "0.0.
|
|
67
|
+
"@internal/lint": "0.0.125",
|
|
68
|
+
"@mastra/libsql": "1.21.1",
|
|
69
|
+
"@mastra/pg": "1.21.1",
|
|
70
|
+
"@internal/types-builder": "0.0.100"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=22.19.0"
|