-
Notifications
You must be signed in to change notification settings - Fork 155
Expand file tree
/
Copy pathexecutor.ts
More file actions
102 lines (91 loc) · 3.68 KB
/
Copy pathexecutor.ts
File metadata and controls
102 lines (91 loc) · 3.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// ---------------------------------------------------------------------------
// Cloud executor — stateless, per-request, new SDK shape
// ---------------------------------------------------------------------------
//
// Each invocation of `createScopedExecutor` runs inside a request-scoped
// Effect and yields a fresh executor bound to the current DbService's
// per-request postgres.js client. Cloudflare Workers + Hyperdrive demand
// fresh connections per request, so "build once" means "once per request"
// here.
import { Effect } from "effect";
import {
Scope,
ScopeId,
collectTables,
createExecutor,
makeHostedHttpClientLayer,
} from "@executor-js/sdk";
import { env } from "cloudflare:workers";
import executorConfig from "../../executor.config";
import { DbService } from "./db";
import { createDrizzleFumaDb } from "./fuma";
// ---------------------------------------------------------------------------
// Plugin list lives in `executor.config.ts` — that file is the single source
// of truth for runtime, schema wiring, and the test harness. Per-request
// runtime values (WorkOS credentials from the Worker env) are passed through
// the factory's `deps` parameter.
// ---------------------------------------------------------------------------
export type CloudPlugins = ReturnType<typeof executorConfig.plugins>;
export const createCloudPlugins = (): CloudPlugins =>
executorConfig.plugins({
workosCredentials: {
apiKey: env.WORKOS_API_KEY,
clientId: env.WORKOS_CLIENT_ID,
},
});
// ---------------------------------------------------------------------------
// Create a fresh executor for a (user, org) pair (stateless, per-request).
//
// Scope stack is `[userOrgScope, orgScope]` — innermost first. The
// user-within-org scope id (`user-org:${userId}:${orgId}`) intentionally
// includes the org id so the same WorkOS user in a different org gets a
// distinct scope row; future workspace scopes can slot in between without
// conflicting with a hypothetical global user scope.
//
// OAuth token writes require an explicit `tokenScope`. User sign-in UI passes
// the user-org scope so a member's access/refresh tokens cannot leak to other
// members via `secrets.list`, while source rows and org-wide credentials live
// on the outer scope.
// ---------------------------------------------------------------------------
export const createScopedExecutor = (
userId: string,
organizationId: string,
organizationName: string,
plugins: CloudPlugins = createCloudPlugins(),
) =>
Effect.gen(function* () {
const { db } = yield* DbService;
const httpClientLayer = makeHostedHttpClientLayer({
allowLocalNetwork: env.NODE_ENV === "test",
});
const fuma = createDrizzleFumaDb({
db,
tables: collectTables(plugins),
namespace: "executor_cloud",
provider: "postgresql",
});
const orgScope = Scope.make({
id: ScopeId.make(organizationId),
name: organizationName,
createdAt: new Date(),
});
const userOrgScope = Scope.make({
id: ScopeId.make(`user-org:${userId}:${organizationId}`),
name: `Personal · ${organizationName}`,
createdAt: new Date(),
});
// The executor surface returns raw `StorageFailure`; translation to
// the opaque `InternalError({ traceId })` happens at the HTTP edge
// via `withCapture` (see `api/protected-layers.ts`). That's
// where `ErrorCaptureLive` (Sentry) gets wired in.
return yield* createExecutor({
scopes: [userOrgScope, orgScope],
db: fuma.db,
plugins,
httpClientLayer,
onElicitation: "accept-all",
coreTools: {
webBaseUrl: env.VITE_PUBLIC_SITE_URL ?? "https://executor.sh",
},
});
});