@dual-os/sdk 0.0.0-stage → 0.8.0-next.0

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 (214) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +19 -2
  3. package/dist/Action.d.ts +201 -0
  4. package/dist/Action.d.ts.map +1 -0
  5. package/dist/Action.js +172 -0
  6. package/dist/Action.js.map +1 -0
  7. package/dist/AgentChannel.d.ts +1717 -0
  8. package/dist/AgentChannel.d.ts.map +1 -0
  9. package/dist/AgentChannel.js +708 -0
  10. package/dist/AgentChannel.js.map +1 -0
  11. package/dist/AgentChannelConformance.d.ts +1543 -0
  12. package/dist/AgentChannelConformance.d.ts.map +1 -0
  13. package/dist/AgentChannelConformance.js +163 -0
  14. package/dist/AgentChannelConformance.js.map +1 -0
  15. package/dist/AgentChannelRuntime.d.ts +78 -0
  16. package/dist/AgentChannelRuntime.d.ts.map +1 -0
  17. package/dist/AgentChannelRuntime.js +270 -0
  18. package/dist/AgentChannelRuntime.js.map +1 -0
  19. package/dist/AiProvider.d.ts +132 -0
  20. package/dist/AiProvider.d.ts.map +1 -0
  21. package/dist/AiProvider.js +100 -0
  22. package/dist/AiProvider.js.map +1 -0
  23. package/dist/AuthMethod.d.ts +381 -0
  24. package/dist/AuthMethod.d.ts.map +1 -0
  25. package/dist/AuthMethod.js +225 -0
  26. package/dist/AuthMethod.js.map +1 -0
  27. package/dist/AuthMethodBinding.d.ts +77 -0
  28. package/dist/AuthMethodBinding.d.ts.map +1 -0
  29. package/dist/AuthMethodBinding.js +31 -0
  30. package/dist/AuthMethodBinding.js.map +1 -0
  31. package/dist/AuthMethodExecution.d.ts +223 -0
  32. package/dist/AuthMethodExecution.d.ts.map +1 -0
  33. package/dist/AuthMethodExecution.js +277 -0
  34. package/dist/AuthMethodExecution.js.map +1 -0
  35. package/dist/ConnectionResolver.d.ts +219 -0
  36. package/dist/ConnectionResolver.d.ts.map +1 -0
  37. package/dist/ConnectionResolver.js +153 -0
  38. package/dist/ConnectionResolver.js.map +1 -0
  39. package/dist/DefinitionError.d.ts +67 -0
  40. package/dist/DefinitionError.d.ts.map +1 -0
  41. package/dist/DefinitionError.js +82 -0
  42. package/dist/DefinitionError.js.map +1 -0
  43. package/dist/DualRegistry.d.ts +71 -0
  44. package/dist/DualRegistry.d.ts.map +1 -0
  45. package/dist/DualRegistry.js +96 -0
  46. package/dist/DualRegistry.js.map +1 -0
  47. package/dist/DualRuntime.d.ts +279 -0
  48. package/dist/DualRuntime.d.ts.map +1 -0
  49. package/dist/DualRuntime.js +311 -0
  50. package/dist/DualRuntime.js.map +1 -0
  51. package/dist/Files.d.ts +80 -0
  52. package/dist/Files.d.ts.map +1 -0
  53. package/dist/Files.js +66 -0
  54. package/dist/Files.js.map +1 -0
  55. package/dist/Group.d.ts +106 -0
  56. package/dist/Group.d.ts.map +1 -0
  57. package/dist/Group.js +86 -0
  58. package/dist/Group.js.map +1 -0
  59. package/dist/Http.d.ts +342 -0
  60. package/dist/Http.d.ts.map +1 -0
  61. package/dist/Http.js +677 -0
  62. package/dist/Http.js.map +1 -0
  63. package/dist/Mcp.d.ts +109 -0
  64. package/dist/Mcp.d.ts.map +1 -0
  65. package/dist/Mcp.js +176 -0
  66. package/dist/Mcp.js.map +1 -0
  67. package/dist/OAuth2TokenRefresher.d.ts +18 -0
  68. package/dist/OAuth2TokenRefresher.d.ts.map +1 -0
  69. package/dist/OAuth2TokenRefresher.js +19 -0
  70. package/dist/OAuth2TokenRefresher.js.map +1 -0
  71. package/dist/OAuthFlow.d.ts +71 -0
  72. package/dist/OAuthFlow.d.ts.map +1 -0
  73. package/dist/OAuthFlow.js +488 -0
  74. package/dist/OAuthFlow.js.map +1 -0
  75. package/dist/Plugin.d.ts +383 -0
  76. package/dist/Plugin.d.ts.map +1 -0
  77. package/dist/Plugin.js +249 -0
  78. package/dist/Plugin.js.map +1 -0
  79. package/dist/PluginRuntime.d.ts +3186 -0
  80. package/dist/PluginRuntime.d.ts.map +1 -0
  81. package/dist/PluginRuntime.js +331 -0
  82. package/dist/PluginRuntime.js.map +1 -0
  83. package/dist/Runner.d.ts +1 -0
  84. package/dist/Runner.js +124 -0
  85. package/dist/Runner.js.map +1 -0
  86. package/dist/ToolFile.d.ts +23 -0
  87. package/dist/ToolFile.d.ts.map +1 -0
  88. package/dist/ToolFile.js +33 -0
  89. package/dist/ToolFile.js.map +1 -0
  90. package/dist/Trigger.d.ts +358 -0
  91. package/dist/Trigger.d.ts.map +1 -0
  92. package/dist/Trigger.js +287 -0
  93. package/dist/Trigger.js.map +1 -0
  94. package/dist/_virtual/_rolldown/runtime.js +13 -0
  95. package/dist/index.d.ts +24 -0
  96. package/dist/index.js +24 -0
  97. package/dist/internal/auth-context.d.ts +23 -0
  98. package/dist/internal/auth-context.d.ts.map +1 -0
  99. package/dist/internal/auth-context.js +1 -0
  100. package/dist/internal/auth-execution.d.ts +27 -0
  101. package/dist/internal/auth-execution.d.ts.map +1 -0
  102. package/dist/internal/auth-execution.js +66 -0
  103. package/dist/internal/auth-execution.js.map +1 -0
  104. package/dist/internal/auth-runtime.d.ts +5 -0
  105. package/dist/internal/auth-runtime.d.ts.map +1 -0
  106. package/dist/internal/auth-runtime.js +16 -0
  107. package/dist/internal/auth-runtime.js.map +1 -0
  108. package/dist/internal/capabilities.d.ts +24 -0
  109. package/dist/internal/capabilities.d.ts.map +1 -0
  110. package/dist/internal/capabilities.js +22 -0
  111. package/dist/internal/capabilities.js.map +1 -0
  112. package/dist/internal/children.d.ts +34 -0
  113. package/dist/internal/children.d.ts.map +1 -0
  114. package/dist/internal/children.js +53 -0
  115. package/dist/internal/children.js.map +1 -0
  116. package/dist/internal/collection-utils.d.ts +4 -0
  117. package/dist/internal/collection-utils.d.ts.map +1 -0
  118. package/dist/internal/collection-utils.js +6 -0
  119. package/dist/internal/collection-utils.js.map +1 -0
  120. package/dist/internal/dual/actions.d.ts +33 -0
  121. package/dist/internal/dual/actions.d.ts.map +1 -0
  122. package/dist/internal/dual/actions.js +26 -0
  123. package/dist/internal/dual/actions.js.map +1 -0
  124. package/dist/internal/dual/invoke.d.ts +63 -0
  125. package/dist/internal/dual/invoke.d.ts.map +1 -0
  126. package/dist/internal/dual/invoke.js +75 -0
  127. package/dist/internal/dual/invoke.js.map +1 -0
  128. package/dist/internal/dual/registry.d.ts +125 -0
  129. package/dist/internal/dual/registry.d.ts.map +1 -0
  130. package/dist/internal/dual/registry.js +172 -0
  131. package/dist/internal/dual/registry.js.map +1 -0
  132. package/dist/internal/dual/triggers.d.ts +29 -0
  133. package/dist/internal/dual/triggers.d.ts.map +1 -0
  134. package/dist/internal/dual/triggers.js +26 -0
  135. package/dist/internal/dual/triggers.js.map +1 -0
  136. package/dist/internal/handler-context.d.ts +9 -0
  137. package/dist/internal/handler-context.d.ts.map +1 -0
  138. package/dist/internal/handler-context.js +31 -0
  139. package/dist/internal/handler-context.js.map +1 -0
  140. package/dist/internal/identifier.d.ts +6 -0
  141. package/dist/internal/identifier.d.ts.map +1 -0
  142. package/dist/internal/identifier.js +28 -0
  143. package/dist/internal/identifier.js.map +1 -0
  144. package/dist/internal/plugin-handlers.d.ts +38 -0
  145. package/dist/internal/plugin-handlers.d.ts.map +1 -0
  146. package/dist/internal/plugin-handlers.js +52 -0
  147. package/dist/internal/plugin-handlers.js.map +1 -0
  148. package/dist/internal/proto.d.ts +7 -0
  149. package/dist/internal/proto.d.ts.map +1 -0
  150. package/dist/internal/proto.js +10 -0
  151. package/dist/internal/proto.js.map +1 -0
  152. package/dist/internal/schema-runtime.d.ts +6 -0
  153. package/dist/internal/schema-runtime.d.ts.map +1 -0
  154. package/dist/internal/schema-runtime.js +10 -0
  155. package/dist/internal/schema-runtime.js.map +1 -0
  156. package/dist/internal/trigger-runtime-shared.d.ts +41 -0
  157. package/dist/internal/trigger-runtime-shared.d.ts.map +1 -0
  158. package/dist/internal/trigger-runtime-shared.js +132 -0
  159. package/dist/internal/trigger-runtime-shared.js.map +1 -0
  160. package/dist/internal/trigger-runtime.d.ts +12 -0
  161. package/dist/internal/trigger-runtime.d.ts.map +1 -0
  162. package/dist/internal/trigger-runtime.js +95 -0
  163. package/dist/internal/trigger-runtime.js.map +1 -0
  164. package/dist/internal/types.d.ts +7 -0
  165. package/dist/internal/types.d.ts.map +1 -0
  166. package/dist/internal/types.js +1 -0
  167. package/dist/remote-runtime.d.ts +585 -0
  168. package/dist/remote-runtime.d.ts.map +1 -0
  169. package/dist/remote-runtime.js +367 -0
  170. package/dist/remote-runtime.js.map +1 -0
  171. package/package.json +55 -4
  172. package/src/Action.ts +370 -0
  173. package/src/AgentChannel.ts +860 -0
  174. package/src/AgentChannelConformance.ts +124 -0
  175. package/src/AgentChannelRuntime.ts +616 -0
  176. package/src/AiProvider.ts +255 -0
  177. package/src/AuthMethod.ts +954 -0
  178. package/src/AuthMethodBinding.ts +112 -0
  179. package/src/AuthMethodExecution.ts +542 -0
  180. package/src/ConnectionResolver.ts +265 -0
  181. package/src/DefinitionError.ts +115 -0
  182. package/src/DualRegistry.ts +152 -0
  183. package/src/DualRuntime.ts +1081 -0
  184. package/src/Files.ts +89 -0
  185. package/src/Group.ts +292 -0
  186. package/src/Http.ts +1205 -0
  187. package/src/Mcp.ts +299 -0
  188. package/src/OAuth2TokenRefresher.ts +22 -0
  189. package/src/OAuthFlow.ts +874 -0
  190. package/src/Plugin.ts +912 -0
  191. package/src/PluginRuntime.ts +414 -0
  192. package/src/Runner.ts +248 -0
  193. package/src/ToolFile.ts +28 -0
  194. package/src/Trigger.ts +722 -0
  195. package/src/index.ts +158 -0
  196. package/src/internal/auth-context.ts +27 -0
  197. package/src/internal/auth-execution.ts +241 -0
  198. package/src/internal/auth-runtime.ts +20 -0
  199. package/src/internal/capabilities.ts +55 -0
  200. package/src/internal/children.ts +126 -0
  201. package/src/internal/collection-utils.ts +8 -0
  202. package/src/internal/dual/actions.ts +116 -0
  203. package/src/internal/dual/invoke.ts +307 -0
  204. package/src/internal/dual/registry.ts +340 -0
  205. package/src/internal/dual/triggers.ts +128 -0
  206. package/src/internal/handler-context.ts +57 -0
  207. package/src/internal/identifier.ts +27 -0
  208. package/src/internal/plugin-handlers.ts +146 -0
  209. package/src/internal/proto.ts +8 -0
  210. package/src/internal/schema-runtime.ts +15 -0
  211. package/src/internal/trigger-runtime-shared.ts +449 -0
  212. package/src/internal/trigger-runtime.ts +223 -0
  213. package/src/internal/types.ts +12 -0
  214. package/src/remote-runtime.ts +469 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Resolved auth method bindings exposed to action and trigger handlers.
