@executor-js/sdk 1.4.28 → 1.4.30

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 (142) hide show
  1. package/dist/blob.d.ts +2 -1
  2. package/dist/blob.d.ts.map +1 -1
  3. package/dist/chunk-2LU3SC64.js +7 -0
  4. package/dist/chunk-2LU3SC64.js.map +1 -0
  5. package/dist/chunk-7D4SUZUM.js +38 -0
  6. package/dist/chunk-7D4SUZUM.js.map +1 -0
  7. package/dist/chunk-B4YGTZF6.js +22 -0
  8. package/dist/chunk-B4YGTZF6.js.map +1 -0
  9. package/dist/chunk-BZNIRSMG.js +7873 -0
  10. package/dist/chunk-BZNIRSMG.js.map +1 -0
  11. package/dist/chunk-ECMCGZYE.js +5814 -0
  12. package/dist/chunk-ECMCGZYE.js.map +1 -0
  13. package/dist/{chunk-6SQWMOM4.js → chunk-MLNVNVRI.js} +2 -6
  14. package/dist/chunk-MLNVNVRI.js.map +1 -0
  15. package/dist/chunk-P2WKYAC5.js +1 -0
  16. package/dist/chunk-P2WKYAC5.js.map +1 -0
  17. package/dist/chunk-ZANQD7E2.js +1035 -0
  18. package/dist/chunk-ZANQD7E2.js.map +1 -0
  19. package/dist/client.d.ts +3 -0
  20. package/dist/client.d.ts.map +1 -1
  21. package/dist/client.js +2 -0
  22. package/dist/client.js.map +1 -1
  23. package/dist/config.d.ts +3 -7
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/core-schema.d.ts +283 -408
  26. package/dist/core-schema.d.ts.map +1 -1
  27. package/dist/core-tools.d.ts +6 -0
  28. package/dist/core-tools.d.ts.map +1 -0
  29. package/dist/core.js +120 -168
  30. package/dist/core.js.map +1 -1
  31. package/dist/credential-bindings.d.ts +86 -24
  32. package/dist/credential-bindings.d.ts.map +1 -1
  33. package/dist/errors.d.ts +1 -0
  34. package/dist/errors.d.ts.map +1 -1
  35. package/dist/executor.d.ts +46 -12
  36. package/dist/executor.d.ts.map +1 -1
  37. package/dist/fuma-runtime.d.ts +50 -0
  38. package/dist/fuma-runtime.d.ts.map +1 -0
  39. package/dist/http-source.d.ts +109 -0
  40. package/dist/http-source.d.ts.map +1 -0
  41. package/dist/http-source.js +155 -0
  42. package/dist/http-source.js.map +1 -0
  43. package/dist/index.d.ts +17 -12
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +44 -21
  46. package/dist/index.js.map +1 -1
  47. package/dist/oauth-service.d.ts +2 -10
  48. package/dist/oauth-service.d.ts.map +1 -1
  49. package/dist/oauth.d.ts +9 -1
  50. package/dist/oauth.d.ts.map +1 -1
  51. package/dist/plugin-storage.d.ts +40 -0
  52. package/dist/plugin-storage.d.ts.map +1 -0
  53. package/dist/plugin.d.ts +110 -34
  54. package/dist/plugin.d.ts.map +1 -1
  55. package/dist/promise-executor.d.ts +33 -8
  56. package/dist/promise-executor.d.ts.map +1 -1
  57. package/dist/promise.d.ts +3 -3
  58. package/dist/promise.d.ts.map +1 -1
  59. package/dist/schema-refs.d.ts +1 -0
  60. package/dist/schema-refs.d.ts.map +1 -1
  61. package/dist/schema-refs.test.d.ts +2 -0
  62. package/dist/schema-refs.test.d.ts.map +1 -0
  63. package/dist/schema-types.d.ts +22 -8
  64. package/dist/schema-types.d.ts.map +1 -1
  65. package/dist/scope-policy.d.ts +23 -0
  66. package/dist/scope-policy.d.ts.map +1 -0
  67. package/dist/scope-policy.test.d.ts +2 -0
  68. package/dist/scope-policy.test.d.ts.map +1 -0
  69. package/dist/scope.d.ts +10 -0
  70. package/dist/scope.d.ts.map +1 -1
  71. package/dist/secrets.d.ts +1 -1
  72. package/dist/secrets.d.ts.map +1 -1
  73. package/dist/shared.d.ts +13 -0
  74. package/dist/shared.d.ts.map +1 -0
  75. package/dist/shared.js +103 -0
  76. package/dist/shared.js.map +1 -0
  77. package/dist/sqlite-test-db-OK2WQNR5.js +8 -0
  78. package/dist/sqlite-test-db-OK2WQNR5.js.map +1 -0
  79. package/dist/sqlite-test-db.d.ts +22 -0
  80. package/dist/sqlite-test-db.d.ts.map +1 -0
  81. package/dist/test-config.d.ts +45 -4
  82. package/dist/test-config.d.ts.map +1 -1
  83. package/dist/testing/oauth-test-server.d.ts +80 -0
  84. package/dist/testing/oauth-test-server.d.ts.map +1 -0
  85. package/dist/testing.d.ts +4 -1
  86. package/dist/testing.d.ts.map +1 -1
  87. package/dist/testing.js +706 -18
  88. package/dist/testing.js.map +1 -1
  89. package/dist/testing.test.d.ts +2 -0
  90. package/dist/testing.test.d.ts.map +1 -0
  91. package/dist/tool-result.d.ts +22 -0
  92. package/dist/tool-result.d.ts.map +1 -0
  93. package/dist/tool-result.test.d.ts +2 -0
  94. package/dist/tool-result.test.d.ts.map +1 -0
  95. package/dist/types.d.ts +1 -0
  96. package/dist/types.d.ts.map +1 -1
  97. package/dist/vendor/json-schema-to-typescript/applySchemaTyping.d.ts +3 -0
  98. package/dist/vendor/json-schema-to-typescript/applySchemaTyping.d.ts.map +1 -0
  99. package/dist/vendor/json-schema-to-typescript/compat.d.ts +10 -0
  100. package/dist/vendor/json-schema-to-typescript/compat.d.ts.map +1 -0
  101. package/dist/vendor/json-schema-to-typescript/formatter.d.ts +3 -0
  102. package/dist/vendor/json-schema-to-typescript/formatter.d.ts.map +1 -0
  103. package/dist/vendor/json-schema-to-typescript/generator.d.ts +7 -0
  104. package/dist/vendor/json-schema-to-typescript/generator.d.ts.map +1 -0
  105. package/dist/vendor/json-schema-to-typescript/index.d.ts +93 -0
  106. package/dist/vendor/json-schema-to-typescript/index.d.ts.map +1 -0
  107. package/dist/vendor/json-schema-to-typescript/linker.d.ts +8 -0
  108. package/dist/vendor/json-schema-to-typescript/linker.d.ts.map +1 -0
  109. package/dist/vendor/json-schema-to-typescript/normalizer.d.ts +5 -0
  110. package/dist/vendor/json-schema-to-typescript/normalizer.d.ts.map +1 -0
  111. package/dist/vendor/json-schema-to-typescript/optimizer.d.ts +4 -0
  112. package/dist/vendor/json-schema-to-typescript/optimizer.d.ts.map +1 -0
  113. package/dist/vendor/json-schema-to-typescript/optionValidator.d.ts +3 -0
  114. package/dist/vendor/json-schema-to-typescript/optionValidator.d.ts.map +1 -0
  115. package/dist/vendor/json-schema-to-typescript/parser.d.ts +8 -0
  116. package/dist/vendor/json-schema-to-typescript/parser.d.ts.map +1 -0
  117. package/dist/vendor/json-schema-to-typescript/resolver.d.ts +7 -0
  118. package/dist/vendor/json-schema-to-typescript/resolver.d.ts.map +1 -0
  119. package/dist/vendor/json-schema-to-typescript/types/AST.d.ts +108 -0
  120. package/dist/vendor/json-schema-to-typescript/types/AST.d.ts.map +1 -0
  121. package/dist/vendor/json-schema-to-typescript/types/JSONSchema.d.ts +103 -0
  122. package/dist/vendor/json-schema-to-typescript/types/JSONSchema.d.ts.map +1 -0
  123. package/dist/vendor/json-schema-to-typescript/types/json-schema.d.ts +34 -0
  124. package/dist/vendor/json-schema-to-typescript/types/json-schema.d.ts.map +1 -0
  125. package/dist/vendor/json-schema-to-typescript/typesOfSchema.d.ts +11 -0
  126. package/dist/vendor/json-schema-to-typescript/typesOfSchema.d.ts.map +1 -0
  127. package/dist/vendor/json-schema-to-typescript/utils.d.ts +30 -0
  128. package/dist/vendor/json-schema-to-typescript/utils.d.ts.map +1 -0
  129. package/dist/vendor/json-schema-to-typescript/validator.d.ts +3 -0
  130. package/dist/vendor/json-schema-to-typescript/validator.d.ts.map +1 -0
  131. package/package.json +17 -2
  132. package/dist/chunk-6SQWMOM4.js.map +0 -1
  133. package/dist/chunk-I2OEQ2GJ.js +0 -103
  134. package/dist/chunk-I2OEQ2GJ.js.map +0 -1
  135. package/dist/chunk-OIJL6F6M.js +0 -5507
  136. package/dist/chunk-OIJL6F6M.js.map +0 -1
  137. package/dist/error-handling.test.d.ts +0 -2
  138. package/dist/error-handling.test.d.ts.map +0 -1
  139. package/dist/scoped-adapter.d.ts +0 -28
  140. package/dist/scoped-adapter.d.ts.map +0 -1
  141. package/dist/scoped-adapter.test.d.ts +0 -2
  142. package/dist/scoped-adapter.test.d.ts.map +0 -1
