History

Remove sqlite example package and tighten backend typing

Rhys Sullivan committed 7 months ago

Commit 6fb3ea91 parent d05aa9d9 files · +237 −1150Browse files

Unified · Split

9 files
  1. bun.lock +0 −15
  2. examples/sqlite-sdk-consumer/.gitignore +0 −1
  3. examples/sqlite-sdk-consumer/README.md +0 −41
  4. examples/sqlite-sdk-consumer/package.json +0 −22
  5. examples/sqlite-sdk-consumer/src/index.ts +0 −5
  6. examples/sqlite-sdk-consumer/src/main.ts +0 −53
  7. examples/sqlite-sdk-consumer/src/sqlite-backend.ts +0 −982
  8. examples/sqlite-sdk-consumer/tsconfig.json +0 −15
  9. packages/platform/sdk/src/backend.ts +237 −16
bun.lock+0−15
@@ -101,19 +101,6 @@ · 100 unmodified lines
101 "bun-types": "catalog:",101 "bun-types": "catalog:",
102 },102 },
103 },103 },
104 "examples/sqlite-sdk-consumer": {
105 "name": "@executor/sqlite-sdk-consumer-example",
106 "version": "0.0.0",
107 "dependencies": {
108 "@executor/platform-sdk": "workspace:*",
109 "@executor/source-core": "workspace:*",
110 "effect": "catalog:",
111 },
112 "devDependencies": {
113 "@types/node": "catalog:",
114 "bun-types": "catalog:",
115 },
116 },
117 "packages/auth/mcp-oauth": {104 "packages/auth/mcp-oauth": {
118 "name": "@executor/auth-mcp-oauth",105 "name": "@executor/auth-mcp-oauth",
119 "version": "0.0.0",106 "version": "0.0.0",
@@ -768,8 +755,6 @@ · 648 unmodified lines
768 755
769 "@executor/source-openapi": ["@executor/source-openapi@workspace:packages/sources/openapi"],756 "@executor/source-openapi": ["@executor/source-openapi@workspace:packages/sources/openapi"],
770 757
771 "@executor/sqlite-sdk-consumer-example": ["@executor/sqlite-sdk-consumer-example@workspace:examples/sqlite-sdk-consumer"],
772
773 "@executor/web": ["@executor/web@workspace:apps/web"],758 "@executor/web": ["@executor/web@workspace:apps/web"],
774 759
775 "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="],760 "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="],
examples/sqlite-sdk-consumer/.gitignore+0−1
@@ -1 +0,0 @@
1.data/
examples/sqlite-sdk-consumer/README.md+0−41
@@ -1,41 +0,0 @@
1# SQLite SDK Consumer Example
2
3This example shows an embedder using `@executor/platform-sdk` with a custom SQLite-backed backend.
4
5It keeps the backend intentionally small:
6
7- installation state is stored in SQLite
8- workspace config and workspace state are stored in SQLite
9- source artifacts are stored in SQLite
10- executor state is stored in SQLite
11- secret material is stored in SQLite
12
13The example backend is document-oriented rather than heavily normalized so the adapter seam stays easy to follow.
14
15## Run
16
17```sh
18bun run --cwd examples/sqlite-sdk-consumer start
19```
20
21To keep the demo ephemeral:
22
23```sh
24DATABASE_PATH=:memory: bun run --cwd examples/sqlite-sdk-consumer start
25```
26
27## Shape
28
29The important bit is the consumer-facing construction:
30
31```ts
32import { createExecutor } from "@executor/platform-sdk";
33import { createSqliteExecutorBackend } from "@executor/sqlite-sdk-consumer-example";
34
35const executor = await createExecutor({
36 backend: createSqliteExecutorBackend({
37 databasePath: "./executor.sqlite",
38 workspaceName: "Acme",
39 }),
40});
41```
examples/sqlite-sdk-consumer/package.json+0−22
@@ -1,22 +0,0 @@
1{
2 "name": "@executor/sqlite-sdk-consumer-example",
3 "private": true,
4 "type": "module",
5 "exports": {
6 ".": "./src/index.ts"
7 },
8 "scripts": {
9 "typecheck": "bunx tsc --noEmit -p tsconfig.json",
10 "start": "bun src/main.ts"
11 },
12 "dependencies": {
13 "@executor/platform-sdk": "workspace:*",
14 "@executor/source-core": "workspace:*",
15 "effect": "catalog:"
16 },
17 "devDependencies": {
18 "@types/node": "catalog:",
19 "bun-types": "catalog:"
20 },
21 "version": "0.0.0"
22}
examples/sqlite-sdk-consumer/src/index.ts+0−5
@@ -1,5 +0,0 @@
1export {
2 createSqliteExecutor,
3 createSqliteExecutorBackend,
4 type CreateSqliteExecutorBackendOptions,
5} from "./sqlite-backend";
examples/sqlite-sdk-consumer/src/main.ts+0−53
@@ -1,53 +0,0 @@
1import { createSqliteExecutor } from "./sqlite-backend";
2
3const databasePath = process.env.DATABASE_PATH
4 ?? new URL("../.data/executor.sqlite", import.meta.url).pathname;
5
6const executor = await createSqliteExecutor({
7 databasePath,
8 scopeName: "SQLite Example Workspace",
9 scopeRoot: process.cwd(),
10});
11
12try {
13 const installation = await executor.local.installation();
14
15 const policy = await executor.policies.create({
16 resourcePattern: "math.add",
17 effect: "allow",
18 approvalMode: "auto",
19 });
20
21 const secret = await executor.secrets.create({
22 name: "demo-api-key",
23 value: "secret-value",
24 });
25
26 const execution = await executor.executions.create({
27 code: "return 20 + 22;",
28 });
29
30
31 const sources = await executor.sources.list();
32 const secrets = await executor.secrets.list();
33 const policies = await executor.policies.list();
34
35 console.log(JSON.stringify({
36 databasePath,
37 installation,
38 createdPolicyId: policy.id,
39 createdSecretId: secret.id,
40 execution: {
41 id: execution.execution.id,
42 status: execution.execution.status,
43 resultJson: execution.execution.resultJson,
44 },
45 counts: {
46 sources: sources.length,
47 secrets: secrets.length,
48 policies: policies.length,
49 },
50 }, null, 2));
51} finally {
52 await executor.close();
53}
examples/sqlite-sdk-consumer/src/sqlite-backend.ts+0−982
@@ -1,982 +0,0 @@
1import { createHash, randomUUID } from "node:crypto";
2import { mkdirSync } from "node:fs";
3import { dirname, resolve as resolvePath } from "node:path";
4
5import { Database } from "bun:sqlite";
6import {
7 createExecutor,
8 createExecutorBackend,
9 type CreateExecutorOptions,
10 type Executor,
11 type ExecutorBackend,
12 type ExecutorBackendServices,
13 type ExecutorStateBackend,
14 type ExecutorInstallationBackend,
15 type ExecutorLocalToolBackend,
16 type ExecutorSecretMaterialBackend,
17 type ExecutorSourceArtifactBackend,
18 type ExecutorSourceTypeDeclarationsBackend,
19 type ExecutorScopeConfigBackend,
20 type ExecutorScopeStateBackend,
21} from "@executor/platform-sdk";
22import type { ExecutorRuntimeOptions } from "@executor/platform-sdk/runtime";
23import {
24 type AuthArtifact,
25 ScopeIdSchema,
26 AuthArtifactSchema,
27 type AuthLease,
28 AuthLeaseSchema,
29 type Execution,
30 type ExecutionInteraction,
31 ExecutionInteractionSchema,
32 type ExecutionStep,
33 ExecutionSchema,
34 ExecutionStepSchema,
35 type LocalInstallation,
36 LocalExecutorConfigSchema,
37 LocalInstallationSchema,
38 type ProviderAuthGrant,
39 ProviderAuthGrantSchema,
40 SecretMaterialIdSchema,
41 SecretMaterialSchema,
42 type SourceAuthSession,
43 SourceAuthSessionSchema,
44 SourceCatalogIdSchema,
45 SourceCatalogRevisionIdSchema,
46 type ScopeOauthClient,
47 ScopeOauthClientSchema,
48 type ScopedSourceOauthClient,
49 ScopedSourceOauthClientSchema,
50 type SecretMaterial,
51 type SecretRef,
52} from "@executor/platform-sdk/schema";
53import * as Effect from "effect/Effect";
54import * as Option from "effect/Option";
55import * as Schema from "effect/Schema";
56
57import { contentHash, snapshotFromSourceCatalogSyncResult } from "@executor/source-core";
58
59export type CreateSqliteExecutorBackendOptions = {
60 databasePath?: string;
61 scopeName?: string;
62 scopeRoot?: string | null;
63 scopeId?: string;
64 actorScopeId?: string;
65};
66
67type DocumentNamespace =
68 | "installation"
69 | "scope_config"
70 | "scope_state"
71 | "source_artifact"
72 | "executor_state";
73
74type ExecutorStateCollections = {
75 authArtifacts: readonly AuthArtifact[];
76 authLeases: readonly AuthLease[];
77 sourceOauthClients: readonly ScopedSourceOauthClient[];
78 scopeOauthClients: readonly ScopeOauthClient[];
79 providerAuthGrants: readonly ProviderAuthGrant[];
80 sourceAuthSessions: readonly SourceAuthSession[];
81 secretMaterials: readonly SecretMaterial[];
82 executions: readonly Execution[];
83 executionInteractions: readonly ExecutionInteraction[];
84 executionSteps: readonly ExecutionStep[];
85};
86
87const decodeInstallation = Schema.decodeUnknownSync(LocalInstallationSchema);
88const decodeConfig = Schema.decodeUnknownSync(LocalExecutorConfigSchema);
89const decodeAuthArtifacts = Schema.decodeUnknownSync(Schema.Array(AuthArtifactSchema));
90const decodeAuthLeases = Schema.decodeUnknownSync(Schema.Array(AuthLeaseSchema));
91const decodeSourceOauthClients = Schema.decodeUnknownSync(
92 Schema.Array(ScopedSourceOauthClientSchema),
93);
94const decodeScopeOauthClients = Schema.decodeUnknownSync(
95 Schema.Array(ScopeOauthClientSchema),
96);
97const decodeProviderAuthGrants = Schema.decodeUnknownSync(
98 Schema.Array(ProviderAuthGrantSchema),
99);
100const decodeSourceAuthSessions = Schema.decodeUnknownSync(
101 Schema.Array(SourceAuthSessionSchema),
102);
103const decodeSecretMaterials = Schema.decodeUnknownSync(
104 Schema.Array(SecretMaterialSchema),
105);
106const decodeExecutions = Schema.decodeUnknownSync(Schema.Array(ExecutionSchema));
107const decodeExecutionInteractions = Schema.decodeUnknownSync(
108 Schema.Array(ExecutionInteractionSchema),
109);
110const decodeExecutionSteps = Schema.decodeUnknownSync(
111 Schema.Array(ExecutionStepSchema),
112);
113
114type SourceArtifact = ReturnType<ExecutorSourceArtifactBackend["build"]>;
115type SourceArtifactBuildInput = Parameters<ExecutorSourceArtifactBackend["build"]>[0];
116
117const SQLITE_SECRET_PROVIDER_ID = "sqlite";
118
119type ScopeState = Parameters<ExecutorScopeStateBackend["write"]>[0];
120const defaultProjectConfig = decodeConfig({});
121
122const defaultScopeState = (): ScopeState => ({
123 version: 1 as const,
124 sources: {},
125 policies: {},
126});
127
128const makeIdHash = (value: string): string =>
129 createHash("sha256").update(value).digest("hex").slice(0, 24);
130
131const makeSourceArtifact = (input: SourceArtifactBuildInput): SourceArtifact => {
132 const snapshot = snapshotFromSourceCatalogSyncResult(input.syncResult);
133 const sourceSignature = JSON.stringify({
134 kind: input.source.kind,
135 endpoint: input.source.endpoint,
136 namespace: input.source.namespace,
137 name: input.source.name,
138 enabled: input.source.enabled,
139 binding: input.source.binding,
140 auth: input.source.auth,
141 importAuth: input.source.importAuth,
142 importAuthPolicy: input.source.importAuthPolicy,
143 });
144 const catalogId = SourceCatalogIdSchema.make(
145 `src_catalog_${makeIdHash(sourceSignature)}`,
146 );
147 const revisionId = SourceCatalogRevisionIdSchema.make(
148 `src_catalog_rev_${makeIdHash(sourceSignature)}`,
149 );
150 const importMetadataJson = JSON.stringify(snapshot.import);
151 const snapshotHash = contentHash(JSON.stringify(snapshot));
152 const importMetadataHash = contentHash(importMetadataJson);
153
154 return {
155 version: 4 as const,
156 sourceId: input.source.id,
157 catalogId,
158 generatedAt: Date.now(),
159 revision: {
160 id: revisionId,
161 catalogId,
162 revisionNumber: 1,
163 sourceConfigJson: sourceSignature,
164 importMetadataJson,
165 importMetadataHash,
166 snapshotHash,
167 createdAt: input.source.createdAt,
168 updatedAt: input.source.updatedAt,
169 },
170 snapshot,
171 };
172};
173
174const toError = (cause: unknown): Error =>
175 cause instanceof Error ? cause : new Error(String(cause));
176
177class SqliteDocumentStore {
178 readonly db: Database;
179
180 constructor(databasePath: string) {
181 if (databasePath !== ":memory:") {
182 mkdirSync(dirname(databasePath), { recursive: true });
183 }
184
185 this.db = new Database(databasePath, {
186 create: true,
187 strict: true,
188 });
189
190 this.db.exec(`
191 CREATE TABLE IF NOT EXISTS executor_documents (
192 namespace TEXT NOT NULL,
193 key TEXT NOT NULL,
194 value TEXT NOT NULL,
195 updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
196 PRIMARY KEY (namespace, key)
197 );
198 `);
199 }
200
201 close() {
202 this.db.close();
203 }
204
205 read<T>(
206 namespace: DocumentNamespace,
207 key: string,
208 decode: (value: unknown) => T,
209 fallback: () => T,
210 ): T {
211 const row = this.db
212 .query(
213 "SELECT value FROM executor_documents WHERE namespace = ?1 AND key = ?2",
214 )
215 .get(namespace, key) as { value: string } | null;
216
217 if (!row) {
218 return fallback();
219 }
220
221 return decode(JSON.parse(row.value) as unknown);
222 }
223
224 write(namespace: DocumentNamespace, key: string, value: unknown) {
225 this.db
226 .query(`
227 INSERT INTO executor_documents (namespace, key, value, updated_at)
228 VALUES (?1, ?2, ?3, ?4)
229 ON CONFLICT(namespace, key) DO UPDATE SET
230 value = excluded.value,
231 updated_at = excluded.updated_at
232 `)
233 .run(namespace, key, JSON.stringify(value), Date.now());
234 }
235
236 remove(namespace: DocumentNamespace, key: string) {
237 this.db
238 .query("DELETE FROM executor_documents WHERE namespace = ?1 AND key = ?2")
239 .run(namespace, key);
240 }
241}
242
243const effectTry = <T>(label: string, execute: () => T) =>
244 Effect.try({
245 try: execute,
246 catch: (cause) => new Error(`${label}: ${toError(cause).message}`),
247 });
248
249const sameActor = (
250 left: string | null | undefined,
251 right: string | null | undefined,
252): boolean => (left ?? null) === (right ?? null);
253
254const createInstallationStore = (
255 store: SqliteDocumentStore,
256 options: CreateSqliteExecutorBackendOptions,
257): ExecutorInstallationBackend => {
258 const provision = (): LocalInstallation =>
259 decodeInstallation({
260 actorScopeId: ScopeIdSchema.make(options.actorScopeId ?? "acct_sqlite_example"),
261 scopeId: ScopeIdSchema.make(options.scopeId ?? "ws_sqlite_example"),
262 resolutionScopeIds: [
263 ScopeIdSchema.make(options.scopeId ?? "ws_sqlite_example"),
264 ScopeIdSchema.make(options.actorScopeId ?? "acct_sqlite_example"),
265 ],
266 });
267
268 return {
269 load: () =>
270 effectTry("load installation", () =>
271 store.read("installation", "active", decodeInstallation, provision),
272 ),
273 getOrProvision: () =>
274 effectTry("get or provision installation", () => {
275 const installation = store.read("installation", "active", decodeInstallation, provision);
276 store.write("installation", "active", installation);
277 return installation;
278 }),
279 };
280};
281
282const createScopeConfigStore = (
283 store: SqliteDocumentStore,
284): ExecutorScopeConfigBackend => ({
285 load: () =>
286 effectTry("load workspace config", () => {
287 const projectConfig = store.read(
288 "scope_config",
289 "project",
290 decodeConfig,
291 () => defaultProjectConfig,
292 );
293
294 return {
295 config: projectConfig,
296 homeConfig: null,
297 projectConfig,
298 };
299 }),
300 writeProject: (config) =>
301 effectTry("write workspace config", () => {
302 store.write("scope_config", "project", config);
303 }),
304 resolveRelativePath: ({ path, scopeRoot }) =>
305 resolvePath(scopeRoot, path),
306});
307
308const createScopeStateStore = (
309 store: SqliteDocumentStore,
310): ExecutorScopeStateBackend => ({
311 load: () =>
312 effectTry("load workspace state", () =>
313 store.read("scope_state", "active", (value) => value as ReturnType<typeof defaultScopeState>, defaultScopeState),
314 ),
315 write: (state) =>
316 effectTry("write workspace state", () => {
317 store.write("scope_state", "active", state);
318 }),
319});
320
321const createSourceArtifactStore = (
322 store: SqliteDocumentStore,
323): ExecutorSourceArtifactBackend => ({
324 build: makeSourceArtifact,
325 read: (sourceId) =>
326 effectTry("read source artifact", () =>
327 store.read(
328 "source_artifact",
329 sourceId,
330 (value) => value as SourceArtifact,
331 () => null,
332 ),
333 ),
334 write: ({ sourceId, artifact }) =>
335 effectTry("write source artifact", () => {
336 store.write("source_artifact", sourceId, artifact);
337 }),
338 remove: (sourceId) =>
339 effectTry("remove source artifact", () => {
340 store.remove("source_artifact", sourceId);
341 }),
342});
343
344const createExecutorStateStore = (
345 store: SqliteDocumentStore,
346): ExecutorStateBackend => {
347 const readCollection = <K extends keyof ExecutorStateCollections>(
348 key: K,
349 ): ExecutorStateCollections[K] => {
350 switch (key) {
351 case "authArtifacts":
352 return store.read("executor_state", key, decodeAuthArtifacts, () => []) as ExecutorStateCollections[K];
353 case "authLeases":
354 return store.read("executor_state", key, decodeAuthLeases, () => []) as ExecutorStateCollections[K];
355 case "sourceOauthClients":
356 return store.read("executor_state", key, decodeSourceOauthClients, () => []) as ExecutorStateCollections[K];
357 case "scopeOauthClients":
358 return store.read("executor_state", key, decodeScopeOauthClients, () => []) as ExecutorStateCollections[K];
359 case "providerAuthGrants":
360 return store.read("executor_state", key, decodeProviderAuthGrants, () => []) as ExecutorStateCollections[K];
361 case "sourceAuthSessions":
362 return store.read("executor_state", key, decodeSourceAuthSessions, () => []) as ExecutorStateCollections[K];
363 case "secretMaterials":
364 return store.read("executor_state", key, decodeSecretMaterials, () => []) as ExecutorStateCollections[K];
365 case "executions":
366 return store.read("executor_state", key, decodeExecutions, () => []) as ExecutorStateCollections[K];
367 case "executionInteractions":
368 return store.read("executor_state", key, decodeExecutionInteractions, () => []) as ExecutorStateCollections[K];
369 case "executionSteps":
370 return store.read("executor_state", key, decodeExecutionSteps, () => []) as ExecutorStateCollections[K];
371 }
372 };
373
374 const writeCollection = <K extends keyof ExecutorStateCollections>(
375 key: K,
376 values: ExecutorStateCollections[K],
377 ) => {
378 store.write("executor_state", key, values);
379 };
380
381 const updateCollection = <K extends keyof ExecutorStateCollections, Result>(
382 key: K,
383 mutate: (values: Array<ExecutorStateCollections[K][number]>) => Result,
384 ): Result => {
385 const nextValues = [...readCollection(key)] as Array<ExecutorStateCollections[K][number]>;
386 const result = mutate(nextValues);
387 writeCollection(key, nextValues as ExecutorStateCollections[K]);
388 return result;
389 };
390
391 return {
392 authArtifacts: {
393 listByScopeId: (scopeId) =>
394 effectTry("list auth artifacts by workspace", () =>
395 readCollection("authArtifacts").filter((item) => item.scopeId === scopeId),
396 ),
397 listByScopeAndSourceId: ({ scopeId, sourceId }) =>
398 effectTry("list auth artifacts by source", () =>
399 readCollection("authArtifacts").filter((item) =>
400 item.scopeId === scopeId && item.sourceId === sourceId
401 ),
402 ),
403 getByScopeSourceAndActor: ({ scopeId, sourceId, actorScopeId, slot }) =>
404 effectTry("get auth artifact", () =>
405 Option.fromNullable(
406 readCollection("authArtifacts").find((item) =>
407 item.scopeId === scopeId
408 && item.sourceId === sourceId
409 && sameActor(item.actorScopeId, actorScopeId)
410 && item.slot === slot
411 ),
412 ),
413 ),
414 upsert: (artifact) =>
415 effectTry("upsert auth artifact", () => {
416 updateCollection("authArtifacts", (items) => {
417 const index = items.findIndex((item) =>
418 item.scopeId === artifact.scopeId
419 && item.sourceId === artifact.sourceId
420 && sameActor(item.actorScopeId, artifact.actorScopeId)
421 && item.slot === artifact.slot
422 );
423 if (index >= 0) {
424 items[index] = artifact;
425 } else {
426 items.push(artifact);
427 }
428 });
429 }),
430 removeByScopeSourceAndActor: ({ scopeId, sourceId, actorScopeId, slot }) =>
431 effectTry("remove auth artifact", () =>
432 updateCollection("authArtifacts", (items) => {
433 const before = items.length;
434 const remaining = items.filter((item) => !(
435 item.scopeId === scopeId
436 && item.sourceId === sourceId
437 && sameActor(item.actorScopeId, actorScopeId)
438 && (slot === undefined || item.slot === slot)
439 ));
440 items.splice(0, items.length, ...remaining);
441 return before !== remaining.length;
442 }),
443 ),
444 removeByScopeAndSourceId: ({ scopeId, sourceId }) =>
445 effectTry("remove auth artifacts by source", () =>
446 updateCollection("authArtifacts", (items) => {
447 const remaining = items.filter((item) =>
448 !(item.scopeId === scopeId && item.sourceId === sourceId)
449 );
450 const removed = items.length - remaining.length;
451 items.splice(0, items.length, ...remaining);
452 return removed;
453 }),
454 ),
455 },
456 authLeases: {
457 listAll: () =>
458 effectTry("list auth leases", () => readCollection("authLeases")),
459 getByAuthArtifactId: (authArtifactId) =>
460 effectTry("get auth lease", () =>
461 Option.fromNullable(
462 readCollection("authLeases").find((item) => item.authArtifactId === authArtifactId),
463 ),
464 ),
465 upsert: (lease) =>
466 effectTry("upsert auth lease", () => {
467 updateCollection("authLeases", (items) => {
468 const index = items.findIndex((item) => item.authArtifactId === lease.authArtifactId);
469 if (index >= 0) {
470 items[index] = lease;
471 } else {
472 items.push(lease);
473 }
474 });
475 }),
476 removeByAuthArtifactId: (authArtifactId) =>
477 effectTry("remove auth lease", () =>
478 updateCollection("authLeases", (items) => {
479 const remaining = items.filter((item) => item.authArtifactId !== authArtifactId);
480 const removed = remaining.length !== items.length;
481 items.splice(0, items.length, ...remaining);
482 return removed;
483 }),
484 ),
485 },
486 sourceOauthClients: {
487 getByScopeSourceAndProvider: ({ scopeId, sourceId, providerKey }) =>
488 effectTry("get source oauth client", () =>
489 Option.fromNullable(
490 readCollection("sourceOauthClients").find((item) =>
491 item.scopeId === scopeId
492 && item.sourceId === sourceId
493 && item.providerKey === providerKey
494 ),
495 ),
496 ),
497 upsert: (oauthClient) =>
498 effectTry("upsert source oauth client", () => {
499 updateCollection("sourceOauthClients", (items) => {
500 const index = items.findIndex((item) =>
501 item.scopeId === oauthClient.scopeId
502 && item.sourceId === oauthClient.sourceId
503 && item.providerKey === oauthClient.providerKey
504 );
505 if (index >= 0) {
506 items[index] = oauthClient;
507 } else {
508 items.push(oauthClient);
509 }
510 });
511 }),
512 removeByScopeAndSourceId: ({ scopeId, sourceId }) =>
513 effectTry("remove source oauth clients", () =>
514 updateCollection("sourceOauthClients", (items) => {
515 const remaining = items.filter((item) =>
516 !(item.scopeId === scopeId && item.sourceId === sourceId)
517 );
518 const removed = items.length - remaining.length;
519 items.splice(0, items.length, ...remaining);
520 return removed;
521 }),
522 ),
523 },
524 scopeOauthClients: {
525 listByScopeAndProvider: ({ scopeId, providerKey }) =>
526 effectTry("list workspace oauth clients", () =>
527 readCollection("scopeOauthClients").filter((item) =>
528 item.scopeId === scopeId && item.providerKey === providerKey
529 ),
530 ),
531 getById: (id) =>
532 effectTry("get workspace oauth client", () =>
533 Option.fromNullable(
534 readCollection("scopeOauthClients").find((item) => item.id === id),
535 ),
536 ),
537 upsert: (oauthClient) =>
538 effectTry("upsert workspace oauth client", () => {
539 updateCollection("scopeOauthClients", (items) => {
540 const index = items.findIndex((item) => item.id === oauthClient.id);
541 if (index >= 0) {
542 items[index] = oauthClient;
543 } else {
544 items.push(oauthClient);
545 }
546 });
547 }),
548 removeById: (id) =>
549 effectTry("remove workspace oauth client", () =>
550 updateCollection("scopeOauthClients", (items) => {
551 const remaining = items.filter((item) => item.id !== id);
552 const removed = remaining.length !== items.length;
553 items.splice(0, items.length, ...remaining);
554 return removed;
555 }),
556 ),
557 },
558 providerAuthGrants: {
559 listByScopeId: (scopeId) =>
560 effectTry("list provider grants by workspace", () =>
561 readCollection("providerAuthGrants").filter((item) => item.scopeId === scopeId),
562 ),
563 listByScopeActorAndProvider: ({ scopeId, actorScopeId, providerKey }) =>
564 effectTry("list provider grants by actor", () =>
565 readCollection("providerAuthGrants").filter((item) =>
566 item.scopeId === scopeId
567 && item.actorScopeId === actorScopeId
568 && item.providerKey === providerKey
569 ),
570 ),
571 getById: (id) =>
572 effectTry("get provider grant", () =>
573 Option.fromNullable(
574 readCollection("providerAuthGrants").find((item) => item.id === id),
575 ),
576 ),
577 upsert: (grant) =>
578 effectTry("upsert provider grant", () => {
579 updateCollection("providerAuthGrants", (items) => {
580 const index = items.findIndex((item) => item.id === grant.id);
581 if (index >= 0) {
582 items[index] = grant;
583 } else {
584 items.push(grant);
585 }
586 });
587 }),
588 removeById: (id) =>
589 effectTry("remove provider grant", () =>
590 updateCollection("providerAuthGrants", (items) => {
591 const remaining = items.filter((item) => item.id !== id);
592 const removed = remaining.length !== items.length;
593 items.splice(0, items.length, ...remaining);
594 return removed;
595 }),
596 ),
597 },
598 sourceAuthSessions: {
599 listAll: () =>
600 effectTry("list source auth sessions", () => readCollection("sourceAuthSessions")),
601 listByScopeId: (scopeId) =>
602 effectTry("list source auth sessions by workspace", () =>
603 readCollection("sourceAuthSessions").filter((item) => item.scopeId === scopeId),
604 ),
605 getById: (id) =>
606 effectTry("get source auth session", () =>
607 Option.fromNullable(
608 readCollection("sourceAuthSessions").find((item) => item.id === id),
609 ),
610 ),
611 getByState: (state) =>
612 effectTry("get source auth session by state", () =>
613 Option.fromNullable(
614 readCollection("sourceAuthSessions").find((item) => item.state === state),
615 ),
616 ),
617 getPendingByScopeSourceAndActor: ({ scopeId, sourceId, actorScopeId, credentialSlot }) =>
618 effectTry("get pending source auth session", () =>
619 Option.fromNullable(
620 readCollection("sourceAuthSessions").find((item) =>
621 item.scopeId === scopeId
622 && item.sourceId === sourceId
623 && item.actorScopeId === actorScopeId
624 && item.status === "pending"
625 && (credentialSlot === undefined || item.credentialSlot === credentialSlot)
626 ),
627 ),
628 ),
629 insert: (session) =>
630 effectTry("insert source auth session", () => {
631 updateCollection("sourceAuthSessions", (items) => {
632 items.push(session);
633 });
634 }),
635 update: (id, patch) =>
636 effectTry("update source auth session", () =>
637 updateCollection("sourceAuthSessions", (items) => {
638 const index = items.findIndex((item) => item.id === id);
639 if (index < 0) {
640 return Option.none();
641 }
642
643 const next = {
644 ...items[index],
645 ...patch,
646 };
647 items[index] = next;
648 return Option.some(next);
649 }),
650 ),
651 upsert: (session) =>
652 effectTry("upsert source auth session", () => {
653 updateCollection("sourceAuthSessions", (items) => {
654 const index = items.findIndex((item) => item.id === session.id);
655 if (index >= 0) {
656 items[index] = session;
657 } else {
658 items.push(session);
659 }
660 });
661 }),
662 removeByScopeAndSourceId: (scopeId, sourceId) =>
663 effectTry("remove source auth sessions", () =>
664 updateCollection("sourceAuthSessions", (items) => {
665 const remaining = items.filter((item) =>
666 !(item.scopeId === scopeId && item.sourceId === sourceId)
667 );
668 const removed = remaining.length !== items.length;
669 items.splice(0, items.length, ...remaining);
670 return removed;
671 }),
672 ),
673 },
674 secretMaterials: {
675 getById: (id) =>
676 effectTry("get secret material", () =>
677 Option.fromNullable(
678 readCollection("secretMaterials").find((item) => item.id === id),
679 ),
680 ),
681 listAll: () =>
682 effectTry("list secret materials", () =>
683 readCollection("secretMaterials").map((item) => ({
684 id: item.id,
685 providerId: item.providerId,
686 name: item.name,
687 purpose: item.purpose,
688 createdAt: item.createdAt,
689 updatedAt: item.updatedAt,
690 })),
691 ),
692 upsert: (material) =>
693 effectTry("upsert secret material", () => {
694 updateCollection("secretMaterials", (items) => {
695 const index = items.findIndex((item) => item.id === material.id);
696 if (index >= 0) {
697 items[index] = material;
698 } else {
699 items.push(material);
700 }
701 });
702 }),
703 updateById: (id, update) =>
704 effectTry("update secret material", () =>
705 updateCollection("secretMaterials", (items) => {
706 const index = items.findIndex((item) => item.id === id);
707 if (index < 0) {
708 return Option.none();
709 }
710
711 const next = {
712 ...items[index],
713 name: update.name === undefined ? items[index]!.name : update.name,
714 value: update.value === undefined ? items[index]!.value : update.value,
715 updatedAt: Date.now(),
716 };
717 items[index] = next;
718 return Option.some({
719 id: next.id,
720 providerId: next.providerId,
721 name: next.name,
722 purpose: next.purpose,
723 createdAt: next.createdAt,
724 updatedAt: next.updatedAt,
725 });
726 }),
727 ),
728 removeById: (id) =>
729 effectTry("remove secret material", () =>
730 updateCollection("secretMaterials", (items) => {
731 const remaining = items.filter((item) => item.id !== id);
732 const removed = remaining.length !== items.length;
733 items.splice(0, items.length, ...remaining);
734 return removed;
735 }),
736 ),
737 },
738 executions: {
739 getById: (executionId) =>
740 effectTry("get execution by id", () =>
741 Option.fromNullable(
742 readCollection("executions").find((item) => item.id === executionId),
743 ),
744 ),
745 getByScopeAndId: (scopeId, executionId) =>
746 effectTry("get execution by scope and id", () =>
747 Option.fromNullable(
748 readCollection("executions").find((item) =>
749 item.scopeId === scopeId && item.id === executionId
750 ),
751 ),
752 ),
753 insert: (execution) =>
754 effectTry("insert execution", () => {
755 updateCollection("executions", (items) => {
756 items.push(execution);
757 });
758 }),
759 update: (executionId, patch) =>
760 effectTry("update execution", () =>
761 updateCollection("executions", (items) => {
762 const index = items.findIndex((item) => item.id === executionId);
763 if (index < 0) {
764 return Option.none();
765 }
766
767 const next = {
768 ...items[index],
769 ...patch,
770 };
771 items[index] = next;
772 return Option.some(next);
773 }),
774 ),
775 },
776 executionInteractions: {
777 getById: (interactionId) =>
778 effectTry("get execution interaction", () =>
779 Option.fromNullable(
780 readCollection("executionInteractions").find((item) => item.id === interactionId),
781 ),
782 ),
783 listByExecutionId: (executionId) =>
784 effectTry("list execution interactions", () =>
785 readCollection("executionInteractions").filter((item) => item.executionId === executionId),
786 ),
787 getPendingByExecutionId: (executionId) =>
788 effectTry("get pending execution interaction", () =>
789 Option.fromNullable(
790 readCollection("executionInteractions").find((item) =>
791 item.executionId === executionId && item.status === "pending"
792 ),
793 ),
794 ),
795 insert: (interaction) =>
796 effectTry("insert execution interaction", () => {
797 updateCollection("executionInteractions", (items) => {
798 items.push(interaction);
799 });
800 }),
801 update: (interactionId, patch) =>
802 effectTry("update execution interaction", () =>
803 updateCollection("executionInteractions", (items) => {
804 const index = items.findIndex((item) => item.id === interactionId);
805 if (index < 0) {
806 return Option.none();
807 }
808
809 const next = {
810 ...items[index],
811 ...patch,
812 };
813 items[index] = next;
814 return Option.some(next);
815 }),
816 ),
817 },
818 executionSteps: {
819 getByExecutionAndSequence: (executionId, sequence) =>
820 effectTry("get execution step", () =>
821 Option.fromNullable(
822 readCollection("executionSteps").find((item) =>
823 item.executionId === executionId && item.sequence === sequence
824 ),
825 ),
826 ),
827 listByExecutionId: (executionId) =>
828 effectTry("list execution steps", () =>
829 readCollection("executionSteps")
830 .filter((item) => item.executionId === executionId)
831 .sort((left, right) => left.sequence - right.sequence),
832 ),
833 insert: (step) =>
834 effectTry("insert execution step", () => {
835 updateCollection("executionSteps", (items) => {
836 items.push(step);
837 });
838 }),
839 deleteByExecutionId: (executionId) =>
840 effectTry("delete execution steps", () => {
841 updateCollection("executionSteps", (items) => {
842 const remaining = items.filter((item) => item.executionId !== executionId);
843 items.splice(0, items.length, ...remaining);
844 });
845 }),
846 updateByExecutionAndSequence: (executionId, sequence, patch) =>
847 effectTry("update execution step", () =>
848 updateCollection("executionSteps", (items) => {
849 const index = items.findIndex((item) =>
850 item.executionId === executionId && item.sequence === sequence
851 );
852 if (index < 0) {
853 return Option.none();
854 }
855
856 const next = {
857 ...items[index],
858 ...patch,
859 };
860 items[index] = next;
861 return Option.some(next);
862 }),
863 ),
864 },
865 };
866};
867
868const createSecretMaterialBackend = (
869 executorState: ExecutorStateBackend,
870): ExecutorSecretMaterialBackend => {
871 const getSecretMaterial = (id: string) =>
872 Effect.flatMap(executorState.secretMaterials.getById(id as SecretMaterial["id"]), (result) =>
873 Option.isSome(result)
874 ? Effect.succeed(result.value)
875 : Effect.fail(new Error(`Secret material ${id} not found`)),
876 );
877
878 return {
879 resolve: ({ ref }) =>
880 Effect.flatMap(getSecretMaterial(ref.handle), (material) => {
881 if (material.value === null) {
882 return Effect.fail(new Error(`Secret material ${material.id} has no stored value`));
883 }
884
885 return Effect.succeed(material.value);
886 }),
887 store: ({ purpose, value, name, providerId }) =>
888 Effect.gen(function* () {
889 const now = Date.now();
890 const id = SecretMaterialIdSchema.make(`secret_${randomUUID()}`);
891 const material: SecretMaterial = {
892 id,
893 providerId: providerId ?? SQLITE_SECRET_PROVIDER_ID,
894 handle: id,
895 name: name ?? null,
896 purpose,
897 value,
898 createdAt: now,
899 updatedAt: now,
900 };
901 yield* executorState.secretMaterials.upsert(material);
902 return {
903 providerId: material.providerId,
904 handle: material.handle,
905 } satisfies SecretRef;
906 }),
907 delete: (ref) =>
908 executorState.secretMaterials.removeById(ref.handle as SecretMaterial["id"]),
909 update: ({ ref, name, value }) =>
910 Effect.flatMap(
911 executorState.secretMaterials.updateById(ref.handle as SecretMaterial["id"], {
912 name,
913 value,
914 }),
915 (result) =>
916 Option.isSome(result)
917 ? Effect.succeed(result.value)
918 : Effect.fail(new Error(`Secret material ${ref.handle} not found`)),
919 ),
920 };
921};
922
923export const createSqliteExecutorBackend = (
924 options: CreateSqliteExecutorBackendOptions = {},
925): ExecutorBackend => {
926 const databasePath = options.databasePath === undefined || options.databasePath === ":memory:"
927 ? options.databasePath ?? ":memory:"
928 : resolvePath(options.databasePath);
929
930 return createExecutorBackend({
931 loadServices: () =>
932 Effect.sync(() => {
933 const store = new SqliteDocumentStore(databasePath);
934 const executorState = createExecutorStateStore(store);
935 const secretMaterial = createSecretMaterialBackend(executorState);
936
937 return {
938 scope: {
939 scopeName: options.scopeName ?? "SQLite SDK Example",
940 scopeRoot: options.scopeRoot ?? null,
941 metadata: {
942 kind: "sqlite",
943 databasePath,
944 },
945 },
946 storage: {
947 installation: createInstallationStore(store, options),
948 scopeConfig: createScopeConfigStore(store),
949 scopeState: createScopeStateStore(store),
950 sourceArtifacts: createSourceArtifactStore(store),
951 executorState,
952 secretMaterial,
953 close: async () => {
954 store.close();
955 },
956 },
957 instanceConfig: {
958 resolve: () =>
959 Effect.succeed({
960 platform: "sqlite-sdk-example",
961 secretProviders: [
962 {
963 id: SQLITE_SECRET_PROVIDER_ID,
964 name: "SQLite",
965 canStore: true,
966 },
967 ],
968 defaultSecretStoreProvider: SQLITE_SECRET_PROVIDER_ID,
969 }),
970 },
971 } satisfies ExecutorBackendServices;
972 }),
973 });
974};
975
976export const createSqliteExecutor = (
977 options: CreateSqliteExecutorBackendOptions & ExecutorRuntimeOptions = {},
978): Promise<Executor> =>
979 createExecutor({
980 ...options,
981 backend: createSqliteExecutorBackend(options),
982 } satisfies CreateExecutorOptions);
examples/sqlite-sdk-consumer/tsconfig.json+0−15
@@ -1,15 +0,0 @@
1{
2 "compilerOptions": {
3 "target": "ES2022",
4 "module": "ESNext",
5 "moduleResolution": "Bundler",
6 "strict": true,
7 "skipLibCheck": true,
8 "noEmit": true,
9 "lib": ["ES2022"],
10 "types": ["bun-types", "node"]
11 },
12 "include": [
13 "src/**/*.ts"
14 ]
15}
packages/platform/sdk/src/backend.ts+237−16
@@ -1,4 +1,5 @@
1import * as Effect from "effect/Effect";1import * as Effect from "effect/Effect";
2import * as Option from "effect/Option";
2 3
3import {4import {
4 createExecutorRuntimeFromServices,5 createExecutorRuntimeFromServices,
@@ -29,16 +30,32 @@ export type ExecutorBackend = { · 24 unmodified lines
29 ) => Effect.Effect<ExecutorRuntime, Error>;30 ) => Effect.Effect<ExecutorRuntime, Error>;
30};31};
31 32
32export type ExecutorInstallationBackend = BoundInstallationStore;33type MaybeEffect<T> = T | Promise<T> | Effect.Effect<T, Error, never>;
33export type ExecutorScopeConfigBackend = BoundScopeConfigStore;34type OptionalValue<T> = T | null | Option.Option<T>;
34export type ExecutorScopeStateBackend = BoundScopeStateStore;35type PublicizeMethod<F> = F extends (...args: infer Args) => Effect.Effect<infer Value, any, any>
35export type ExecutorSourceArtifactBackend = BoundSourceArtifactStore;36 ? [Value] extends [Option.Option<infer Inner>]
36export type ExecutorStateBackend = import("./runtime").ExecutorStateStoreShape;37 ? (...args: Args) => MaybeEffect<OptionalValue<Inner>>
37export type ExecutorLocalToolBackend = BoundLocalToolRuntimeLoader;38 : (...args: Args) => MaybeEffect<Value>
38export type ExecutorSourceTypeDeclarationsBackend =39 : F;
39 BoundSourceTypeDeclarationsRefresher;40type PublicizeObject<T> = {
40export type ExecutorSecretMaterialBackend = RuntimeSecretMaterialServices;41 [Key in keyof T]: T[Key] extends (...args: any[]) => any
41export type ExecutorInstanceConfigBackend = RuntimeInstanceConfigService;42 ? PublicizeMethod<T[Key]>
43 : T[Key] extends object
44 ? PublicizeObject<T[Key]>
45 : T[Key];
46};
47
48export type ExecutorInstallationBackend = PublicizeObject<BoundInstallationStore>;
49export type ExecutorScopeConfigBackend = PublicizeObject<BoundScopeConfigStore>;
50export type ExecutorScopeStateBackend = PublicizeObject<BoundScopeStateStore>;
51export type ExecutorSourceArtifactBackend = PublicizeObject<BoundSourceArtifactStore>;
52export type ExecutorStateBackend = PublicizeObject<import("./runtime").ExecutorStateStoreShape>;
53export type ExecutorLocalToolBackend = PublicizeObject<BoundLocalToolRuntimeLoader>;
54export type ExecutorSourceTypeDeclarationsBackend = PublicizeObject<
55 BoundSourceTypeDeclarationsRefresher
56>;
57export type ExecutorSecretMaterialBackend = PublicizeObject<RuntimeSecretMaterialServices>;
58export type ExecutorInstanceConfigBackend = PublicizeObject<RuntimeInstanceConfigService>;
42 59
43export type ExecutorStorageBackend = {60export type ExecutorStorageBackend = {
44 installation: ExecutorInstallationBackend;61 installation: ExecutorInstallationBackend;
@@ -58,21 +75,225 @@ export type ExecutorBackendServices = { · 13 unmodified lines
58 sourceTypeDeclarations?: ExecutorSourceTypeDeclarationsBackend;75 sourceTypeDeclarations?: ExecutorSourceTypeDeclarationsBackend;
59};76};
60 77
78const toError = (cause: unknown): Error =>
79 cause instanceof Error ? cause : new Error(String(cause));
80
81const toEffect = <T>(value: MaybeEffect<T>): Effect.Effect<T, Error, never> => {
82 if (Effect.isEffect(value)) {
83 return value;
84 }
85
86 if (value instanceof Promise) {
87 return Effect.tryPromise({
88 try: () => value,
89 catch: toError,
90 });
91 }
92
93 return Effect.succeed(value);
94};
95
96const toOptionEffect = <T>(
97 value: MaybeEffect<OptionalValue<T>>,
98): Effect.Effect<Option.Option<T>, Error, never> =>
99 toEffect(value).pipe(
100 Effect.map((result) =>
101 Option.isOption(result) ? result : Option.fromNullable(result),
102 ),
103 );
104
105const toInstallationBackend = (
106 input: ExecutorInstallationBackend,
107): BoundInstallationStore => ({
108 load: () => toEffect(input.load()),
109 getOrProvision: () => toEffect(input.getOrProvision()),
110});
111
112const toScopeConfigBackend = (
113 input: ExecutorScopeConfigBackend,
114): BoundScopeConfigStore => ({
115 load: () => toEffect(input.load()),
116 writeProject: (config) => toEffect(input.writeProject(config)),
117 resolveRelativePath: input.resolveRelativePath,
118});
119
120const toScopeStateBackend = (
121 input: ExecutorScopeStateBackend,
122): BoundScopeStateStore => ({
123 load: () => toEffect(input.load()),
124 write: (state) => toEffect(input.write(state)),
125});
126
127const toSourceArtifactBackend = (
128 input: ExecutorSourceArtifactBackend,
129): BoundSourceArtifactStore => ({
130 build: input.build,
131 read: (sourceId) => toEffect(input.read(sourceId)),
132 write: (payload) => toEffect(input.write(payload)),
133 remove: (sourceId) => toEffect(input.remove(sourceId)),
134});
135
136const toSecretMaterialBackend = (
137 input: ExecutorSecretMaterialBackend,
138): RuntimeSecretMaterialServices => ({
139 resolve: (payload) => toEffect(input.resolve(payload)),
140 store: (payload) => toEffect(input.store(payload)),
141 delete: (payload) => toEffect(input.delete(payload)),
142 update: (payload) => toEffect(input.update(payload)),
143});
144
145const toInstanceConfigBackend = (
146 input: ExecutorInstanceConfigBackend,
147): RuntimeInstanceConfigService => ({
148 resolve: () => toEffect(input.resolve()),
149});
150
151const toLocalToolBackend = (
152 input: ExecutorLocalToolBackend,
153): BoundLocalToolRuntimeLoader => ({
154 load: () => toEffect(input.load()),
155});
156
157const toSourceTypeDeclarationsBackend = (
158 input: ExecutorSourceTypeDeclarationsBackend,
159): BoundSourceTypeDeclarationsRefresher => ({
160 refreshWorkspaceInBackground: (payload) =>
161 toEffect(input.refreshWorkspaceInBackground(payload)).pipe(Effect.orDie),
162 refreshSourceInBackground: (payload) =>
163 toEffect(input.refreshSourceInBackground(payload)).pipe(Effect.orDie),
164});
165
166const toExecutorStateBackend = (
167 input: ExecutorStateBackend,
168): import("./runtime").ExecutorStateStoreShape => ({
169 authArtifacts: {
170 listByScopeId: (scopeId) => toEffect(input.authArtifacts.listByScopeId(scopeId)),
171 listByScopeAndSourceId: (payload) =>
172 toEffect(input.authArtifacts.listByScopeAndSourceId(payload)),
173 getByScopeSourceAndActor: (payload) =>
174 toOptionEffect(input.authArtifacts.getByScopeSourceAndActor(payload)),
175 upsert: (artifact) => toEffect(input.authArtifacts.upsert(artifact)),
176 removeByScopeSourceAndActor: (payload) =>
177 toEffect(input.authArtifacts.removeByScopeSourceAndActor(payload)),
178 removeByScopeAndSourceId: (payload) =>
179 toEffect(input.authArtifacts.removeByScopeAndSourceId(payload)),
180 },
181 authLeases: {
182 listAll: () => toEffect(input.authLeases.listAll()),
183 getByAuthArtifactId: (authArtifactId) =>
184 toOptionEffect(input.authLeases.getByAuthArtifactId(authArtifactId)),
185 upsert: (lease) => toEffect(input.authLeases.upsert(lease)),
186 removeByAuthArtifactId: (authArtifactId) =>
187 toEffect(input.authLeases.removeByAuthArtifactId(authArtifactId)),
188 },
189 sourceOauthClients: {
190 getByScopeSourceAndProvider: (payload) =>
191 toOptionEffect(input.sourceOauthClients.getByScopeSourceAndProvider(payload)),
192 upsert: (oauthClient) => toEffect(input.sourceOauthClients.upsert(oauthClient)),
193 removeByScopeAndSourceId: (payload) =>
194 toEffect(input.sourceOauthClients.removeByScopeAndSourceId(payload)),
195 },
196 scopeOauthClients: {
197 listByScopeAndProvider: (payload) =>
198 toEffect(input.scopeOauthClients.listByScopeAndProvider(payload)),
199 getById: (id) => toOptionEffect(input.scopeOauthClients.getById(id)),
200 upsert: (oauthClient) => toEffect(input.scopeOauthClients.upsert(oauthClient)),
201 removeById: (id) => toEffect(input.scopeOauthClients.removeById(id)),
202 },
203 providerAuthGrants: {
204 listByScopeId: (scopeId) =>
205 toEffect(input.providerAuthGrants.listByScopeId(scopeId)),
206 listByScopeActorAndProvider: (payload) =>
207 toEffect(input.providerAuthGrants.listByScopeActorAndProvider(payload)),
208 getById: (id) => toOptionEffect(input.providerAuthGrants.getById(id)),
209 upsert: (grant) => toEffect(input.providerAuthGrants.upsert(grant)),
210 removeById: (id) => toEffect(input.providerAuthGrants.removeById(id)),
211 },
212 sourceAuthSessions: {
213 listAll: () => toEffect(input.sourceAuthSessions.listAll()),
214 listByScopeId: (scopeId) => toEffect(input.sourceAuthSessions.listByScopeId(scopeId)),
215 getById: (id) => toOptionEffect(input.sourceAuthSessions.getById(id)),
216 getByState: (state) => toOptionEffect(input.sourceAuthSessions.getByState(state)),
217 getPendingByScopeSourceAndActor: (payload) =>
218 toOptionEffect(input.sourceAuthSessions.getPendingByScopeSourceAndActor(payload)),
219 insert: (session) => toEffect(input.sourceAuthSessions.insert(session)),
220 update: (id, patch) => toOptionEffect(input.sourceAuthSessions.update(id, patch)),
221 upsert: (session) => toEffect(input.sourceAuthSessions.upsert(session)),
222 removeByScopeAndSourceId: (scopeId, sourceId) =>
223 toEffect(input.sourceAuthSessions.removeByScopeAndSourceId(scopeId, sourceId)),
224 },
225 secretMaterials: {
226 getById: (id) => toOptionEffect(input.secretMaterials.getById(id)),
227 listAll: () => toEffect(input.secretMaterials.listAll()),
228 upsert: (material) => toEffect(input.secretMaterials.upsert(material)),
229 updateById: (id, patch) => toOptionEffect(input.secretMaterials.updateById(id, patch)),
230 removeById: (id) => toEffect(input.secretMaterials.removeById(id)),
231 },
232 executions: {
233 getById: (executionId) => toOptionEffect(input.executions.getById(executionId)),
234 getByScopeAndId: (scopeId, executionId) =>
235 toOptionEffect(input.executions.getByScopeAndId(scopeId, executionId)),
236 insert: (execution) => toEffect(input.executions.insert(execution)),
237 update: (executionId, patch) =>
238 toOptionEffect(input.executions.update(executionId, patch)),
239 },
240 executionInteractions: {
241 getById: (interactionId) =>
242 toOptionEffect(input.executionInteractions.getById(interactionId)),
243 listByExecutionId: (executionId) =>
244 toEffect(input.executionInteractions.listByExecutionId(executionId)),
245 getPendingByExecutionId: (executionId) =>
246 toOptionEffect(input.executionInteractions.getPendingByExecutionId(executionId)),
247 insert: (interaction) => toEffect(input.executionInteractions.insert(interaction)),
248 update: (interactionId, patch) =>
249 toOptionEffect(input.executionInteractions.update(interactionId, patch)),
250 },
251 executionSteps: {
252 getByExecutionAndSequence: (executionId, sequence) =>
253 toOptionEffect(input.executionSteps.getByExecutionAndSequence(executionId, sequence)),
254 listByExecutionId: (executionId) =>
255 toEffect(input.executionSteps.listByExecutionId(executionId)),
256 insert: (step) => toEffect(input.executionSteps.insert(step)),
257 deleteByExecutionId: (executionId) =>
258 toEffect(input.executionSteps.deleteByExecutionId(executionId)),
259 updateByExecutionAndSequence: (executionId, sequence, patch) =>
260 toOptionEffect(
261 input.executionSteps.updateByExecutionAndSequence(
262 executionId,
263 sequence,
264 patch,
265 ),
266 ),
267 },
268});
269
61export const createExecutorBackend = (input: {270export const createExecutorBackend = (input: {
62 loadServices: (271 loadServices: (
63 options: ExecutorRuntimeOptions,272 options: ExecutorRuntimeOptions,
64 ) => Effect.Effect<ExecutorBackendServices, Error>;273 ) => MaybeEffect<ExecutorBackendServices>;
65}): ExecutorBackend => ({274}): ExecutorBackend => ({
66 createRuntime: (options) =>275 createRuntime: (options) =>
67 Effect.flatMap(input.loadServices(options), (services) =>276 Effect.flatMap(toEffect(input.loadServices(options)), (services) =>
68 createExecutorRuntimeFromServices({277 createExecutorRuntimeFromServices({
69 ...options,278 ...options,
70 services: {279 services: {
71 scope: services.scope,280 scope: services.scope,
72 storage: services.storage satisfies RuntimeStorageServices,281 storage: {
73 localToolRuntimeLoader: services.localTools,282 installation: toInstallationBackend(services.storage.installation),
74 sourceTypeDeclarationsRefresher: services.sourceTypeDeclarations,283 scopeConfig: toScopeConfigBackend(services.storage.scopeConfig),
75 instanceConfig: services.instanceConfig,284 scopeState: toScopeStateBackend(services.storage.scopeState),
285 sourceArtifacts: toSourceArtifactBackend(services.storage.sourceArtifacts),
286 executorState: toExecutorStateBackend(services.storage.executorState),
287 secretMaterial: toSecretMaterialBackend(services.storage.secretMaterial),
288 close: services.storage.close,
289 } satisfies RuntimeStorageServices,
290 localToolRuntimeLoader: services.localTools
291 ? toLocalToolBackend(services.localTools)
292 : undefined,
293 sourceTypeDeclarationsRefresher: services.sourceTypeDeclarations
294 ? toSourceTypeDeclarationsBackend(services.sourceTypeDeclarations)
295 : undefined,
296 instanceConfig: toInstanceConfigBackend(services.instanceConfig),
76 },297 },
77 }),298 }),
78 ),299 ),

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t