3
+ *
4
+ * @since 0.0.0
5
+ */
6
+ import type * as AuthMethod from "./AuthMethod.ts";
7
+
8
+ /**
9
+ * A resolved connection binding selected for one invocation.
10
+ *
11
+ * The selected connection is backed by one provider-owned auth method.
12
+ *
13
+ * @since 0.0.0
14
+ * @category models
15
+ */
16
+ export type Binding<out Method extends AuthMethod.Any = AuthMethod.Any> = {
17
+ readonly providerId: string;
18
+ readonly connectionId: string | undefined;
19
+ readonly method: Method;
20
+ readonly methodId: Method["identifier"];
21
+ readonly value: AuthMethod.Value<Method>;
22
+ };
23
+
24
+ /**
25
+ * Create a resolved connection binding.
26
+ *
27
+ * @since 0.0.0
28
+ * @category constructors
29
+ */
30
+ export const makeBinding = <Method extends AuthMethod.Any>(options: {
31
+ readonly providerId: string;
32
+ readonly connectionId: string | undefined;
33
+ readonly method: Method;
34
+ readonly value: AuthMethod.Value<Method>;
35
+ }): Binding<Method> => ({
36
+ connectionId: options.connectionId,
37
+ method: options.method,
38
+ methodId: options.method.identifier,
39
+ providerId: options.providerId,
40
+ value: options.value,
41
+ });
42
+
43
+ /**
44
+ * Union of resolved connection bindings for the provided method list.
45
+ *
46
+ * @since 0.0.0
47
+ * @category type-level
48
+ */
49
+ export type BindingFor<Methods extends readonly AuthMethod.Any[]> =
50
+ Methods[number] extends infer Method
51
+ ? Method extends AuthMethod.Any
52
+ ? Binding<Method>
53
+ : never
54
+ : never;
55
+
56
+ /**
57
+ * Resolved connection binding narrowed to one method from a provider-owned method list.
58
+ *
59
+ * @since 0.0.0
60
+ * @category type-level
61
+ */
62
+ export type BindingForMethod<
63
+ Methods extends readonly AuthMethod.Any[],
64
+ Method extends Methods[number],
65
+ > = BindingFor<Methods> & Binding<Method>;
66
+
67
+ /**
68
+ * Exhaustive handlers for the currently selected connection binding.
69
+ *
70
+ * @since 0.0.0
71
+ * @category models
72
+ */
73
+ export type MatchCases<Methods extends readonly AuthMethod.Any[], Result> = {
74
+ readonly [Method in Methods[number] as Method["identifier"]]: (
75
+ binding: BindingForMethod<Methods, Method>,
76
+ ) => Result;
77
+ };
78
+
79
+ /**
80
+ * Runtime auth helper context exposed to action handlers.
81
+ *
82
+ * @since 0.0.0
83
+ * @category models
84
+ */
85
+ export interface Context<Methods extends readonly AuthMethod.Any[]> {
86
+ readonly current: BindingFor<Methods>;
87
+ // tsrs accepts this `this` predicate only on an interface or class member.
88
+ is<Method extends Methods[number]>(
89
+ method: Method,
90
+ ): this is Context<Methods> & { readonly current: BindingForMethod<Methods, Method> };
91
+ is<Method extends Methods[number]>(
92
+ binding: BindingFor<Methods>,
93
+ method: Method,
94
+ ): binding is BindingForMethod<Methods, Method>;
95
+ match<Result>(cases: MatchCases<Methods, Result>): Result;
96
+ }
97
+
98
+ /**
99
+ * Runtime type guard for matching a resolved binding against a provider-owned method.
100
+ *
101
+ * @since 0.0.0
102
+ * @category guards
103
+ */
104
+ export const isBindingForMethod = <
105
+ Methods extends readonly AuthMethod.Any[],
106
+ Method extends Methods[number],
107
+ >(
108
+ binding: BindingFor<Methods>,
109
+ method: Method,
110
+ ): binding is BindingForMethod<Methods, Method> => (binding as Binding).method === method;
111
+
112
+ export * as AuthMethodBinding from "./AuthMethodBinding.ts";
@@ -0,0 +1,542 @@
1
+ /**
2
+ * Credential decoding, hook execution, material refresh, and value resolution.
3
+ *
4
+ * @since 0.0.0
5
+ */
6
+ import { Effect, Schema } from "effect";
7
+
8
+ import * as AuthMethod from "./AuthMethod.ts";
9
+ import { OAuth2TokenRefresher } from "./OAuth2TokenRefresher.ts";
10
+
11
+ const hookError = (
12
+ methodIdentifier: string,
13
+ operation: AuthMethod.AuthMethodHookOperation,
14
+ cause: unknown,
15
+ ): AuthMethod.AuthMethodHookError =>
16
+ new AuthMethod.AuthMethodHookError({ cause, methodIdentifier, operation });
17
+
18
+ const runHook = <A, R>(
19
+ methodIdentifier: string,
20
+ operation: AuthMethod.AuthMethodHookOperation,
21
+ hook: () => AuthMethod.MaybeEffect<A, unknown, R>,
22
+ ): Effect.Effect<A, AuthMethod.AuthMethodHookError, R> =>
23
+ Effect.try({ catch: cause => hookError(methodIdentifier, operation, cause), try: hook }).pipe(
24
+ Effect.flatMap(result =>
25
+ toEffect(result).pipe(
26
+ Effect.mapError(cause => hookError(methodIdentifier, operation, cause)),
27
+ ),
28
+ ),
29
+ );
30
+
31
+ const toEffect = <Value, Error, Requirements>(
32
+ value: AuthMethod.MaybeEffect<Value, Error, Requirements>,
33
+ ): Effect.Effect<Value, Error, Requirements> =>
34
+ Effect.isEffect(value) ? value : Effect.succeed(value);
35
+
36
+ const hookContext = (
37
+ context: AuthMethod.HookContext<unknown> | undefined,
38
+ ): AuthMethod.HookContext<unknown> => context ?? { setup: undefined };
39
+
40
+ /**
41
+ * Erased runtime view of an auth method.
42
+ *
43
+ * Execution helpers implement against this view and re-expose precise
44
+ * per-method types through their public signatures; the single `as never`
45
+ * closing each implementation is the contained boundary between the two.
46
+ */
47
+ interface AnyImpl {
48
+ readonly identifier: string;
49
+ readonly kind: AuthMethod.AuthMethodKind;
50
+ readonly setup: Schema.Top | undefined;
51
+ readonly input: Schema.Top | undefined;
52
+ readonly material: Schema.Top;
53
+ readonly value: Schema.Top;
54
+ readonly schema: Schema.Top;
55
+ readonly prepare: AuthMethod.PrepareHook<unknown, unknown, unknown, unknown, unknown> | undefined;
56
+ readonly refresh: AuthMethod.RefreshHook<unknown, unknown, unknown, unknown> | undefined;
57
+ readonly resolve: AuthMethod.ResolveHook<unknown, unknown, unknown, unknown, unknown> | undefined;
58
+ readonly display: AuthMethod.DisplayHook<unknown, unknown, unknown, unknown> | undefined;
59
+ readonly test: AuthMethod.TestHook<unknown, unknown, unknown, unknown> | undefined;
60
+ readonly oauth2: AuthMethod.OAuth2Setup<Schema.Top, unknown, unknown> | undefined;
61
+ }
62
+
63
+ const toImpl = (method: AuthMethod.Any): AnyImpl => method as AnyImpl;
64
+
65
+ type SetupRequirements<Method extends AuthMethod.Any> =
66
+ AuthMethod.SetupSchema<Method> extends Schema.Top
67
+ ? Schema.Codec.DecodingServices<AuthMethod.SetupSchema<Method>>
68
+ : never;
69
+
70
+ type InputRequirements<Method extends AuthMethod.Any> =
71
+ AuthMethod.InputSchema<Method> extends Schema.Top
72
+ ? Schema.Codec.DecodingServices<AuthMethod.InputSchema<Method>>
73
+ : never;
74
+
75
+ type MaterialRequirements<Method extends AuthMethod.Any> = Schema.Codec.DecodingServices<
76
+ AuthMethod.MaterialSchema<Method>
77
+ >;
78
+
79
+ type ValueRequirements<Method extends AuthMethod.Any> = Schema.Codec.DecodingServices<
80
+ AuthMethod.ValueSchema<Method>
81
+ >;
82
+
83
+ /**
84
+ * Decode a runtime auth value using the selected method schema.
85
+ *
86
+ * @since 0.0.0
87
+ * @category constructors
88
+ */
89
+ export const decodeValue: <Method extends AuthMethod.Any>(
90
+ method: Method,
91
+ value: unknown,
92
+ ) => Effect.Effect<AuthMethod.Value<Method>, Schema.SchemaError, ValueRequirements<Method>> = (
93
+ method,
94
+ value,
95
+ ) => Schema.decodeUnknownEffect(toImpl(method).schema)(value) as never;
96
+
97
+ /**
98
+ * Decode organization/admin-level setup for a method, if it declares one.
99
+ *
100
+ * @since 0.0.0
101
+ * @category constructors
102
+ */
103
+ export const decodeSetup: <Method extends AuthMethod.Any>(
104
+ method: Method,
105
+ setup: unknown,
106
+ ) => Effect.Effect<AuthMethod.Setup<Method>, Schema.SchemaError, SetupRequirements<Method>> = (
107
+ method,
108
+ setup,
109
+ ) => {
110
+ const impl = toImpl(method);
111
+ return (
112
+ impl.setup === undefined ? Effect.void : Schema.decodeUnknownEffect(impl.setup)(setup)
113
+ ) as never;
114
+ };
115
+
116
+ /**
117
+ * Decode submitted setup input using the method input schema, if any.
118
+ *
119
+ * @since 0.0.0
120
+ * @category constructors
121
+ */
122
+ export const decodeInput: <Method extends AuthMethod.Any>(
123
+ method: Method,
124
+ input: unknown,
125
+ ) => Effect.Effect<AuthMethod.Input<Method>, Schema.SchemaError, InputRequirements<Method>> = (
126
+ method,
127
+ input,
128
+ ) => {
129
+ const impl = toImpl(method);
130
+ return (
131
+ impl.input === undefined ? Effect.void : Schema.decodeUnknownEffect(impl.input)(input)
132
+ ) as never;
133
+ };
134
+
135
+ /**
136
+ * Decode encrypted stored material using the method material schema.
137
+ *
138
+ * @since 0.0.0
139
+ * @category constructors
140
+ */
141
+ export const decodeMaterial: <Method extends AuthMethod.Any>(
142
+ method: Method,
143
+ material: unknown,
144
+ ) => Effect.Effect<
145
+ AuthMethod.Material<Method>,
146
+ Schema.SchemaError,
147
+ MaterialRequirements<Method>
148
+ > = (method, material) => Schema.decodeUnknownEffect(toImpl(method).material)(material) as never;
149
+
150
+ /**
151
+ * Prepare already-decoded setup input into encrypted stored material.
152
+ *
153
+ * @since 0.0.0
154
+ * @category constructors
155
+ */
156
+ export const prepareDecoded: <Method extends AuthMethod.Any>(
157
+ method: Method,
158
+ input: AuthMethod.Input<Method>,
159
+ context?: AuthMethod.HookContext<AuthMethod.Setup<Method>>,
160
+ ) => Effect.Effect<
161
+ AuthMethod.Material<Method>,
162
+ Schema.SchemaError | AuthMethod.AuthMethodHookError,
163
+ MaterialRequirements<Method> | AuthMethod.Requirements<Method>
164
+ > = (method, input, context) => {
165
+ const impl = toImpl(method);
166
+ const prepare = impl.prepare;
167
+
168
+ if (prepare === undefined) {
169
+ return (
170
+ impl.input === impl.material
171
+ ? Effect.succeed(input)
172
+ : Schema.decodeUnknownEffect(impl.material)(input)
173
+ ) as never;
174
+ }
175
+
176
+ return runHook(impl.identifier, "prepare", () => prepare(input, hookContext(context))).pipe(
177
+ Effect.flatMap(material => Schema.decodeUnknownEffect(impl.material)(material)),
178
+ ) as never;
179
+ };
180
+
181
+ /**
182
+ * Decode and prepare submitted setup input into encrypted stored material.
183
+ *
184
+ * @since 0.0.0
185
+ * @category constructors
186
+ */
187
+ export const prepareUnknown: <Method extends AuthMethod.Any>(
188
+ method: Method,
189
+ input: unknown,
190
+ context?: AuthMethod.HookContext<AuthMethod.Setup<Method>>,
191
+ ) => Effect.Effect<
192
+ AuthMethod.Material<Method>,
193
+ Schema.SchemaError | AuthMethod.AuthMethodHookError,
194
+ InputRequirements<Method> | MaterialRequirements<Method> | AuthMethod.Requirements<Method>
195
+ > = (method, input, context) =>
196
+ decodeInput(method, input).pipe(
197
+ Effect.flatMap(decodedInput => prepareDecoded(method, decodedInput, context)),
198
+ );
199
+
200
+ const oauth2ExpiresAt = (material: unknown): number | undefined =>
201
+ typeof material === "object" && material !== null && "expiresAt" in material
202
+ ? typeof (material as { readonly expiresAt?: unknown }).expiresAt === "number"
203
+ ? (material as { readonly expiresAt: number }).expiresAt
204
+ : undefined
205
+ : undefined;
206
+
207
+ const oauth2RefreshToken = (material: unknown): string | undefined =>
208
+ typeof material === "object" && material !== null && "refreshToken" in material
209
+ ? typeof (material as { readonly refreshToken?: unknown }).refreshToken === "string" &&
210
+ (material as { readonly refreshToken: string }).refreshToken.trim() !== ""
211
+ ? (material as { readonly refreshToken: string }).refreshToken
212
+ : undefined
213
+ : undefined;
214
+
215
+ const refreshOAuth2Decoded = Effect.fn("AuthMethod.refreshOAuth2Decoded")(function* (
216
+ method: AuthMethod.Any,
217
+ material: unknown,
218
+ context: AuthMethod.RefreshContext<unknown>,
219
+ ) {
220
+ if (
221
+ context.reason === "before-use" &&
222
+ (oauth2ExpiresAt(material) ?? Number.NEGATIVE_INFINITY) > context.now + context.minValidityMs
223
+ ) {
224
+ return undefined;
225
+ }
226
+
227
+ const refreshToken = oauth2RefreshToken(material);
228
+ if (refreshToken === undefined) {
229
+ return yield* new AuthMethod.OAuth2RefreshTokenUnavailableError({
230
+ methodIdentifier: method.identifier,
231
+ });
232
+ }
233
+
234
+ const refresher = yield* OAuth2TokenRefresher.Service;
235
+ const tokens = yield* runHook(method.identifier, "oauth2TokenRefresh", () =>
236
+ refresher.refresh({ method, refreshToken, setup: context.setup }),
237
+ );
238
+ return yield* prepareOAuth2Tokens(method, tokens, { setup: context.setup });
239
+ });
240
+
241
+ /**
242
+ * Decode and prepare submitted setup input into encrypted stored material.
243
+ *
244
+ * @since 0.0.0
245
+ * @category constructors
246
+ */
247
+ export const prepare = prepareUnknown;
248
+
249
+ /**
250
+ * Refresh already-decoded stored material when the auth method supports it.
251
+ *
252
+ * A result of `undefined` means the material is still usable and should not be
253
+ * persisted again.
254
+ *
255
+ * @since 0.0.0
256
+ * @category constructors
257
+ */
258
+ export const refreshDecoded: <Method extends AuthMethod.Any>(
259
+ method: Method,
260
+ material: AuthMethod.Material<Method>,
261
+ context: AuthMethod.RefreshContext<AuthMethod.Setup<Method>>,
262
+ ) => Effect.Effect<
263
+ AuthMethod.Material<Method> | undefined,
264
+ | AuthMethod.ExpectedOAuth2MethodError
265
+ | AuthMethod.OAuth2RefreshTokenUnavailableError
266
+ | Schema.SchemaError
267
+ | AuthMethod.AuthMethodHookError,
268
+ MaterialRequirements<Method> | AuthMethod.Requirements<Method>
269
+ > = (method, material, context) => {
270
+ const impl = toImpl(method);
271
+
272
+ if (impl.oauth2 !== undefined) {
273
+ return refreshOAuth2Decoded(method, material, context) as never;
274
+ }
275
+
276
+ const refresh = impl.refresh;
277
+ if (refresh === undefined) {
278
+ return Effect.void as never;
279
+ }
280
+
281
+ return runHook(impl.identifier, "refresh", () => refresh(material, context)).pipe(
282
+ Effect.flatMap(refreshed =>
283
+ refreshed === undefined ? Effect.void : Schema.decodeUnknownEffect(impl.material)(refreshed),
284
+ ),
285
+ ) as never;
286
+ };
287
+
288
+ /**
289
+ * Decode stored material and run the auth method refresh hook, if any.
290
+ *
291
+ * @since 0.0.0
292
+ * @category constructors
293
+ */
294
+ export const refreshUnknown: <Method extends AuthMethod.Any>(
295
+ method: Method,
296
+ material: unknown,
297
+ context: AuthMethod.RefreshContext<AuthMethod.Setup<Method>>,
298
+ ) => Effect.Effect<
299
+ AuthMethod.Material<Method> | undefined,
300
+ | AuthMethod.ExpectedOAuth2MethodError
301
+ | AuthMethod.OAuth2RefreshTokenUnavailableError
302
+ | Schema.SchemaError
303
+ | AuthMethod.AuthMethodHookError,
304
+ MaterialRequirements<Method> | AuthMethod.Requirements<Method>
305
+ > = (method, material, context) =>
306
+ decodeMaterial(method, material).pipe(
307
+ Effect.flatMap(decodedMaterial => refreshDecoded(method, decodedMaterial, context)),
308
+ );
309
+
310
+ /**
311
+ * Prepare normalized OAuth2 token exchange output into encrypted stored material.
312
+ *
313
+ * OAuth2 has no frontend-submitted setup input. The platform owns redirect,
314
+ * callback, state/PKCE, and token exchange, then calls this helper at the
315
+ * token-response boundary.
316
+ *
317
+ * @since 0.0.0
318
+ * @category constructors
319
+ */
320
+ export const prepareOAuth2Tokens: <Method extends AuthMethod.Any>(
321
+ method: Method,
322
+ tokens: AuthMethod.OAuth2TokenResponse,
323
+ context?: AuthMethod.HookContext<AuthMethod.Setup<Method>>,
324
+ ) => Effect.Effect<
325
+ AuthMethod.Material<Method>,
326
+ AuthMethod.ExpectedOAuth2MethodError | Schema.SchemaError | AuthMethod.AuthMethodHookError,
327
+ MaterialRequirements<Method> | AuthMethod.Requirements<Method>
328
+ > = (method, tokens, context) => {
329
+ const impl = toImpl(method);
330
+
331
+ if (impl.oauth2 === undefined) {
332
+ return Effect.fail(
333
+ new AuthMethod.ExpectedOAuth2MethodError({ methodIdentifier: impl.identifier }),
334
+ ) as never;
335
+ }
336
+
337
+ const fromTokens = impl.oauth2.fromTokens;
338
+ const material: Effect.Effect<unknown, unknown, unknown> =
339
+ fromTokens === undefined
340
+ ? Effect.succeed(tokens)
341
+ : runHook(impl.identifier, "prepareOAuth2Tokens", () =>
342
+ fromTokens(tokens, hookContext(context)),
343
+ );
344
+
345
+ return material.pipe(
346
+ Effect.flatMap(value => Schema.decodeUnknownEffect(impl.material)(value)),
347
+ ) as never;
348
+ };
349
+
350
+ /**
351
+ * Resolve already-decoded stored material into the runtime value handlers receive.
352
+ *
353
+ * @since 0.0.0
354
+ * @category constructors
355
+ */
356
+ export const resolveDecoded: <Method extends AuthMethod.Any>(
357
+ method: Method,
358
+ material: AuthMethod.Material<Method>,
359
+ context?: AuthMethod.HookContext<AuthMethod.Setup<Method>>,
360
+ ) => Effect.Effect<
361
+ AuthMethod.Value<Method>,
362
+ Schema.SchemaError | AuthMethod.AuthMethodHookError,
363
+ ValueRequirements<Method> | AuthMethod.Requirements<Method>
364
+ > = (method, material, context) => {
365
+ const impl = toImpl(method);
366
+ const resolve = impl.resolve;
367
+
368
+ if (resolve === undefined) {
369
+ return (
370
+ impl.material === impl.value
371
+ ? Effect.succeed(material)
372
+ : Schema.decodeUnknownEffect(impl.schema)(material)
373
+ ) as never;
374
+ }
375
+
376
+ return runHook(impl.identifier, "resolve", () => resolve(material, hookContext(context))).pipe(
377
+ Effect.flatMap(value => Schema.decodeUnknownEffect(impl.schema)(value)),
378
+ ) as never;
379
+ };
380
+
381
+ /**
382
+ * Decode stored material and resolve it into the runtime value handlers receive.
383
+ *
384
+ * @since 0.0.0
385
+ * @category constructors
386
+ */
387
+ export const resolve: <Method extends AuthMethod.Any>(
388
+ method: Method,
389
+ material: unknown,
390
+ context?: AuthMethod.HookContext<AuthMethod.Setup<Method>>,
391
+ ) => Effect.Effect<
392
+ AuthMethod.Value<Method>,
393
+ Schema.SchemaError | AuthMethod.AuthMethodHookError,
394
+ MaterialRequirements<Method> | ValueRequirements<Method> | AuthMethod.Requirements<Method>
395
+ > = (method, material, context) =>
396
+ decodeMaterial(method, material).pipe(
397
+ Effect.flatMap(decodedMaterial => resolveDecoded(method, decodedMaterial, context)),
398
+ );
399
+
400
+ /**
401
+ * Resolve untrusted persisted material into the runtime value handlers receive.
402
+ *
403
+ * This is the persistence-boundary variant of {@link resolve}: callers can pass
404
+ * raw JSON from storage and the method material schema is decoded before any
405
+ * provider hook runs.
406
+ *
407
+ * @since 0.0.0
408
+ * @category constructors
409
+ */
410
+ export const resolveUnknown: typeof resolve = resolve;
411
+
412
+ const jsonAuth = {
413
+ authMethodId: Schema.NonEmptyString,
414
+ providerId: Schema.NonEmptyString,
415
+ setup: Schema.optionalKey(Schema.Json),
416
+ };
417
+
418
+ /** Operation-specific authentication data for isolated execution. */
419
+ export type JsonRequest = typeof JsonRequest.Type;
420
+ export const JsonRequest = Schema.Union([
421
+ Schema.TaggedStruct("decodeSetup", jsonAuth),
422
+ Schema.TaggedStruct("prepare", { ...jsonAuth, input: Schema.Json }),
423
+ Schema.TaggedStruct("prepareOAuth2Tokens", {
424
+ ...jsonAuth,
425
+ tokens: Schema.Struct({
426
+ accessToken: Schema.String,
427
+ expiresAt: Schema.optionalKey(Schema.Number),
428
+ expiresIn: Schema.optionalKey(Schema.Number),
429
+ raw: Schema.Record(Schema.String, Schema.Json),
430
+ refreshToken: Schema.optionalKey(Schema.String),
431
+ scope: Schema.optionalKey(Schema.String),
432
+ tokenType: Schema.optionalKey(Schema.String),
433
+ }),
434
+ }),
435
+ Schema.TaggedStruct("refresh", {
436
+ ...jsonAuth,
437
+ material: Schema.Json,
438
+ minValidityMs: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)),
439
+ now: Schema.Finite,
440
+ reason: Schema.Literals(["before-use", "forced"]),
441
+ }),
442
+ Schema.TaggedStruct("resolve", { ...jsonAuth, material: Schema.Json }),
443
+ Schema.TaggedStruct("display", { ...jsonAuth, material: Schema.Json }),
444
+ Schema.TaggedStruct("test", { ...jsonAuth, material: Schema.Json }),
445
+ ]);
446
+
447
+ /** Decode canonical JSON once; executable hooks and codecs stay in the guest. */
448
+ export const executeJson = Effect.fn("AuthMethod.executeJson")(function* (
449
+ method: AuthMethod.Any,
450
+ request: JsonRequest,
451
+ ) {
452
+ const decode = (schema: Schema.Top, value: unknown) =>
453
+ Schema.decodeUnknownEffect(Schema.toCodecJson(schema))(value);
454
+ const encode = (schema: Schema.Top, value: unknown) =>
455
+ Schema.encodeEffect(Schema.toCodecJson(schema))(value);
456
+ const setup = method.setup === undefined ? undefined : yield* decode(method.setup, request.setup);
457
+ if (request._tag === "decodeSetup") {
458
+ return method.setup === undefined ? undefined : yield* encode(method.setup, setup);
459
+ }
460
+ if (request._tag === "prepare") {
461
+ const input =
462
+ method.input === undefined ? undefined : yield* decode(method.input, request.input);
463
+ return yield* encode(method.material, yield* prepareDecoded(method, input, { setup }));
464
+ }
465
+ if (request._tag === "prepareOAuth2Tokens") {
466
+ return yield* encode(
467
+ method.material,
468
+ yield* prepareOAuth2Tokens(method, request.tokens, { setup }),
469
+ );
470
+ }
471
+ const material = yield* decode(method.material, request.material);
472
+ if (request._tag === "refresh") {
473
+ const refreshed = yield* refreshDecoded(method, material, { ...request, setup });
474
+ return refreshed === undefined ? undefined : yield* encode(method.material, refreshed);
475
+ }
476
+ if (request._tag === "resolve") {
477
+ return {
478
+ methodId: method.identifier,
479
+ providerId: request.providerId,
480
+ value: yield* encode(method.value, yield* resolveDecoded(method, material, { setup })),
481
+ };
482
+ }
483
+ const impl = toImpl(method);
484
+ if (request._tag === "display") {
485
+ const hook = impl.display;
486
+ return hook === undefined
487
+ ? undefined
488
+ : yield* runHook(method.identifier, "display", () => hook(material, { setup }));
489
+ }
490
+ const hook = impl.test;
491
+ return hook === undefined
492
+ ? { ok: true }
493
+ : yield* runHook(method.identifier, "test", () => hook(material, { setup }));
494
+ });
495
+
496
+ /** @since 0.0.0 @category constructors */
497
+ export const display: <Method extends AuthMethod.Any>(
498
+ method: Method,
499
+ material: AuthMethod.Material<Method>,
500
+ context?: AuthMethod.HookContext<AuthMethod.Setup<Method>>,
501
+ ) => Effect.Effect<
502
+ AuthMethod.Display | undefined,
503
+ Schema.SchemaError | AuthMethod.AuthMethodHookError,
504
+ MaterialRequirements<Method> | AuthMethod.Requirements<Method>
505
+ > = (method, material, context) => {
506
+ const impl = toImpl(method);
507
+ const displayHook = impl.display;
508
+
509
+ return decodeMaterial(method, material).pipe(
510
+ Effect.flatMap(decodedMaterial =>
511
+ displayHook === undefined
512
+ ? Effect.void
513
+ : runHook(impl.identifier, "display", () =>
514
+ displayHook(decodedMaterial, hookContext(context)),
515
+ ),
516
+ ),
517
+ ) as never;
518
+ };
519
+
520
+ /** @since 0.0.0 @category constructors */
521
+ export const test: <Method extends AuthMethod.Any>(
522
+ method: Method,
523
+ material: AuthMethod.Material<Method>,
524
+ context?: AuthMethod.HookContext<AuthMethod.Setup<Method>>,
525
+ ) => Effect.Effect<
526
+ AuthMethod.TestResult,
527
+ Schema.SchemaError | AuthMethod.AuthMethodHookError,
528
+ MaterialRequirements<Method> | AuthMethod.Requirements<Method>
529
+ > = (method, material, context) => {
530
+ const impl = toImpl(method);
531
+ const testHook = impl.test;
532
+
533
+ return decodeMaterial(method, material).pipe(
534
+ Effect.flatMap(decodedMaterial =>
535
+ testHook === undefined
536
+ ? Effect.succeed<AuthMethod.TestResult>({ ok: true })
537
+ : runHook(impl.identifier, "test", () => testHook(decodedMaterial, hookContext(context))),
538
+ ),
539
+ ) as never;
540
+ };
541
+
542
+ export * as AuthMethodExecution from "./AuthMethodExecution.ts";