@opengeni/api-router 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/app.d.ts +9 -1
  2. package/dist/app.js +7 -1
  3. package/dist/{chunk-3HIA43CC.js → chunk-HBEJMWD3.js} +5470 -2223
  4. package/dist/chunk-HBEJMWD3.js.map +1 -0
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.js +279 -55
  7. package/dist/index.js.map +1 -1
  8. package/package.json +20 -20
  9. package/src/app.ts +583 -166
  10. package/src/auth/managed-auth.ts +32 -16
  11. package/src/http/auth.ts +8 -1
  12. package/src/http/common.ts +6 -2
  13. package/src/http/sse.ts +84 -8
  14. package/src/index.ts +178 -75
  15. package/src/integrations/oauth-client.ts +403 -120
  16. package/src/integrations/provider-domain.ts +4 -1
  17. package/src/mcp/documents.ts +173 -94
  18. package/src/mcp/server.ts +1600 -693
  19. package/src/mcp/session-view.ts +8 -2
  20. package/src/mcp/toolspace.ts +175 -84
  21. package/src/observability.ts +7 -1
  22. package/src/routes/api-keys.ts +39 -23
  23. package/src/routes/billing.ts +180 -65
  24. package/src/routes/capabilities.ts +17 -8
  25. package/src/routes/catalog-assets.ts +5 -2
  26. package/src/routes/codex.ts +244 -63
  27. package/src/routes/connections.ts +71 -33
  28. package/src/routes/documents.ts +242 -92
  29. package/src/routes/enrollments.ts +100 -70
  30. package/src/routes/environments.ts +205 -136
  31. package/src/routes/files.ts +164 -39
  32. package/src/routes/github.ts +123 -50
  33. package/src/routes/install.ts +9 -2
  34. package/src/routes/machines.ts +9 -8
  35. package/src/routes/packs.ts +141 -89
  36. package/src/routes/rigs.ts +189 -0
  37. package/src/routes/scheduled-tasks.ts +51 -9
  38. package/src/routes/sessions.ts +870 -328
  39. package/src/routes/social.ts +50 -38
  40. package/src/routes/workspace-capture.ts +238 -0
  41. package/src/routes/workspaces.ts +146 -13
  42. package/src/sandbox/access.ts +11 -3
  43. package/src/sandbox/auth-callout.ts +5 -1
  44. package/src/sandbox/channel-a.ts +104 -27
  45. package/src/sandbox/enrollment.ts +13 -3
  46. package/src/sandbox/machines.ts +68 -59
  47. package/src/sandbox/metrics-ingestion.ts +238 -17
  48. package/src/sandbox/viewer.ts +172 -46
  49. package/dist/chunk-3HIA43CC.js.map +0 -1
@@ -27,12 +27,14 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
27
27
  trustedOrigins: betterAuthTrustedOrigins(settings),
28
28
  advanced: {
29
29
  useSecureCookies: settings.publicBaseUrl?.startsWith("https://") ?? false,
30
- ...(settings.betterAuthCookieDomain ? {
31
- crossSubDomainCookies: {
32
- enabled: true,
33
- domain: settings.betterAuthCookieDomain,
34
- },
35
- } : {}),
30
+ ...(settings.betterAuthCookieDomain
31
+ ? {
32
+ crossSubDomainCookies: {
33
+ enabled: true,
34
+ domain: settings.betterAuthCookieDomain,
35
+ },
36
+ }
37
+ : {}),
36
38
  database: {
37
39
  generateId: () => crypto.randomUUID(),
38
40
  },
@@ -149,7 +151,11 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
149
151
  }) as ManagedAuth;
150
152
  }
151
153
 