package/dist/testing.js CHANGED
@@ -1,21 +1,698 @@
1
1
  import {
2
- makeTestConfig,
3
- memorySecretsPlugin
4
- } from "./chunk-I2OEQ2GJ.js";
5
- import "./chunk-OIJL6F6M.js";
2
+ Scope,
3
+ collectTables,
4
+ createExecutor,
5
+ definePlugin
6
+ } from "./chunk-BZNIRSMG.js";
7
+ import {
8
+ ScopeId
9
+ } from "./chunk-ZANQD7E2.js";
10
+ import {
11
+ createSqliteTestFumaDb
12
+ } from "./chunk-ECMCGZYE.js";
13
+ import "./chunk-7D4SUZUM.js";
6
14
 
7
15
  // src/testing.ts
16
+ import * as NodeHttpServer2 from "@effect/platform-node/NodeHttpServer";
17
+ import { Context as Context3, Data as Data2, Effect as Effect3, Layer as Layer3, Predicate as Predicate2 } from "effect";
18
+ import {
19
+ HttpClient as HttpClient2,
20
+ HttpRouter,
21
+ HttpServer as HttpServer2,
22
+ HttpServerRequest as HttpServerRequest2
23
+ } from "effect/unstable/http";
24
+
25
+ // src/test-config.ts
26
+ import { Context, Effect, Layer } from "effect";
27
+ import { withQueryContext } from "fumadb/query";
28
+ var makeLazyTestFumaDb = (options) => {
29
+ let started;
30
+ const start = () => {
31
+ if (!started) {
32
+ started = import("./sqlite-test-db-OK2WQNR5.js").then(
33
+ ({ createSqliteTestFumaDb: createSqliteTestFumaDb2 }) => createSqliteTestFumaDb2({
34
+ tables: options.tables,
35
+ namespace: "executor_test",
36
+ path: options.dataDir ? `${options.dataDir}/test.db` : void 0
37
+ })
38
+ );
39
+ }
40
+ return started;
41
+ };
42
+ const internal = {
43
+ tables: options.tables,
44
+ count: async (table, value) => (await start()).db.internal.count(table, value),
45
+ create: async (table, values) => (await start()).db.internal.create(table, values),
46
+ createMany: async (table, values) => (await start()).db.internal.createMany(table, values),
47
+ deleteMany: async (table, value) => (await start()).db.internal.deleteMany(table, value),
48
+ findFirst: async (table, value) => (await start()).db.internal.findFirst(table, value),
49
+ findMany: async (table, value) => (await start()).db.internal.findMany(table, value),
50
+ transaction: async (run) => (await start()).db.internal.transaction(run),
51
+ updateMany: async (table, value) => (await start()).db.internal.updateMany(table, value),
52
+ upsert: async (table, value) => (await start()).db.internal.upsert(table, value)
53
+ };
54
+ const queryMethods = /* @__PURE__ */ new Set([
55
+ "count",
56
+ "create",
57
+ "createMany",
58
+ "deleteMany",
59
+ "findFirst",
60
+ "findMany",
61
+ "transaction",
62
+ "updateMany",
63
+ "upsert"
64
+ ]);
65
+ const makeDb = (context) => new Proxy(
66
+ { internal: context === void 0 ? internal : { ...internal, context } },
67
+ {
68
+ get(target, prop) {
69
+ if (prop === "internal") return target.internal;
70
+ if (prop === "withContext") {
71
+ return (nextContext) => makeDb(nextContext);
72
+ }
73
+ if (!queryMethods.has(prop)) return void 0;
74
+ return async (...args) => {
75
+ const actual = await start();
76
+ const actualDb = context === void 0 ? actual.db : withQueryContext(actual.db, context);
77
+ const method = Reflect.get(actualDb, prop);
78
+ return method.apply(actualDb, args);
79
+ };
80
+ }
81
+ }
82
+ );
83
+ const db = makeDb();
84
+ return {
85
+ db,
86
+ warm: async () => {
87
+ await start();
88
+ },
89
+ close: async () => {
90
+ if (!started) return;
91
+ await (await started).close();
92
+ }
93
+ };
94
+ };
95
+ var makeTestConfig = (options) => {
96
+ const scopes = options?.scopes ?? [
97
+ Scope.make({
98
+ id: ScopeId.make("test-scope"),
99
+ name: options?.scopeName ?? "test",
100
+ createdAt: /* @__PURE__ */ new Date()
101
+ })
102
+ ];
103
+ const tables = collectTables(options?.plugins ?? []);
104
+ const testDb = makeLazyTestFumaDb({
105
+ tables,
106
+ backend: options?.backend ?? "sqlite",
107
+ dataDir: options?.dataDir
108
+ });
109
+ const db = withQueryContext(testDb.db, {
110
+ allowedScopeIds: new Set(scopes.map((scope) => String(scope.id)))
111
+ });
112
+ return {
113
+ scopes,
114
+ db,
115
+ plugins: options?.plugins,
116
+ testDb,
117
+ // Tests default to auto-accepting elicitation prompts. Override via
118
+ // a wrapping spread if a test exercises a real handler:
119
+ // { ...makeTestConfig(...), onElicitation: customHandler }
120
+ onElicitation: "accept-all"
121
+ };
122
+ };
123
+ var TestWorkspace = class _TestWorkspace extends Context.Service()(
124
+ "executor-sdk/TestWorkspace"
125
+ ) {
126
+ static current = () => Effect.gen(function* () {
127
+ const workspace = yield* _TestWorkspace;
128
+ return workspace;
129
+ });
130
+ };
131
+ var makeTestWorkspaceHarness = (options) => Effect.acquireRelease(
132
+ Effect.gen(function* () {
133
+ const config = makeTestConfig(options);
134
+ const executor = yield* createExecutor(config);
135
+ return {
136
+ config,
137
+ executor,
138
+ testDb: config.testDb,
139
+ scopes: config.scopes
140
+ };
141
+ }),
142
+ ({ executor, testDb }) => executor.close().pipe(
143
+ Effect.ignore,
144
+ Effect.andThen(Effect.promise(() => testDb.close()).pipe(Effect.ignore))
145
+ )
146
+ );
147
+ var makeTestWorkspaceLayer = (options) => Layer.effect(TestWorkspace)(
148
+ makeTestWorkspaceHarness(options).pipe(
149
+ Effect.tap(({ testDb }) => Effect.promise(() => testDb.warm()))
150
+ )
151
+ );
152
+ var makeTestExecutor = (options) => makeTestWorkspaceHarness(options).pipe(Effect.map(({ executor }) => executor));
153
+ var memorySecretsPlugin = definePlugin(() => {
154
+ const store = /* @__PURE__ */ new Map();
155
+ const provider = {
156
+ key: "memory",
157
+ writable: true,
158
+ get: (id, scope) => Effect.sync(() => store.get(`${scope}\0${id}`) ?? null),
159
+ set: (id, value, scope) => Effect.sync(() => {
160
+ store.set(`${scope}\0${id}`, value);
161
+ }),
162
+ delete: (id, scope) => Effect.sync(() => store.delete(`${scope}\0${id}`)),
163
+ list: () => Effect.sync(
164
+ () => Array.from(store.keys()).map((key) => {
165
+ const name = key.split("\0", 2)[1] ?? key;
166
+ return { id: name, name };
167
+ })
168
+ )
169
+ };
170
+ return {
171
+ id: "memory-secrets",
172
+ storage: () => ({}),
173
+ secretProviders: [provider]
174
+ };
175
+ });
176
+
177
+ // src/testing/oauth-test-server.ts
8
178
  import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