152
- export async function managedSessionAccessContext(auth: ManagedAuth, db: Database, headers: Headers) {
154
+ export async function managedSessionAccessContext(
155
+ auth: ManagedAuth,
156
+ db: Database,
157
+ headers: Headers,
158
+ ) {
153
159
  const session = await auth.api.getSession({ headers });
154
160
  if (!session?.user) {
155
161
  return null;
@@ -184,15 +190,20 @@ function betterAuthTrustedOrigins(settings: Settings): string[] {
184
190
  return [...origins];
185
191
  }
186
192
 
187
- async function sendEmail(settings: Settings, input: {
188
- to: string;
189
- subject: string;
190
- text: string;
191
- html: string;
192
- }): Promise<void> {
193
+ async function sendEmail(
194
+ settings: Settings,
195
+ input: {
196
+ to: string;
197
+ subject: string;
198
+ text: string;
199
+ html: string;
200
+ },
201
+ ): Promise<void> {
193
202
  if (!settings.resendApiKey) {
194
203
  if (settings.environment === "local" || settings.environment === "test") {
195
- console.warn(`[opengeni] Skipping email to ${input.to}: OPENGENI_RESEND_API_KEY is not configured`);
204
+ console.warn(
205
+ `[opengeni] Skipping email to ${input.to}: OPENGENI_RESEND_API_KEY is not configured`,
206
+ );
196
207
  return;
197
208
  }
198
209
  throw new Error("OPENGENI_RESEND_API_KEY is required to send managed auth email");
@@ -212,7 +223,9 @@ async function sendEmail(settings: Settings, input: {
212
223
 
213
224
  async function verificationUrl(settings: Settings, email: string): Promise<string> {
214
225
  if (!settings.betterAuthSecret) {
215
- throw new Error("OPENGENI_BETTER_AUTH_SECRET is required to send managed auth verification email");
226
+ throw new Error(
227
+ "OPENGENI_BETTER_AUTH_SECRET is required to send managed auth verification email",
228
+ );
216
229
  }
217
230
  if (!settings.publicBaseUrl) {
218
231
  throw new Error("OPENGENI_PUBLIC_BASE_URL is required to send managed auth verification email");
@@ -225,7 +238,10 @@ async function verificationUrl(settings: Settings, email: string): Promise<strin
225
238
  }
226
239
 
227
240
  function splitCsv(raw: string): string[] {
228
- return raw.split(",").map((value) => value.trim()).filter(Boolean);
241
+ return raw
242
+ .split(",")
243
+ .map((value) => value.trim())
244
+ .filter(Boolean);
229
245
  }
230
246
 
231
247
  function escapeHtml(value: string): string {
package/src/http/auth.ts CHANGED
@@ -84,7 +84,14 @@ function isAuthorized(c: Context, expected: string | undefined): boolean {
84
84
  return false;
85
85
  }
86
86
  const explicit = c.req.header("x-opengeni-access-key");
87
- return constantTimeEqual(explicit, expected);
87
+ if (constantTimeEqual(explicit, expected)) {
88
+ return true;
89
+ }
90
+ const authorization = c.req.header("authorization");
91
+ const bearer = authorization?.startsWith("Bearer ")
92
+ ? authorization.slice("Bearer ".length)
93
+ : undefined;
94
+ return constantTimeEqual(bearer, expected);
88
95
  }
89
96
 
90
97
  function constantTimeEqual(actual: string | undefined, expected: string): boolean {
@@ -9,8 +9,12 @@ export function boundedLimit(raw: string | undefined): number {
9
9
  return Math.min(500, Math.max(1, Math.floor(limit)));
10
10
  }
11
11
 
12
- export async function assertSessionExists(db: Database, workspaceId: string, sessionId: string): Promise<void> {
13
- if (!await getSession(db, workspaceId, sessionId)) {
12
+ export async function assertSessionExists(
13
+ db: Database,
14
+ workspaceId: string,
15
+ sessionId: string,
16
+ ): Promise<void> {
17
+ if (!(await getSession(db, workspaceId, sessionId))) {
14
18
  throw new HTTPException(404, { message: "session not found" });
15
19
  }
16
20
  }
package/src/http/sse.ts CHANGED
@@ -1,8 +1,15 @@
1
- import type { SessionEvent } from "@opengeni/contracts";
2
- import { listSessionEvents, type Database } from "@opengeni/db";
1
+ import type { SessionEvent, WorkspaceControlEvent } from "@opengeni/contracts";
2
+ import { listSessionEvents, listWorkspaceControlEvents, type Database } from "@opengeni/db";
3
3
  import { formatSse, type EventBus } from "@opengeni/events";
4
4
 
5
- export async function sseSessionStream(db: Database, bus: EventBus, workspaceId: string, sessionId: string, after: number, signal: AbortSignal): Promise<Response> {
5
+ export async function sseSessionStream(
6
+ db: Database,
7
+ bus: EventBus,
8
+ workspaceId: string,
9
+ sessionId: string,
10
+ after: number,
11
+ signal: AbortSignal,
12
+ ): Promise<Response> {
6
13
  const encoder = new TextEncoder();
7
14
  let controller: ReadableStreamDefaultController<Uint8Array>;
8
15
  let lastSent = after;
@@ -18,7 +25,13 @@ export async function sseSessionStream(db: Database, bus: EventBus, workspaceId:
18
25
  return;
19
26
  }
20
27
  if (event.sequence > lastSent + 1) {
21
- const missing = await listSessionEvents(db, workspaceId, sessionId, lastSent, event.sequence - lastSent - 1);
28
+ const missing = await listSessionEvents(
29
+ db,
30
+ workspaceId,
31
+ sessionId,
32
+ lastSent,
33
+ event.sequence - lastSent - 1,
34
+ );
22
35
  for (const missed of missing) {
23
36
  if (missed.sequence > lastSent) {
24
37
  controller.enqueue(encoder.encode(formatSse(missed)));
@@ -40,7 +53,11 @@ export async function sseSessionStream(db: Database, bus: EventBus, workspaceId:
40
53
  }
41
54
  });
42
55
 
43
- await replaySessionEvents((cursor, limit) => listSessionEvents(db, workspaceId, sessionId, cursor, limit), send, after);
56
+ await replaySessionEvents(
57
+ (cursor, limit) => listSessionEvents(db, workspaceId, sessionId, cursor, limit),
58
+ send,
59
+ after,
60
+ );
44
61
  replaying = false;
45
62
  for (const event of buffered.sort((a, b) => a.sequence - b.sequence)) {
46
63
  await send(event);
@@ -53,9 +70,13 @@ export async function sseSessionStream(db: Database, bus: EventBus, workspaceId:
53
70
  },
54
71
  });
55
72
 
56
- signal.addEventListener("abort", () => {
57
- unsubscribe?.();
58
- }, { once: true });
73
+ signal.addEventListener(
74
+ "abort",
75
+ () => {
76
+ unsubscribe?.();
77
+ },
78
+ { once: true },
79
+ );
59
80
 
60
81
  return new Response(stream, {
61
82
  headers: {
@@ -87,3 +108,58 @@ export async function replaySessionEvents(
87
108
  }
88
109
  }
89
110
  }
111
+
112
+ export async function sseWorkspaceControlStream(
113
+ db: Database,
114
+ bus: EventBus,
115
+ workspaceId: string,
116
+ after: number,
117
+ signal: AbortSignal,
118
+ ): Promise<Response> {
119
+ const encoder = new TextEncoder();
120
+ let lastSent = after;
121
+ let replaying = true;
122
+ const buffered: WorkspaceControlEvent[] = [];
123
+ let unsubscribe: (() => void) | null = null;
124
+
125
+ const stream = new ReadableStream<Uint8Array>({
126
+ start: async (controller) => {
127
+ const send = (event: WorkspaceControlEvent) => {
128
+ if (event.sequence <= lastSent) return;
129
+ controller.enqueue(encoder.encode(formatSse(event)));
130
+ lastSent = event.sequence;
131
+ };
132
+ unsubscribe = await bus.subscribeWorkspaceControl(workspaceId, async (event) => {
133
+ if (replaying) {
134
+ buffered.push(event);
135
+ } else {
136
+ send(event);
137
+ }
138
+ });
139
+ let cursor = after;
140
+ while (true) {
141
+ const page = await listWorkspaceControlEvents(db, workspaceId, cursor, 1000);
142
+ for (const event of page) {
143
+ send(event);
144
+ cursor = Math.max(cursor, event.sequence);
145
+ }
146
+ if (page.length < 1000) break;
147
+ }
148
+ replaying = false;
149
+ for (const event of buffered.sort((left, right) => left.sequence - right.sequence)) {
150
+ send(event);
151
+ }
152
+ buffered.length = 0;
153
+ controller.enqueue(encoder.encode(": connected\n\n"));
154
+ },
155
+ cancel: () => unsubscribe?.(),
156
+ });
157
+ signal.addEventListener("abort", () => unsubscribe?.(), { once: true });
158
+ return new Response(stream, {
159
+ headers: {
160
+ "Content-Type": "text/event-stream; charset=utf-8",
161
+ "Cache-Control": "no-cache, no-transform",
162
+ Connection: "keep-alive",
163
+ },
164
+ });
165
+ }
package/src/index.ts CHANGED
@@ -1,9 +1,27 @@
1
- import { dbSearchPath, getSettings, resolveNatsCalloutConfig, resolveNatsControlPlaneAuth, retryStartupDependency, startupRetryOptions } from "@opengeni/config";
2
- import type { ScheduledTask, ScheduledTaskOverlapPolicy, ScheduledTaskScheduleSpec } from "@opengeni/contracts";
3
- import { createDb } from "@opengeni/db";
1
+ import {
2
+ dbSearchPath,
3
+ getSettings,
4
+ resolveNatsCalloutConfig,
5
+ resolveNatsControlPlaneAuth,
6
+ retryStartupDependency,
7
+ startupRetryOptions,
8
+ } from "@opengeni/config";
9
+ import type {
10
+ ScheduledTask,
11
+ ScheduledTaskOverlapPolicy,
12
+ ScheduledTaskScheduleSpec,
13
+ } from "@opengeni/contracts";
14
+ import { createDb, markSessionWorkflowWakeDelivered, type Database } from "@opengeni/db";
4
15
  import { createNatsEventBus, type ResponderConnection } from "@opengeni/events";
5
16
  import { createObservability, logStartupDependencyRetry } from "@opengeni/observability";
6
- import { Connection, Client as TemporalClient, ScheduleNotFoundError, ScheduleOverlapPolicy, WorkflowExecutionAlreadyStartedError } from "@temporalio/client";
17
+ import { SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID } from "@opengeni/core";
18
+ import {
19
+ Connection,
20
+ Client as TemporalClient,
21
+ ScheduleNotFoundError,
22
+ ScheduleOverlapPolicy,
23
+ WorkflowExecutionAlreadyStartedError,
24
+ } from "@temporalio/client";
7
25
  import type { ScheduleOptions, ScheduleSpec, ScheduleUpdateOptions } from "@temporalio/client";
8
26
  import { createApp, type DocumentIndexClient, type SessionWorkflowClient } from "./app";
9
27
  import { observabilityEventLogger } from "./observability";
@@ -35,7 +53,10 @@ const TEMPORAL_MONTHS = [
35
53
  "DECEMBER",
36
54
  ] as const;
37
55
 
38
- export async function createTemporalWorkflowClient(settings: ReturnType<typeof getSettings>): Promise<{
56
+ export async function createTemporalWorkflowClient(
57
+ settings: ReturnType<typeof getSettings>,
58
+ db: Database,
59
+ ): Promise<{
39
60
  client: SessionWorkflowClient;
40
61
  documentIndexer: DocumentIndexClient;
41
62
  close: () => Promise<void>;
@@ -49,34 +70,81 @@ export async function createTemporalWorkflowClient(settings: ReturnType<typeof g
49
70
  signalUserMessage: async ({ eventId, workflowId }) => {
50
71
  await temporal.workflow.getHandle(workflowId).signal("userMessage", eventId);
51
72
  },
52
- wakeSessionWorkflow: async ({ accountId, workspaceId, sessionId, workflowId }) => {
73
+ wakeSessionWorkflow: async ({
74
+ accountId,
75
+ workspaceId,
76
+ sessionId,
77
+ workflowId,
78
+ wakeRevision,
79
+ interruptionRequested,
80
+ }) => {
53
81
  await temporal.workflow.signalWithStart("sessionWorkflow", {
54
82
  taskQueue: settings.temporalTaskQueue,
55
83
  workflowId,
56
84
  workflowIdReusePolicy: "ALLOW_DUPLICATE",
57
85
  args: [{ accountId, workspaceId, sessionId }],
58
- signal: "queueChanged",
86
+ signal: interruptionRequested ? "sessionControl" : "queueChanged",
87
+ });
88
+ await markSessionWorkflowWakeDelivered(db, {
89
+ accountId,
90
+ workspaceId,
91
+ sessionId,
92
+ temporalWorkflowId: workflowId,
93
+ wakeRevision,
59
94
  });
60
95
  },
61
- signalApprovalDecision: async ({ eventId, workflowId }) => {
62
- await temporal.workflow.getHandle(workflowId).signal("approvalDecision", eventId);
96
+ requestSessionWorkflowWakeDispatch: async () => {
97
+ await temporal.schedule
98
+ .getHandle(SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID)
99
+ .trigger(ScheduleOverlapPolicy.BUFFER_ONE);
63
100
  },
64
- signalInterrupt: async ({ accountId, workspaceId, sessionId, eventId, workflowId }) => {
65
- // Start-or-signal: an interrupt POSTed while the session is idle has no
66
- // running workflow execution to signal, and getHandle().signal() would
67
- // throw WorkflowNotFoundError -> a 500 (the operator-can't-stop bug). Like
68
- // wakeSessionWorkflow, signalWithStart delivers the signal to a live run
69
- // when one exists and otherwise starts a fresh sessionWorkflow that picks
70
- // the buffered `interrupt` up immediately. ALLOW_DUPLICATE matches the
71
- // wake path so a running execution is reused rather than rejected.
101
+ signalCodexCapacity: async ({
102
+ accountId,
103
+ workspaceId,
104
+ sessionId,
105
+ workflowId,
106
+ wakeRevision,
107
+ workflowWakeRevision,
108
+ }) => {
72
109
  await temporal.workflow.signalWithStart("sessionWorkflow", {
73
110
  taskQueue: settings.temporalTaskQueue,
74
111
  workflowId,
75
112
  workflowIdReusePolicy: "ALLOW_DUPLICATE",
76
113
  args: [{ accountId, workspaceId, sessionId }],
77
- signal: "interrupt",
114
+ signal: "codexCapacityChanged",
115
+ signalArgs: [wakeRevision],
116
+ });
117
+ await markSessionWorkflowWakeDelivered(db, {
118
+ accountId,
119
+ workspaceId,
120
+ sessionId,
121
+ temporalWorkflowId: workflowId,
122
+ wakeRevision: workflowWakeRevision,
123
+ });
124
+ },
125
+ signalApprovalDecision: async ({
126
+ accountId,
127
+ workspaceId,
128
+ sessionId,
129
+ eventId,
130
+ workflowId,
131
+ workflowWakeRevision,
132
+ }) => {
133
+ await temporal.workflow.signalWithStart("sessionWorkflow", {
134
+ taskQueue: settings.temporalTaskQueue,
135
+ workflowId,
136
+ workflowIdReusePolicy: "ALLOW_DUPLICATE",
137
+ args: [{ accountId, workspaceId, sessionId }],
138
+ signal: "approvalDecision",
78
139
  signalArgs: [eventId],
79
140
  });
141
+ await markSessionWorkflowWakeDelivered(db, {
142
+ accountId,
143
+ workspaceId,
144
+ sessionId,
145
+ temporalWorkflowId: workflowId,
146
+ wakeRevision: workflowWakeRevision,
147
+ });
80
148
  },
81
149
  syncScheduledTask: async ({ task }) => {
82
150
  const schedule = temporal.schedule.getHandle(task.temporalScheduleId);
@@ -91,36 +159,60 @@ export async function createTemporalWorkflowClient(settings: ReturnType<typeof g
91
159
  }
92
160
  },
93
161
  deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => {
94
- await temporal.schedule.getHandle(temporalScheduleId).delete().catch(() => undefined);
162
+ await temporal.schedule
163
+ .getHandle(temporalScheduleId)
164
+ .delete()
165
+ .catch(() => undefined);
95
166
  },
96
167
  triggerScheduledTask: async ({ task, agentRunUsageIdempotencyKey, triggerWorkflowId }) => {
97
- // Deterministic workflowId (derived from the trigger token by the
98
- // caller) + REJECT_DUPLICATE makes a retried manual trigger idempotent:
99
- // the second start collides on the id and is rejected instead of
100
- // spawning a second run. The shared idempotency key dedupes the charge.
101
- const workflowId = triggerWorkflowId ?? `scheduled-task-${task.id}-manual-${crypto.randomUUID()}`;
102
- try {
103
- await temporal.workflow.start("scheduledTaskFireWorkflow", {
104
- taskQueue: settings.temporalTaskQueue,
105
- workflowId,
106
- workflowIdReusePolicy: "REJECT_DUPLICATE",
107
- args: [{
108
- accountId: task.accountId,
109
- workspaceId: task.workspaceId,
110
- taskId: task.id,
111
- triggerType: "manual",
112
- agentRunUsageIdempotencyKey,
113
- }],
114
- });
115
- } catch (error) {
116
- // A duplicate trigger token started this run already; treat the retry
117
- // as a no-op so the (idempotent) usage charge stays the only effect.
118
- if (isWorkflowAlreadyStarted(error)) {
119
- return;
120
- }
168
+ // Deterministic workflowId (derived from the trigger token by the
169
+ // caller) + REJECT_DUPLICATE makes a retried manual trigger idempotent:
170
+ // the second start collides on the id and is rejected instead of
171
+ // spawning a second run. The shared idempotency key dedupes the charge.
172
+ const workflowId =
173
+ triggerWorkflowId ?? `scheduled-task-${task.id}-manual-${crypto.randomUUID()}`;
174
+ try {
175
+ await temporal.workflow.start("scheduledTaskFireWorkflow", {
176
+ taskQueue: settings.temporalTaskQueue,
177
+ workflowId,
178
+ workflowIdReusePolicy: "REJECT_DUPLICATE",
179
+ args: [
180
+ {
181
+ accountId: task.accountId,
182
+ workspaceId: task.workspaceId,
183
+ taskId: task.id,
184
+ triggerType: "manual",
185
+ agentRunUsageIdempotencyKey,
186
+ },
187
+ ],
188
+ });
189
+ } catch (error) {
190
+ // A duplicate trigger token started this run already; treat the retry
191
+ // as a no-op so the (idempotent) usage charge stays the only effect.
192
+ if (isWorkflowAlreadyStarted(error)) {
193
+ return;
194
+ }
121
195
  throw error;
122
196
  }
123
197
  },
198
+ startRigVerification: async ({ workspaceId, changeId, versionId, workflowId }) => {
199
+ const targetId = changeId ?? versionId;
200
+ if (!targetId) {
201
+ throw new Error("rig verification requires changeId or versionId");
202
+ }
203
+ await temporal.workflow.start("rigVerificationWorkflow", {
204
+ taskQueue: settings.temporalTaskQueue,
205
+ workflowId: workflowId ?? `rig-verification-${targetId}-${crypto.randomUUID()}`,
206
+ workflowIdReusePolicy: "ALLOW_DUPLICATE",
207
+ args: [
208
+ {
209
+ workspaceId,
210
+ ...(changeId ? { changeId } : {}),
211
+ ...(versionId ? { versionId } : {}),
212
+ },
213
+ ],
214
+ });
215
+ },
124
216
  check: async () => {
125
217
  await connection.workflowService.getSystemInfo({});
126
218
  },
@@ -158,7 +250,8 @@ export async function startApi() {
158
250
  let bus: Awaited<ReturnType<typeof createNatsEventBus>> | undefined;
159
251
  let workflowClient: Awaited<ReturnType<typeof createTemporalWorkflowClient>> | undefined;
160
252
  const retryOptions = startupRetryOptions(settings);
161
- const onRetry = (event: Parameters<typeof logStartupDependencyRetry>[1]) => logStartupDependencyRetry(observability, event);
253
+ const onRetry = (event: Parameters<typeof logStartupDependencyRetry>[1]) =>
254
+ logStartupDependencyRetry(observability, event);
162
255
  // The PRIVILEGED control-plane NATS login (M-AUTH): when the server runs with
163
256
  // auth_callout, api/worker authenticate as a static account user permitted to
164
257
  // request `agent.*.rpc`. Null in local dev (anonymous connect — the bus default).
@@ -169,7 +262,9 @@ export async function startApi() {
169
262
  () =>
170
263
  createNatsEventBus(
171
264
  settings.natsUrl,
172
- controlPlaneAuth ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password } : undefined,
265
+ controlPlaneAuth
266
+ ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password }
267
+ : undefined,
173
268
  { logger: observabilityEventLogger(observability) },
174
269
  ),
175
270
  {
@@ -177,16 +272,16 @@ export async function startApi() {
177
272
  onRetry,
178
273
  },
179
274
  );
180
- workflowClient = await retryStartupDependency("Temporal", () => createTemporalWorkflowClient(settings), {
181
- ...retryOptions,
182
- onRetry,
183
- });
275
+ workflowClient = await retryStartupDependency(
276
+ "Temporal",
277
+ () => createTemporalWorkflowClient(settings, dbClient.db),
278
+ {
279
+ ...retryOptions,
280
+ onRetry,
281
+ },
282
+ );
184
283
  } catch (error) {
185
- await Promise.allSettled([
186
- bus?.close(),
187
- workflowClient?.close(),
188
- dbClient.close(),
189
- ]);
284
+ await Promise.allSettled([bus?.close(), workflowClient?.close(), dbClient.close()]);
190
285
  throw error;
191
286
  }
192
287
  if (!bus || !workflowClient) {
@@ -295,25 +390,29 @@ export function temporalScheduleSpec(schedule: ScheduledTaskScheduleSpec): Sched
295
390
  }
296
391
  if (schedule.type === "calendar") {
297
392
  return {
298
- calendars: [{
299
- hour: schedule.hour,
300
- minute: schedule.minute,
301
- second: 0,
302
- ...(schedule.daysOfWeek ? { dayOfWeek: schedule.daysOfWeek } : {}),
303
- }],
393
+ calendars: [
394
+ {
395
+ hour: schedule.hour,
396
+ minute: schedule.minute,
397
+ second: 0,
398
+ ...(schedule.daysOfWeek ? { dayOfWeek: schedule.daysOfWeek } : {}),
399
+ },
400
+ ],
304
401
  timezone: schedule.timeZone,
305
402
  };
306
403
  }
307
404
  const runAt = new Date(schedule.runAt);
308
405
  return {
309
- calendars: [{
310
- year: runAt.getUTCFullYear(),
311
- month: temporalMonth(runAt.getUTCMonth()),
312
- dayOfMonth: runAt.getUTCDate(),
313
- hour: runAt.getUTCHours(),
314
- minute: runAt.getUTCMinutes(),
315
- second: runAt.getUTCSeconds(),
316
- }],
406
+ calendars: [
407
+ {
408
+ year: runAt.getUTCFullYear(),
409
+ month: temporalMonth(runAt.getUTCMonth()),
410
+ dayOfMonth: runAt.getUTCDate(),
411
+ hour: runAt.getUTCHours(),
412
+ minute: runAt.getUTCMinutes(),
413
+ second: runAt.getUTCSeconds(),
414
+ },
415
+ ],
317
416
  timezone: "UTC",
318
417
  };
319
418
  }
@@ -330,12 +429,14 @@ function temporalScheduleOptions(task: ScheduledTask, taskQueue: string): Schedu
330
429
  type: "startWorkflow",
331
430
  workflowType: "scheduledTaskFireWorkflow",
332
431
  taskQueue,
333
- args: [{
334
- accountId: task.accountId,
335
- workspaceId: task.workspaceId,
336
- taskId: task.id,
337
- triggerType: "scheduled",
338
- }],
432
+ args: [
433
+ {
434
+ accountId: task.accountId,
435
+ workspaceId: task.workspaceId,
436
+ taskId: task.id,
437
+ triggerType: "scheduled",
438
+ },
439
+ ],
339
440
  },
340
441
  policies: {
341
442
  overlap: temporalOverlapPolicy(task.overlapPolicy),
@@ -362,6 +463,8 @@ function temporalScheduleUpdateOptions(options: ScheduleOptions): ScheduleUpdate
362
463
  ...(options.policies ? { policies: options.policies } : {}),
363
464
  state: options.state ?? {},
364
465
  ...(options.searchAttributes ? { searchAttributes: options.searchAttributes } : {}),
365
- ...(options.typedSearchAttributes ? { typedSearchAttributes: options.typedSearchAttributes } : {}),
466
+ ...(options.typedSearchAttributes
467
+ ? { typedSearchAttributes: options.typedSearchAttributes }
468
+ : {}),
366
469
  };
367
470
  }