9
- import { Context, Data, Effect, Layer, Predicate } from "effect";
179
+ import { Context as Context2, Data, Effect as Effect2, Layer as Layer2, Option, Predicate, Ref, Schema } from "effect";
180
+ import { createHash, randomUUID } from "crypto";
10
181
  import {
182
+ FetchHttpClient,
11
183
  HttpClient,
12
- HttpRouter,
184
+ HttpClientRequest,
13
185
  HttpServer,
14
- HttpServerRequest
186
+ HttpServerRequest,
187
+ HttpServerResponse
15
188
  } from "effect/unstable/http";
16
- var TestHttpServerAddressError = class extends Data.TaggedError("TestHttpServerAddressError") {
189
+ var OAuthTestServerAddressError = class extends Data.TaggedError("OAuthTestServerAddressError") {
190
+ };
191
+ var OAuthTestServerFlowError = class extends Data.TaggedError("OAuthTestServerFlowError") {
192
+ };
193
+ var JsonObject = Schema.Record(Schema.String, Schema.Unknown);
194
+ var decodeJsonObject = Schema.decodeUnknownOption(Schema.fromJsonString(JsonObject));
195
+ var TokenResponse = Schema.Struct({
196
+ access_token: Schema.String,
197
+ refresh_token: Schema.optional(Schema.String),
198
+ token_type: Schema.String,
199
+ expires_in: Schema.optional(Schema.Number),
200
+ scope: Schema.optional(Schema.String)
201
+ });
202
+ var decodeTokenResponse = Schema.decodeUnknownEffect(TokenResponse);
203
+ var defaultScopes = ["read", "write"];
204
+ var jsonResponse = (status, body, headers = {}) => HttpServerResponse.jsonUnsafe(body, { status, headers });
205
+ var textResponse = (status, body, headers = {}) => HttpServerResponse.text(body, {
206
+ status,
207
+ headers,
208
+ contentType: "text/plain; charset=utf-8"
209
+ });
210
+ var redirectResponse = (location) => HttpServerResponse.redirect(location);
211
+ var parseJsonObject = (body) => {
212
+ const result = decodeJsonObject(body);
213
+ return Option.isSome(result) ? result.value : null;
214
+ };
215
+ var arrayOfStrings = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
216
+ var decodeBasicAuthorization = (value) => {
217
+ if (!value) return null;
218
+ const match = /^Basic\s+(.+)$/i.exec(value);
219
+ if (!match) return null;
220
+ const decoded = Buffer.from(match[1], "base64").toString("utf8");
221
+ const separator = decoded.indexOf(":");
222
+ if (separator < 0) return null;
223
+ return {
224
+ username: decoded.slice(0, separator),
225
+ password: decoded.slice(separator + 1)
226
+ };
227
+ };
228
+ var codeChallengeForVerifier = (verifier) => createHash("sha256").update(verifier).digest("base64url");
229
+ var oauthError = (status, error, errorDescription) => jsonResponse(
230
+ status,
231
+ {
232
+ error,
233
+ error_description: errorDescription
234
+ },
235
+ status === 401 ? { "www-authenticate": 'Basic realm="OAuth test server"' } : {}
236
+ );
237
+ var manualRedirectHttpClientLayer = FetchHttpClient.layer.pipe(
238
+ Layer2.provide(Layer2.succeed(FetchHttpClient.RequestInit, { redirect: "manual" }))
239
+ );
240
+ var executeManualRedirect = (request, requestUrl) => HttpClient.execute(request).pipe(
241
+ Effect2.mapError(
242
+ (cause) => new OAuthTestServerFlowError({
243
+ message: `OAuth test flow request failed for ${requestUrl}`,
244
+ cause
245
+ })
246
+ ),
247
+ Effect2.provide(manualRedirectHttpClientLayer)
248
+ );
249
+ var executeOAuthHttp = (request, requestUrl) => HttpClient.execute(request).pipe(
250
+ Effect2.mapError(
251
+ (cause) => new OAuthTestServerFlowError({
252
+ message: `OAuth test flow request failed for ${requestUrl}`,
253
+ cause
254
+ })
255
+ ),
256
+ Effect2.provide(FetchHttpClient.layer)
257
+ );
258
+ var requiredRedirectLocation = (response, requestUrl) => Effect2.gen(function* () {
259
+ if (response.status < 300 || response.status >= 400) {
260
+ return yield* new OAuthTestServerFlowError({
261
+ message: `Expected redirect from ${requestUrl}, got HTTP ${response.status}`
262
+ });
263
+ }
264
+ const location = response.headers.location;
265
+ if (!location) {
266
+ return yield* new OAuthTestServerFlowError({
267
+ message: `Expected Location header from ${requestUrl}`
268
+ });
269
+ }
270
+ return new URL(location, requestUrl).toString();
271
+ });
272
+ var serveOAuthTestHttpApp = (handler) => Effect2.gen(function* () {
273
+ const context = yield* Layer2.build(
274
+ Layer2.fresh(
275
+ HttpServer.serve(
276
+ HttpServerRequest.HttpServerRequest.asEffect().pipe(Effect2.flatMap(handler))
277
+ ).pipe(Layer2.provideMerge(NodeHttpServer.layerTest))
278
+ )
279
+ ).pipe(Effect2.mapError((address2) => new OAuthTestServerAddressError({ address: address2 })));
280
+ const server = Context2.get(context, HttpServer.HttpServer);
281
+ const address = server.address;
282
+ if (!Predicate.isTagged(address, "TcpAddress")) {
283
+ return yield* new OAuthTestServerAddressError({ address });
284
+ }
285
+ return { baseUrl: `http://127.0.0.1:${address.port}` };
286
+ });
287
+ var requestBodyText = (request) => request.text.pipe(Effect2.catch(() => Effect2.succeed("")));
288
+ var completeAuthorizationCodeFlow = (defaults) => (input) => Effect2.gen(function* () {
289
+ const loginResponse = yield* executeManualRedirect(
290
+ HttpClientRequest.get(input.authorizationUrl),
291
+ input.authorizationUrl
292
+ );
293
+ const loginUrl = yield* requiredRedirectLocation(loginResponse, input.authorizationUrl);
294
+ const credentials = Buffer.from(
295
+ `${input.username ?? defaults.username}:${input.password ?? defaults.password}`
296
+ ).toString("base64");
297
+ const callbackResponse = yield* executeManualRedirect(
298
+ HttpClientRequest.post(loginUrl).pipe(
299
+ HttpClientRequest.setHeader("authorization", `Basic ${credentials}`)
300
+ ),
301
+ loginUrl
302
+ );
303
+ const callbackUrl = yield* requiredRedirectLocation(callbackResponse, loginUrl);
304
+ const parsed = new URL(callbackUrl);
305
+ const state = parsed.searchParams.get("state");
306
+ const code = parsed.searchParams.get("code");
307
+ if (!state || !code) {
308
+ return yield* new OAuthTestServerFlowError({
309
+ message: "OAuth callback did not include both state and code"
310
+ });
311
+ }
312
+ return { callbackUrl, state, code };
313
+ });
314
+ var completeAuthorizationCodeTokenFlow = (defaults) => (input = {}) => Effect2.gen(function* () {
315
+ const clientId = input.clientId ?? defaults.clientId;
316
+ const clientSecret = input.clientSecret ?? defaults.clientSecret;
317
+ const redirectUrl = input.redirectUrl ?? "http://127.0.0.1/callback";
318
+ const codeVerifier = `verifier_${randomUUID()}`;
319
+ const state = `state_${randomUUID()}`;
320
+ const authorizationUrl = new URL(defaults.authorizationEndpoint);
321
+ authorizationUrl.searchParams.set("response_type", "code");
322
+ authorizationUrl.searchParams.set("client_id", clientId);
323
+ authorizationUrl.searchParams.set("redirect_uri", redirectUrl);
324
+ authorizationUrl.searchParams.set("state", state);
325
+ authorizationUrl.searchParams.set("code_challenge", codeChallengeForVerifier(codeVerifier));
326
+ authorizationUrl.searchParams.set("code_challenge_method", "S256");
327
+ if (input.scopes && input.scopes.length > 0) {
328
+ authorizationUrl.searchParams.set("scope", input.scopes.join(" "));
329
+ }
330
+ if (input.resource) {
331
+ authorizationUrl.searchParams.set("resource", input.resource);
332
+ }
333
+ const callback = yield* completeAuthorizationCodeFlow({
334
+ username: defaults.username,
335
+ password: defaults.password
336
+ })({
337
+ authorizationUrl: authorizationUrl.toString(),
338
+ username: input.username,
339
+ password: input.password
340
+ });
341
+ const tokenResponse = yield* executeOAuthHttp(
342
+ HttpClientRequest.post(defaults.tokenEndpoint).pipe(
343
+ HttpClientRequest.bodyUrlParams({
344
+ grant_type: "authorization_code",
345
+ code: callback.code,
346
+ redirect_uri: redirectUrl,
347
+ client_id: clientId,
348
+ client_secret: clientSecret,
349
+ code_verifier: codeVerifier
350
+ })
351
+ ),
352
+ defaults.tokenEndpoint
353
+ );
354
+ if (tokenResponse.status !== 200) {
355
+ const body = yield* tokenResponse.text.pipe(
356
+ Effect2.catch(() => Effect2.succeed("<unavailable>"))
357
+ );
358
+ return yield* new OAuthTestServerFlowError({
359
+ message: `Expected token response HTTP 200, got HTTP ${tokenResponse.status}: ${body}`
360
+ });
361
+ }
362
+ const raw = yield* tokenResponse.json.pipe(
363
+ Effect2.mapError(
364
+ (cause) => new OAuthTestServerFlowError({
365
+ message: "OAuth token response was not valid JSON",
366
+ cause
367
+ })
368
+ )
369
+ );
370
+ const token = yield* decodeTokenResponse(raw).pipe(
371
+ Effect2.mapError(
372
+ (cause) => new OAuthTestServerFlowError({
373
+ message: "OAuth token response did not match the expected shape",
374
+ cause
375
+ })
376
+ )
377
+ );
378
+ return {
379
+ accessToken: token.access_token,
380
+ refreshToken: token.refresh_token,
381
+ tokenType: token.token_type,
382
+ expiresIn: token.expires_in,
383
+ scope: token.scope
384
+ };
385
+ });
386
+ var serveOAuthTestServer = (options = {}) => Effect2.gen(function* () {
387
+ const requests = yield* Ref.make([]);
388
+ const issuedAccessTokens = yield* Ref.make(/* @__PURE__ */ new Set());
389
+ const users = {
390
+ [options.defaultUsername ?? "alice"]: options.defaultPassword ?? "password",
391
+ ...options.users ?? {}
392
+ };
393
+ const supportRefresh = options.supportRefresh ?? true;
394
+ const scopes = options.scopes ?? defaultScopes;
395
+ const clients = /* @__PURE__ */ new Map();
396
+ const transactions = /* @__PURE__ */ new Map();
397
+ const authorizationCodes = /* @__PURE__ */ new Map();
398
+ const refreshTokens = /* @__PURE__ */ new Map();
399
+ const defaultClientId = options.defaultClientId ?? "test-client";
400
+ const defaultClientSecret = options.defaultClientSecret ?? "test-secret";
401
+ clients.set(defaultClientId, {
402
+ clientSecret: defaultClientSecret,
403
+ redirectUris: /* @__PURE__ */ new Set(),
404
+ tokenEndpointAuthMethod: "client_secret_post"
405
+ });
406
+ for (const [clientId, clientSecret] of Object.entries(options.clients ?? {})) {
407
+ clients.set(clientId, {
408
+ clientSecret,
409
+ redirectUris: /* @__PURE__ */ new Set(),
410
+ tokenEndpointAuthMethod: clientSecret === null ? "none" : "client_secret_post"
411
+ });
412
+ }
413
+ let issuerUrl = "";
414
+ const server = yield* serveOAuthTestHttpApp(
415
+ (request) => Effect2.gen(function* () {
416
+ const currentIssuerUrl = issuerUrl || "http://127.0.0.1";
417
+ const requestUrl = new URL(request.url, currentIssuerUrl);
418
+ const body = yield* requestBodyText(request);
419
+ const headers = request.headers;
420
+ yield* Ref.update(requests, (all) => [
421
+ ...all,
422
+ {
423
+ method: request.method,
424
+ url: requestUrl.toString(),
425
+ path: requestUrl.pathname,
426
+ headers,
427
+ body
428
+ }
429
+ ]);
430
+ if (requestUrl.pathname.startsWith("/.well-known/oauth-protected-resource")) {
431
+ const suffix = requestUrl.pathname.slice("/.well-known/oauth-protected-resource".length);
432
+ const resource = `${currentIssuerUrl}${suffix}`;
433
+ return jsonResponse(200, {
434
+ resource,
435
+ authorization_servers: [currentIssuerUrl],
436
+ bearer_methods_supported: ["header"],
437
+ scopes_supported: scopes
438
+ });
439
+ }
440
+ if (requestUrl.pathname === "/.well-known/oauth-authorization-server" || requestUrl.pathname === "/.well-known/openid-configuration") {
441
+ return jsonResponse(200, {
442
+ issuer: currentIssuerUrl,
443
+ authorization_endpoint: `${currentIssuerUrl}/authorize`,
444
+ token_endpoint: `${currentIssuerUrl}/token`,
445
+ registration_endpoint: `${currentIssuerUrl}/register`,
446
+ response_types_supported: ["code"],
447
+ grant_types_supported: ["authorization_code", "refresh_token", "client_credentials"],
448
+ code_challenge_methods_supported: ["S256"],
449
+ token_endpoint_auth_methods_supported: [
450
+ "none",
451
+ "client_secret_post",
452
+ "client_secret_basic"
453
+ ],
454
+ scopes_supported: scopes
455
+ });
456
+ }
457
+ if (requestUrl.pathname === "/register" && request.method === "POST") {
458
+ const json = parseJsonObject(body);
459
+ if (!json) {
460
+ return oauthError(400, "invalid_client_metadata", "Expected JSON body");
461
+ }
462
+ const requestedMethod = typeof json.token_endpoint_auth_method === "string" ? json.token_endpoint_auth_method : "none";
463
+ const clientId = `client_${randomUUID()}`;
464
+ const clientSecret = requestedMethod === "client_secret_basic" || requestedMethod === "client_secret_post" ? `secret_${randomUUID()}` : null;
465
+ const redirectUris = new Set(arrayOfStrings(json.redirect_uris));
466
+ clients.set(clientId, {
467
+ clientSecret,
468
+ redirectUris,
469
+ tokenEndpointAuthMethod: requestedMethod
470
+ });
471
+ return jsonResponse(
472
+ 201,
473
+ {
474
+ client_id: clientId,
475
+ ...clientSecret ? { client_secret: clientSecret } : {},
476
+ client_id_issued_at: Math.floor(Date.now() / 1e3),
477
+ token_endpoint_auth_method: requestedMethod,
478
+ redirect_uris: [...redirectUris],
479
+ grant_types: arrayOfStrings(json.grant_types),
480
+ response_types: arrayOfStrings(json.response_types),
481
+ scope: typeof json.scope === "string" ? json.scope : scopes.join(" ")
482
+ },
483
+ { "cache-control": "no-store" }
484
+ );
485
+ }
486
+ if (requestUrl.pathname === "/authorize" && request.method === "GET") {
487
+ const clientId = requestUrl.searchParams.get("client_id");
488
+ const redirectUri = requestUrl.searchParams.get("redirect_uri");
489
+ const state = requestUrl.searchParams.get("state");
490
+ const codeChallenge = requestUrl.searchParams.get("code_challenge");
491
+ const responseType = requestUrl.searchParams.get("response_type");
492
+ if (!clientId || !redirectUri || !state || !codeChallenge || responseType !== "code") {
493
+ return oauthError(400, "invalid_request", "Missing authorization parameters");
494
+ }
495
+ const client = clients.get(clientId);
496
+ if (client && client.redirectUris.size > 0 && !client.redirectUris.has(redirectUri)) {
497
+ return oauthError(400, "invalid_request", "redirect_uri is not registered");
498
+ }
499
+ if (!client) {
500
+ clients.set(clientId, {
501
+ clientSecret: null,
502
+ redirectUris: /* @__PURE__ */ new Set([redirectUri]),
503
+ tokenEndpointAuthMethod: "none"
504
+ });
505
+ }
506
+ const transaction = `txn_${randomUUID()}`;
507
+ transactions.set(transaction, {
508
+ clientId,
509
+ redirectUri,
510
+ state,
511
+ codeChallenge,
512
+ scope: requestUrl.searchParams.get("scope"),
513
+ resource: requestUrl.searchParams.get("resource")
514
+ });
515
+ return redirectResponse(
516
+ `${currentIssuerUrl}/login?transaction=${encodeURIComponent(transaction)}`
517
+ );
518
+ }
519
+ if (requestUrl.pathname === "/login") {
520
+ const transactionId = requestUrl.searchParams.get("transaction");
521
+ const transaction = transactionId ? transactions.get(transactionId) : void 0;
522
+ if (!transactionId || !transaction) {
523
+ return oauthError(400, "invalid_request", "Unknown login transaction");
524
+ }
525
+ if (request.method === "GET") {
526
+ return textResponse(200, "OAuth test login");
527
+ }
528
+ const basic = decodeBasicAuthorization(headers.authorization);
529
+ if (!basic || users[basic.username] !== basic.password) {
530
+ return jsonResponse(
531
+ 401,
532
+ { error: "access_denied" },
533
+ { "www-authenticate": 'Basic realm="OAuth test server"' }
534
+ );
535
+ }
536
+ const code = `code_${randomUUID()}`;
537
+ transactions.delete(transactionId);
538
+ authorizationCodes.set(code, { ...transaction, username: basic.username });
539
+ const callbackUrl = new URL(transaction.redirectUri);
540
+ callbackUrl.searchParams.set("code", code);
541
+ callbackUrl.searchParams.set("state", transaction.state);
542
+ return redirectResponse(callbackUrl.toString());
543
+ }
544
+ if (requestUrl.pathname === "/token" && request.method === "POST") {
545
+ const params = new URLSearchParams(body);
546
+ const basic = decodeBasicAuthorization(headers.authorization);
547
+ const clientId = basic?.username ?? params.get("client_id");
548
+ const clientSecret = basic?.password ?? params.get("client_secret");
549
+ const client = clientId ? clients.get(clientId) : void 0;
550
+ if (!clientId || !client) {
551
+ return oauthError(401, "invalid_client", "Unknown client");
552
+ }
553
+ if (client.clientSecret !== null && client.clientSecret !== clientSecret) {
554
+ return oauthError(401, "invalid_client", "Invalid client secret");
555
+ }
556
+ const grantType = params.get("grant_type");
557
+ if (grantType === "authorization_code") {
558
+ const code = params.get("code");
559
+ const redirectUri = params.get("redirect_uri");
560
+ const codeVerifier = params.get("code_verifier");
561
+ const record = code ? authorizationCodes.get(code) : void 0;
562
+ if (!code || !redirectUri || !codeVerifier || !record) {
563
+ return oauthError(400, "invalid_grant", "Unknown authorization code");
564
+ }
565
+ if (record.clientId !== clientId || record.redirectUri !== redirectUri || record.codeChallenge !== codeChallengeForVerifier(codeVerifier)) {
566
+ return oauthError(400, "invalid_grant", "Authorization code validation failed");
567
+ }
568
+ authorizationCodes.delete(code);
569
+ const accessToken = `at_${randomUUID()}`;
570
+ const refreshToken = `rt_${randomUUID()}`;
571
+ yield* Ref.update(issuedAccessTokens, (tokens) => /* @__PURE__ */ new Set([...tokens, accessToken]));
572
+ refreshTokens.set(refreshToken, {
573
+ clientId,
574
+ username: record.username,
575
+ scope: record.scope,
576
+ resource: record.resource
577
+ });
578
+ return jsonResponse(
579
+ 200,
580
+ {
581
+ access_token: accessToken,
582
+ refresh_token: refreshToken,
583
+ token_type: "Bearer",
584
+ expires_in: 3600,
585
+ ...record.scope ? { scope: record.scope } : {}
586
+ },
587
+ { "cache-control": "no-store" }
588
+ );
589
+ }
590
+ if (grantType === "refresh_token") {
591
+ const refreshToken = params.get("refresh_token");
592
+ const record = refreshToken ? refreshTokens.get(refreshToken) : void 0;
593
+ if (!supportRefresh || !refreshToken || !record || record.clientId !== clientId) {
594
+ return oauthError(400, "invalid_grant", "Unknown refresh token");
595
+ }
596
+ const nextAccessToken = `at_${randomUUID()}`;
597
+ const nextRefreshToken = `rt_${randomUUID()}`;
598
+ refreshTokens.delete(refreshToken);
599
+ refreshTokens.set(nextRefreshToken, record);
600
+ yield* Ref.update(
601
+ issuedAccessTokens,
602
+ (tokens) => /* @__PURE__ */ new Set([...tokens, nextAccessToken])
603
+ );
604
+ return jsonResponse(
605
+ 200,
606
+ {
607
+ access_token: nextAccessToken,
608
+ refresh_token: nextRefreshToken,
609
+ token_type: "Bearer",
610
+ expires_in: 3600,
611
+ ...record.scope ? { scope: record.scope } : {}
612
+ },
613
+ { "cache-control": "no-store" }
614
+ );
615
+ }
616
+ if (grantType === "client_credentials") {
617
+ const accessToken = `at_${randomUUID()}`;
618
+ yield* Ref.update(issuedAccessTokens, (tokens) => /* @__PURE__ */ new Set([...tokens, accessToken]));
619
+ return jsonResponse(
620
+ 200,
621
+ {
622
+ access_token: accessToken,
623
+ token_type: "Bearer",
624
+ expires_in: 3600,
625
+ scope: params.get("scope") ?? scopes.join(" ")
626
+ },
627
+ { "cache-control": "no-store" }
628
+ );
629
+ }
630
+ return oauthError(400, "unsupported_grant_type", "Unsupported grant type");
631
+ }
632
+ if (requestUrl.pathname === "/mcp") {
633
+ const authorization = headers.authorization;
634
+ const token = authorization?.replace(/^Bearer\s+/i, "");
635
+ const valid = token ? yield* Ref.get(issuedAccessTokens).pipe(Effect2.map((tokens) => tokens.has(token))) : false;
636
+ if (!valid) {
637
+ return jsonResponse(
638
+ 401,
639
+ { error: "invalid_token" },
640
+ {
641
+ "www-authenticate": `Bearer resource_metadata="${currentIssuerUrl}/.well-known/oauth-protected-resource/mcp", error="invalid_token"`
642
+ }
643
+ );
644
+ }
645
+ return jsonResponse(200, {
646
+ jsonrpc: "2.0",
647
+ id: 1,
648
+ result: { protocolVersion: "2025-06-18", capabilities: {} }
649
+ });
650
+ }
651
+ return jsonResponse(404, { error: "not_found" });
652
+ })
653
+ );
654
+ issuerUrl = server.baseUrl;
655
+ const accessTokenSet = Ref.get(issuedAccessTokens);
656
+ return {
657
+ issuerUrl,
658
+ authorizationEndpoint: `${issuerUrl}/authorize`,
659
+ tokenEndpoint: `${issuerUrl}/token`,
660
+ registrationEndpoint: `${issuerUrl}/register`,
661
+ protectedResourceMetadataUrl: `${issuerUrl}/.well-known/oauth-protected-resource`,
662
+ resourceUrl: issuerUrl,
663
+ mcpResourceUrl: `${issuerUrl}/mcp`,
664
+ completeAuthorizationCodeFlow: completeAuthorizationCodeFlow({
665
+ username: options.defaultUsername ?? "alice",
666
+ password: options.defaultPassword ?? "password"
667
+ }),
668
+ completeAuthorizationCodeTokenFlow: completeAuthorizationCodeTokenFlow({
669
+ username: options.defaultUsername ?? "alice",
670
+ password: options.defaultPassword ?? "password",
671
+ clientId: defaultClientId,
672
+ clientSecret: defaultClientSecret,
673
+ authorizationEndpoint: `${issuerUrl}/authorize`,
674
+ tokenEndpoint: `${issuerUrl}/token`
675
+ }),
676
+ requests: Ref.get(requests),
677
+ clearRequests: Ref.set(requests, []),
678
+ issuedAccessTokens: accessTokenSet.pipe(Effect2.map((tokens) => [...tokens])),
679
+ acceptsAccessToken: (token) => accessTokenSet.pipe(Effect2.map((tokens) => tokens.has(token))),
680
+ acceptsAuthorizationHeader: (authorization) => {
681
+ const token = authorization?.replace(/^Bearer\s+/i, "");
682
+ return token ? accessTokenSet.pipe(Effect2.map((tokens) => tokens.has(token))) : Effect2.succeed(false);
683
+ }
684
+ };
685
+ });
686
+ var OAuthTestServer = class _OAuthTestServer extends Context2.Service()(
687
+ "@executor-js/sdk/testing/OAuthTestServer"
688
+ ) {
689
+ static layer = (options) => Layer2.effect(_OAuthTestServer, serveOAuthTestServer(options));
17
690
  };
18
- var TestHttpServerServeError = class extends Data.TaggedError("TestHttpServerServeError") {
691
+
692
+ // src/testing.ts
693
+ var TestHttpServerAddressError = class extends Data2.TaggedError("TestHttpServerAddressError") {
694
+ };
695
+ var TestHttpServerServeError = class extends Data2.TaggedError("TestHttpServerServeError") {
19
696
  };
20
697
  var testHttpRoute = HttpRouter.route;
21
698
  var serveTestHttpRoutes = (routes) => makeTestHttpServer(
@@ -25,32 +702,43 @@ var serveTestHttpRoutes = (routes) => makeTestHttpServer(
25
702
  })
26
703
  );
27
704
  var serveTestHttpApp = (handler) => makeTestHttpServer(
28
- HttpServer.serve(HttpServerRequest.HttpServerRequest.asEffect().pipe(Effect.flatMap(handler)))
705
+ HttpServer2.serve(HttpServerRequest2.HttpServerRequest.asEffect().pipe(Effect3.flatMap(handler)))
29
706
  );
30
- var makeTestHttpServer = (serverLayer) => Effect.gen(function* () {
31
- const context = yield* Layer.build(
32
- Layer.fresh(serverLayer.pipe(Layer.provideMerge(NodeHttpServer.layerTest)))
33
- ).pipe(Effect.mapError((cause) => new TestHttpServerServeError({ cause })));
34
- const server = Context.get(context, HttpServer.HttpServer);
707
+ var serveTestHttpServerLayer = (serverLayer) => makeTestHttpServer(serverLayer);
708
+ var makeTestHttpServer = (serverLayer) => Effect3.gen(function* () {
709
+ const context = yield* Layer3.build(
710
+ Layer3.fresh(serverLayer.pipe(Layer3.provideMerge(NodeHttpServer2.layerTest)))
711
+ ).pipe(Effect3.mapError((cause) => new TestHttpServerServeError({ cause })));
712
+ const server = Context3.get(context, HttpServer2.HttpServer);
35
713
  const address = server.address;
36
- if (!Predicate.isTagged(address, "TcpAddress")) {
714
+ if (!Predicate2.isTagged(address, "TcpAddress")) {
37
715
  return yield* new TestHttpServerAddressError({ address });
38
716
  }
39
- const client = Context.get(context, HttpClient.HttpClient);
717
+ const client = Context3.get(context, HttpClient2.HttpClient);
40
718
  const baseUrl = `http://127.0.0.1:${address.port}`;
41
719
  return {
42
720
  baseUrl,
43
- httpClientLayer: Layer.succeed(HttpClient.HttpClient, client),
721
+ httpClientLayer: Layer3.succeed(HttpClient2.HttpClient, client),
44
722
  url: (path = "") => new URL(path, baseUrl).toString()
45
723
  };
46
724
  });
47
725
  export {
726
+ OAuthTestServer,
727
+ OAuthTestServerAddressError,
728
+ OAuthTestServerFlowError,
48
729
  TestHttpServerAddressError,
49
730
  TestHttpServerServeError,
731
+ TestWorkspace,
732
+ createSqliteTestFumaDb,
50
733
  makeTestConfig,
734
+ makeTestExecutor,
735
+ makeTestWorkspaceHarness,
736
+ makeTestWorkspaceLayer,
51
737
  memorySecretsPlugin,
738
+ serveOAuthTestServer,
52
739
  serveTestHttpApp,
53
740
  serveTestHttpRoutes,
741
+ serveTestHttpServerLayer,
54
742
  testHttpRoute
55
743
  };
56
744
  //# sourceMappingURL=testing.js.map