@ekanos/sdk 0.3.0 → 0.4.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 (60) hide show
  1. package/README.md +113 -33
  2. package/api-report.md +7 -3
  3. package/dist/components/base-activation-dialog.d.ts +15 -5
  4. package/dist/components/base-activation-dialog.js +31 -5
  5. package/dist/components/base-activation-dialog.js.map +1 -1
  6. package/dist/components/base-activation-form.d.ts +20 -5
  7. package/dist/components/base-activation-form.js +12 -5
  8. package/dist/components/base-activation-form.js.map +1 -1
  9. package/dist/components/base-marketplace-tile.js +12 -10
  10. package/dist/components/base-marketplace-tile.js.map +1 -1
  11. package/dist/components/index.d.ts +1 -1
  12. package/dist/components/index.js.map +1 -1
  13. package/dist/components/oauth-activation-form.d.ts +23 -16
  14. package/dist/components/oauth-activation-form.js +34 -20
  15. package/dist/components/oauth-activation-form.js.map +1 -1
  16. package/dist/components/widgets/use-relative-time.d.ts +7 -0
  17. package/dist/components/widgets/use-relative-time.js +55 -0
  18. package/dist/components/widgets/use-relative-time.js.map +1 -0
  19. package/dist/components/widgets/widget-ask-bar.js.map +1 -1
  20. package/dist/components/widgets/widget-context.d.ts +21 -0
  21. package/dist/components/widgets/widget-context.js.map +1 -1
  22. package/dist/components/widgets/widget-preview-provider.d.ts +1 -1
  23. package/dist/components/widgets/widget-preview-provider.js +1 -1
  24. package/dist/components/widgets/widget-preview-provider.js.map +1 -1
  25. package/dist/components/widgets/widget-state-components.d.ts +4 -1
  26. package/dist/components/widgets/widget-state-components.js +6 -4
  27. package/dist/components/widgets/widget-state-components.js.map +1 -1
  28. package/dist/components/widgets/widget.d.ts +47 -2
  29. package/dist/components/widgets/widget.js +111 -23
  30. package/dist/components/widgets/widget.js.map +1 -1
  31. package/dist/context/egress.js.map +1 -1
  32. package/dist/context/storage-validation.d.ts +24 -4
  33. package/dist/context/storage-validation.js +5 -2
  34. package/dist/context/storage-validation.js.map +1 -1
  35. package/dist/hooks/ask-assistant-context.js.map +1 -1
  36. package/dist/hooks/fetch-integration-storage.js.map +1 -1
  37. package/dist/hooks/partner-oauth.js +3 -0
  38. package/dist/hooks/partner-oauth.js.map +1 -1
  39. package/dist/hooks/use-integration-action.js.map +1 -1
  40. package/dist/hooks/use-oauth-connection-status.d.ts +78 -78
  41. package/dist/hooks/use-oauth-connection-status.js +17 -10
  42. package/dist/hooks/use-oauth-connection-status.js.map +1 -1
  43. package/dist/hooks/widget-event-context.js.map +1 -1
  44. package/dist/integration/event-surfaces.js.map +1 -1
  45. package/dist/integration/index.d.ts +2 -2
  46. package/dist/integration/index.js +1 -1
  47. package/dist/integration/index.js.map +1 -1
  48. package/dist/testing/invoke.js +9 -5
  49. package/dist/testing/invoke.js.map +1 -1
  50. package/dist/testing/mock-context.js +8 -7
  51. package/dist/testing/mock-context.js.map +1 -1
  52. package/dist/testing/mock-fetch-provider.js.map +1 -1
  53. package/dist/types/integration.d.ts +27 -21
  54. package/dist/types/integration.js.map +1 -1
  55. package/dist/types/widget-ask-context.d.ts +9 -0
  56. package/dist/types/widget-ask-context.js.map +1 -1
  57. package/dist/types/workspace-target.d.ts +3 -2
  58. package/dist/types/workspace-target.js.map +1 -1
  59. package/eslint.preset.mjs +9 -8
  60. package/package.json +37 -41
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Result shape of an integration's check-connection server action
3
- * (the standard enhanceAction envelope).
3
+ * (the standard server-action envelope).
4
4
  */
5
5
  export interface ConnectionCheckResult {
6
6
  success: boolean;
@@ -40,26 +40,6 @@ export interface UseOAuthConnectionStatusOptions {
40
40
  * connected without a manual refresh.
41
41
  */
42
42
  export declare function useOAuthConnectionStatus(accountId: string, options: UseOAuthConnectionStatusOptions): {
43
- oauthJustCompleted: boolean;
44
- oauthError: string | null;
45
- data: {
46
- isConnected: boolean;
47
- error: string | undefined;
48
- obtainedAt?: undefined;
49
- } | {
50
- isConnected: boolean;
51
- obtainedAt: string | null | undefined;
52
- error?: undefined;
53
- };
54
- error: Error;
55
- isError: true;
56
- isPending: false;
57
- isLoading: false;
58
- isLoadingError: false;
59
- isRefetchError: true;
60
- isSuccess: false;
61
- isPlaceholderData: false;
62
- status: "error";
63
43
  dataUpdatedAt: number;
64
44
  errorUpdatedAt: number;
65
45
  failureCount: number;
@@ -78,32 +58,24 @@ export declare function useOAuthConnectionStatus(accountId: string, options: Use
78
58
  error: string | undefined;
79
59
  obtainedAt?: undefined;
80
60
  } | {
61
+ error?: undefined;
81
62
  isConnected: boolean;
82
63
  obtainedAt: string | null | undefined;
83
- error?: undefined;
84
64
  }, Error>>;
85
65
  fetchStatus: import("@tanstack/react-query").FetchStatus;
86
- } | {
87
- oauthJustCompleted: boolean;
88
- oauthError: string | null;
89
- data: {
90
- isConnected: boolean;
91
- error: string | undefined;
92
- obtainedAt?: undefined;
93
- } | {
94
- isConnected: boolean;
95
- obtainedAt: string | null | undefined;
96
- error?: undefined;
97
- };
98
- error: null;
99
- isError: false;
66
+ data: undefined;
67
+ error: Error;
68
+ isError: true;
100
69
  isPending: false;
101
70
  isLoading: false;
102
- isLoadingError: false;
71
+ isLoadingError: true;
103
72
  isRefetchError: false;
104
- isSuccess: true;
73
+ isSuccess: false;
105
74
  isPlaceholderData: false;
106
- status: "success";
75
+ status: 'error';
76
+ oauthJustCompleted: boolean;
77
+ oauthError: string | null;
78
+ } | {
107
79
  dataUpdatedAt: number;
108
80
  errorUpdatedAt: number;
109
81
  failureCount: number;
@@ -122,24 +94,24 @@ export declare function useOAuthConnectionStatus(accountId: string, options: Use
122
94
  error: string | undefined;
123
95
  obtainedAt?: undefined;
124
96
  } | {
97
+ error?: undefined;
125
98
  isConnected: boolean;
126
99
  obtainedAt: string | null | undefined;
127
- error?: undefined;
128
100
  }, Error>>;
129
101
  fetchStatus: import("@tanstack/react-query").FetchStatus;
130
- } | {
131
- oauthJustCompleted: boolean;
132
- oauthError: string | null;
133
102
  data: undefined;
134
- error: Error;
135
- isError: true;
136
- isPending: false;
137
- isLoading: false;
138
- isLoadingError: true;
103
+ error: null;
104
+ isError: false;
105
+ isPending: true;
106
+ isLoading: true;
107
+ isLoadingError: false;
139
108
  isRefetchError: false;
140
109
  isSuccess: false;
141
110
  isPlaceholderData: false;
142
- status: "error";
111
+ status: 'pending';
112
+ oauthJustCompleted: boolean;
113
+ oauthError: string | null;
114
+ } | {
143
115
  dataUpdatedAt: number;
144
116
  errorUpdatedAt: number;
145
117
  failureCount: number;
@@ -148,6 +120,7 @@ export declare function useOAuthConnectionStatus(accountId: string, options: Use
148
120
  isFetched: boolean;
149
121
  isFetchedAfterMount: boolean;
150
122
  isFetching: boolean;
123
+ isLoading: boolean;
151
124
  isInitialLoading: boolean;
152
125
  isPaused: boolean;
153
126
  isRefetching: boolean;
@@ -158,24 +131,23 @@ export declare function useOAuthConnectionStatus(accountId: string, options: Use
158
131
  error: string | undefined;
159
132
  obtainedAt?: undefined;
160
133
  } | {
134
+ error?: undefined;
161
135
  isConnected: boolean;
162
136
  obtainedAt: string | null | undefined;
163
- error?: undefined;
164
137
  }, Error>>;
165
138
  fetchStatus: import("@tanstack/react-query").FetchStatus;
166
- } | {
167
- oauthJustCompleted: boolean;
168
- oauthError: string | null;
169
139
  data: undefined;
170
140
  error: null;
171
141
  isError: false;
172
142
  isPending: true;
173
- isLoading: true;
174
143
  isLoadingError: false;
175
144
  isRefetchError: false;
176
145
  isSuccess: false;
177
146
  isPlaceholderData: false;
178
- status: "pending";
147
+ status: 'pending';
148
+ oauthJustCompleted: boolean;
149
+ oauthError: string | null;
150
+ } | {
179
151
  dataUpdatedAt: number;
180
152
  errorUpdatedAt: number;
181
153
  failureCount: number;
@@ -194,23 +166,32 @@ export declare function useOAuthConnectionStatus(accountId: string, options: Use
194
166
  error: string | undefined;
195
167
  obtainedAt?: undefined;
196
168
  } | {
169
+ error?: undefined;
197
170
  isConnected: boolean;
198
171
  obtainedAt: string | null | undefined;
199
- error?: undefined;
200
172
  }, Error>>;
201
173
  fetchStatus: import("@tanstack/react-query").FetchStatus;
202
- } | {
203
- oauthJustCompleted: boolean;
204
- oauthError: string | null;
205
- data: undefined;
206
- error: null;
174
+ data: {
175
+ isConnected: boolean;
176
+ error: string | undefined;
177
+ obtainedAt?: undefined;
178
+ } | {
179
+ error?: undefined;
180
+ isConnected: boolean;
181
+ obtainedAt: string | null | undefined;
182
+ };
207
183
  isError: false;
208
- isPending: true;
184
+ error: null;
185
+ isPending: false;
186
+ isLoading: false;
209
187
  isLoadingError: false;
210
188
  isRefetchError: false;
211
- isSuccess: false;
212
- isPlaceholderData: false;
213
- status: "pending";
189
+ isSuccess: true;
190
+ isPlaceholderData: true;
191
+ status: 'success';
192
+ oauthJustCompleted: boolean;
193
+ oauthError: string | null;
194
+ } | {
214
195
  dataUpdatedAt: number;
215
196
  errorUpdatedAt: number;
216
197
  failureCount: number;
@@ -219,7 +200,6 @@ export declare function useOAuthConnectionStatus(accountId: string, options: Use
219
200
  isFetched: boolean;
220
201
  isFetchedAfterMount: boolean;
221
202
  isFetching: boolean;
222
- isLoading: boolean;
223
203
  isInitialLoading: boolean;
224
204
  isPaused: boolean;
225
205
  isRefetching: boolean;
@@ -230,32 +210,32 @@ export declare function useOAuthConnectionStatus(accountId: string, options: Use
230
210
  error: string | undefined;
231
211
  obtainedAt?: undefined;
232
212
  } | {
213
+ error?: undefined;
233
214
  isConnected: boolean;
234
215
  obtainedAt: string | null | undefined;
235
- error?: undefined;
236
216
  }, Error>>;
237
217
  fetchStatus: import("@tanstack/react-query").FetchStatus;
238
- } | {
239
- oauthJustCompleted: boolean;
240
- oauthError: string | null;
241
218
  data: {
242
219
  isConnected: boolean;
243
220
  error: string | undefined;
244
221
  obtainedAt?: undefined;
245
222
  } | {
223
+ error?: undefined;
246
224
  isConnected: boolean;
247
225
  obtainedAt: string | null | undefined;
248
- error?: undefined;
249
226
  };
250
- isError: false;
251
- error: null;
227
+ error: Error;
228
+ isError: true;
252
229
  isPending: false;
253
230
  isLoading: false;
254
231
  isLoadingError: false;
255
- isRefetchError: false;
256
- isSuccess: true;
257
- isPlaceholderData: true;
258
- status: "success";
232
+ isRefetchError: true;
233
+ isSuccess: false;
234
+ isPlaceholderData: false;
235
+ status: 'error';
236
+ oauthJustCompleted: boolean;
237
+ oauthError: string | null;
238
+ } | {
259
239
  dataUpdatedAt: number;
260
240
  errorUpdatedAt: number;
261
241
  failureCount: number;
@@ -274,11 +254,31 @@ export declare function useOAuthConnectionStatus(accountId: string, options: Use
274
254
  error: string | undefined;
275
255
  obtainedAt?: undefined;
276
256
  } | {
257
+ error?: undefined;
277
258
  isConnected: boolean;
278
259
  obtainedAt: string | null | undefined;
279
- error?: undefined;
280
260
  }, Error>>;
281
261
  fetchStatus: import("@tanstack/react-query").FetchStatus;
262
+ data: {
263
+ isConnected: boolean;
264
+ error: string | undefined;
265
+ obtainedAt?: undefined;
266
+ } | {
267
+ error?: undefined;
268
+ isConnected: boolean;
269
+ obtainedAt: string | null | undefined;
270
+ };
271
+ error: null;
272
+ isError: false;
273
+ isPending: false;
274
+ isLoading: false;
275
+ isLoadingError: false;
276
+ isRefetchError: false;
277
+ isSuccess: true;
278
+ isPlaceholderData: false;
279
+ status: 'success';
280
+ oauthJustCompleted: boolean;
281
+ oauthError: string | null;
282
282
  };
283
283
  /**
284
284
  * Resolves the gating booleans every OAuth-backed dashboard widget needs:
@@ -42,14 +42,18 @@ export function useOAuthConnectionStatus(accountId, options) {
42
42
  refetchOnWindowFocus: false,
43
43
  });
44
44
  const errorParamName = errorParam === null || errorParam === void 0 ? void 0 : errorParam.name;
45
- // Justified useEffect: after the OAuth redirect lands back
46
- // on this page with `?oauth_success=true`, we must (1) strip the transient
47
- // query params from the URL and (2) invalidate the cached connection status
48
- // so widgets flip to connected without a manual refresh. Both are
49
- // post-render side effects on external systems (history API + query
50
- // cache), not derivable during render.
45
+ const returnedWithError = oauthError !== null;
46
+ // Justified useEffect: after the OAuth redirect lands back on this page —
47
+ // with `?oauth_success=true` OR with the integration's error param — we
48
+ // must (1) strip the transient query params from the URL, so a reload does
49
+ // not re-announce a stale success or re-show a stale error, and (2) on
50
+ // success invalidate the cached connection status so widgets flip to
51
+ // connected without a manual refresh. Both are post-render side effects
52
+ // on external systems (history API + query cache), not derivable during
53
+ // render. The error itself stays in state for this render, so the alert
54
+ // still shows once.
51
55
  useEffect(() => {
52
- if (!oauthJustCompleted)
56
+ if (!oauthJustCompleted && !returnedWithError)
53
57
  return;
54
58
  const url = new URL(window.location.href);
55
59
  url.searchParams.delete('oauth_success');
@@ -57,11 +61,14 @@ export function useOAuthConnectionStatus(accountId, options) {
57
61
  url.searchParams.delete(errorParamName);
58
62
  }
59
63
  window.history.replaceState({}, '', url.pathname + url.search);
60
- queryClient.invalidateQueries({
61
- queryKey: ['integration-data', accountId, connectionKey],
62
- });
64
+ if (oauthJustCompleted) {
65
+ queryClient.invalidateQueries({
66
+ queryKey: ['integration-data', accountId, connectionKey],
67
+ });
68
+ }
63
69
  }, [
64
70
  oauthJustCompleted,
71
+ returnedWithError,
65
72
  accountId,
66
73
  connectionKey,
67
74
  errorParamName,
@@ -1 +1 @@
1
- {"version":3,"file":"use-oauth-connection-status.js","sourceRoot":"","sources":["../../src/hooks/use-oauth-connection-status.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { useQuery, useQueryClient } from '@tanstack/react-query';\n\n/**\n * Result shape of an integration's check-connection server action\n * (the standard enhanceAction envelope).\n */\nexport interface ConnectionCheckResult {\n success: boolean;\n data?: { obtainedAt?: string | null } | null;\n error?: string;\n}\n\nexport type ConnectionCheckAction = (input: {\n accountId: string;\n}) => Promise<ConnectionCheckResult>;\n\n/**\n * Optional OAuth failure query param an integration's connect route may\n * append on redirect (e.g. dropbox_error=credentials_not_configured).\n */\nexport interface OAuthErrorParam {\n name: string;\n /** Map the raw param value to a user-facing message; return null to ignore. */\n toMessage: (value: string) => string | null;\n}\n\nexport interface UseOAuthConnectionStatusOptions {\n /**\n * Cache key segment, by convention `<slug>-connection`. The full query key\n * is ['integration-data', accountId, connectionKey] so all widgets and the\n * activation form share one deduped request per dashboard load.\n */\n connectionKey: string;\n checkConnection: ConnectionCheckAction;\n /** Gate the query (e.g. on integration-active state). Defaults to true. */\n enabled?: boolean;\n errorParam?: OAuthErrorParam;\n}\n\n/**\n * Shared OAuth connection-status hook. Detects the `oauth_success=true`\n * return from an integration's connect route, strips the transient params\n * from the URL, and invalidates the cached status so dependent UI flips to\n * connected without a manual refresh.\n */\nexport function useOAuthConnectionStatus(\n accountId: string,\n options: UseOAuthConnectionStatusOptions,\n) {\n const {\n connectionKey,\n checkConnection,\n enabled = true,\n errorParam,\n } = options;\n const queryClient = useQueryClient();\n\n // Read the OAuth return params synchronously on mount (not in an effect)\n // so the first render already knows whether we just came back from OAuth.\n const [oauthJustCompleted] = useState(() => {\n if (typeof window === 'undefined') return false;\n const params = new URLSearchParams(window.location.search);\n return params.get('oauth_success') === 'true';\n });\n\n const [oauthError] = useState<string | null>(() => {\n if (!errorParam || typeof window === 'undefined') return null;\n const value = new URLSearchParams(window.location.search).get(\n errorParam.name,\n );\n return value ? errorParam.toMessage(value) : null;\n });\n\n const query = useQuery({\n queryKey: ['integration-data', accountId, connectionKey],\n queryFn: async () => {\n const result = await checkConnection({ accountId });\n\n if (!result.success) {\n return { isConnected: false, error: result.error };\n }\n\n return {\n isConnected: true,\n obtainedAt: result.data?.obtainedAt,\n };\n },\n enabled: enabled && !!accountId,\n staleTime: 5 * 60 * 1000,\n refetchOnWindowFocus: false,\n });\n\n const errorParamName = errorParam?.name;\n\n // Justified useEffect: after the OAuth redirect lands back\n // on this page with `?oauth_success=true`, we must (1) strip the transient\n // query params from the URL and (2) invalidate the cached connection status\n // so widgets flip to connected without a manual refresh. Both are\n // post-render side effects on external systems (history API + query\n // cache), not derivable during render.\n useEffect(() => {\n if (!oauthJustCompleted) return;\n\n const url = new URL(window.location.href);\n url.searchParams.delete('oauth_success');\n\n if (errorParamName) {\n url.searchParams.delete(errorParamName);\n }\n\n window.history.replaceState({}, '', url.pathname + url.search);\n\n queryClient.invalidateQueries({\n queryKey: ['integration-data', accountId, connectionKey],\n });\n }, [\n oauthJustCompleted,\n accountId,\n connectionKey,\n errorParamName,\n queryClient,\n ]);\n\n return { ...query, oauthJustCompleted, oauthError };\n}\n\n/**\n * Resolves the gating booleans every OAuth-backed dashboard widget needs:\n *\n * - `enabled` — the integration is active and an account id is present.\n * - `isConnected` — a live OAuth connection exists (access token).\n * - `dataEnabled` — safe to fire the widget's data query (active + connected).\n * - `connectionPending` — still resolving the connection status.\n * - `connectionError` — the connection check itself failed (network/5xx).\n * Distinct from `notConnected`: a transient failure should surface as an\n * error, not as a misleading \"connect X\" prompt.\n * - `notConnected` — activated, resolved without error, but no OAuth\n * connection. The widget should render its \"connect X\" inactive state.\n *\n * The underlying connection check shares one query key across all widgets,\n * so react-query dedupes it to a single request per dashboard load.\n */\nexport function useOAuthWidgetGate(\n accountId: string | undefined,\n integrationActive: boolean,\n options: Pick<\n UseOAuthConnectionStatusOptions,\n 'connectionKey' | 'checkConnection'\n >,\n) {\n const enabled = integrationActive && !!accountId;\n const query = useOAuthConnectionStatus(accountId ?? '', {\n ...options,\n enabled,\n });\n\n const connectionPending = enabled && query.isPending;\n const connectionError = enabled && query.isError;\n const isConnected = query.data?.isConnected === true;\n const dataEnabled = enabled && isConnected;\n // Only treat as \"not connected\" when the check resolved successfully with\n // no token — a query error is an error state, not a disconnected account.\n const notConnected =\n enabled && !connectionPending && !connectionError && !isConnected;\n\n return {\n enabled,\n isConnected,\n connectionPending,\n connectionError,\n dataEnabled,\n notConnected,\n };\n}\n"]}
1
+ {"version":3,"file":"use-oauth-connection-status.js","sourceRoot":"","sources":["../../src/hooks/use-oauth-connection-status.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { useQuery, useQueryClient } from '@tanstack/react-query';\n\n/**\n * Result shape of an integration's check-connection server action\n * (the standard server-action envelope).\n */\nexport interface ConnectionCheckResult {\n success: boolean;\n data?: { obtainedAt?: string | null } | null;\n error?: string;\n}\n\nexport type ConnectionCheckAction = (input: {\n accountId: string;\n}) => Promise<ConnectionCheckResult>;\n\n/**\n * Optional OAuth failure query param an integration's connect route may\n * append on redirect (e.g. dropbox_error=credentials_not_configured).\n */\nexport interface OAuthErrorParam {\n name: string;\n /** Map the raw param value to a user-facing message; return null to ignore. */\n toMessage: (value: string) => string | null;\n}\n\nexport interface UseOAuthConnectionStatusOptions {\n /**\n * Cache key segment, by convention `<slug>-connection`. The full query key\n * is ['integration-data', accountId, connectionKey] so all widgets and the\n * activation form share one deduped request per dashboard load.\n */\n connectionKey: string;\n checkConnection: ConnectionCheckAction;\n /** Gate the query (e.g. on integration-active state). Defaults to true. */\n enabled?: boolean;\n errorParam?: OAuthErrorParam;\n}\n\n/**\n * Shared OAuth connection-status hook. Detects the `oauth_success=true`\n * return from an integration's connect route, strips the transient params\n * from the URL, and invalidates the cached status so dependent UI flips to\n * connected without a manual refresh.\n */\nexport function useOAuthConnectionStatus(\n accountId: string,\n options: UseOAuthConnectionStatusOptions,\n) {\n const {\n connectionKey,\n checkConnection,\n enabled = true,\n errorParam,\n } = options;\n const queryClient = useQueryClient();\n\n // Read the OAuth return params synchronously on mount (not in an effect)\n // so the first render already knows whether we just came back from OAuth.\n const [oauthJustCompleted] = useState(() => {\n if (typeof window === 'undefined') return false;\n const params = new URLSearchParams(window.location.search);\n return params.get('oauth_success') === 'true';\n });\n\n const [oauthError] = useState<string | null>(() => {\n if (!errorParam || typeof window === 'undefined') return null;\n const value = new URLSearchParams(window.location.search).get(\n errorParam.name,\n );\n return value ? errorParam.toMessage(value) : null;\n });\n\n const query = useQuery({\n queryKey: ['integration-data', accountId, connectionKey],\n queryFn: async () => {\n const result = await checkConnection({ accountId });\n\n if (!result.success) {\n return { isConnected: false, error: result.error };\n }\n\n return {\n isConnected: true,\n obtainedAt: result.data?.obtainedAt,\n };\n },\n enabled: enabled && !!accountId,\n staleTime: 5 * 60 * 1000,\n refetchOnWindowFocus: false,\n });\n\n const errorParamName = errorParam?.name;\n\n const returnedWithError = oauthError !== null;\n\n // Justified useEffect: after the OAuth redirect lands back on this page —\n // with `?oauth_success=true` OR with the integration's error param — we\n // must (1) strip the transient query params from the URL, so a reload does\n // not re-announce a stale success or re-show a stale error, and (2) on\n // success invalidate the cached connection status so widgets flip to\n // connected without a manual refresh. Both are post-render side effects\n // on external systems (history API + query cache), not derivable during\n // render. The error itself stays in state for this render, so the alert\n // still shows once.\n useEffect(() => {\n if (!oauthJustCompleted && !returnedWithError) return;\n\n const url = new URL(window.location.href);\n url.searchParams.delete('oauth_success');\n\n if (errorParamName) {\n url.searchParams.delete(errorParamName);\n }\n\n window.history.replaceState({}, '', url.pathname + url.search);\n\n if (oauthJustCompleted) {\n queryClient.invalidateQueries({\n queryKey: ['integration-data', accountId, connectionKey],\n });\n }\n }, [\n oauthJustCompleted,\n returnedWithError,\n accountId,\n connectionKey,\n errorParamName,\n queryClient,\n ]);\n\n return { ...query, oauthJustCompleted, oauthError };\n}\n\n/**\n * Resolves the gating booleans every OAuth-backed dashboard widget needs:\n *\n * - `enabled` — the integration is active and an account id is present.\n * - `isConnected` — a live OAuth connection exists (access token).\n * - `dataEnabled` — safe to fire the widget's data query (active + connected).\n * - `connectionPending` — still resolving the connection status.\n * - `connectionError` — the connection check itself failed (network/5xx).\n * Distinct from `notConnected`: a transient failure should surface as an\n * error, not as a misleading \"connect X\" prompt.\n * - `notConnected` — activated, resolved without error, but no OAuth\n * connection. The widget should render its \"connect X\" inactive state.\n *\n * The underlying connection check shares one query key across all widgets,\n * so react-query dedupes it to a single request per dashboard load.\n */\nexport function useOAuthWidgetGate(\n accountId: string | undefined,\n integrationActive: boolean,\n options: Pick<\n UseOAuthConnectionStatusOptions,\n 'connectionKey' | 'checkConnection'\n >,\n) {\n const enabled = integrationActive && !!accountId;\n const query = useOAuthConnectionStatus(accountId ?? '', {\n ...options,\n enabled,\n });\n\n const connectionPending = enabled && query.isPending;\n const connectionError = enabled && query.isError;\n const isConnected = query.data?.isConnected === true;\n const dataEnabled = enabled && isConnected;\n // Only treat as \"not connected\" when the check resolved successfully with\n // no token — a query error is an error state, not a disconnected account.\n const notConnected =\n enabled && !connectionPending && !connectionError && !isConnected;\n\n return {\n enabled,\n isConnected,\n connectionPending,\n connectionError,\n dataEnabled,\n notConnected,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"widget-event-context.js","sourceRoot":"","sources":["../../src/hooks/widget-event-context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAa,aAAa,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAa3E,MAAM,qBAAqB,GAAG,aAAa,CACzC,IAAI,CACL,CAAC;AAMF;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAE,QAAQ,EAA4B;IACxE,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,GAAG,EAAyB,CAAC,CAAC;IAChE,yEAAyE;IACzE,2EAA2E;IAC3E,yEAAyE;IACzE,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,GAAG,EAAuB,CAAC,CAAC;IAElE,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,QAA+B,EAAE,EAAE;QAChE,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAErC,2EAA2E;QAC3E,qDAAqD;QACrD,KAAK,MAAM,KAAK,IAAI,kBAAkB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YACxD,IAAI,CAAC;gBACH,QAAQ,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,GAAG,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;QAED,OAAO,GAAG,EAAE;YACV,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,KAAwB,EAAE,EAAE;;QACvD,MAAM,KAAK,GAAgB;YACzB,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;YACvB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,MAAA,KAAK,CAAC,UAAU,mCAAI,KAAK;SACtC,CAAC;QAEF,wEAAwE;QACxE,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACrB,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;QAED,KAAK,MAAM,UAAU,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;YAChD,IAAI,CAAC;gBACH,UAAU,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,KAAC,qBAAqB,IAAC,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,YACjD,QAAQ,GACa,CACzB,CAAC;AACJ,CAAC;AAED,MAAM,QAAQ,GAA+B;IAC3C,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC;IACjB,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,OAAO,GAAG,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAE3C,OAAO,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,QAAQ,CAAC;AAC7B,CAAC","sourcesContent":["'use client';\n\nimport { ReactNode, createContext, use, useCallback, useRef } from 'react';\n\nimport type {\n PublishEventInput,\n WidgetEvent,\n WidgetEventSubscriber,\n} from './widget-event-types';\n\ninterface WidgetEventBusContextValue {\n publish: (input: PublishEventInput) => void;\n subscribe: (callback: WidgetEventSubscriber) => () => void;\n}\n\nconst WidgetEventBusContext = createContext<WidgetEventBusContextValue | null>(\n null,\n);\n\ninterface WidgetEventProviderProps {\n children: ReactNode;\n}\n\n/**\n * Provides the widget event bus to the dashboard tree.\n * Wrap around DashboardGrid so all widgets can publish and subscribe.\n */\nexport function WidgetEventProvider({ children }: WidgetEventProviderProps) {\n const subscribersRef = useRef(new Set<WidgetEventSubscriber>());\n // Latest REPLAYABLE event per subtype, replayed to late subscribers so a\n // widget that mounts AFTER a producer published (e.g. Deal Alerts mounting\n // after My Pipeline already loaded) still receives the most recent event\n // instead of hanging forever waiting for one that already fired. Only sticky\n // state events opt in via `replayable`; transient commands are never retained\n // here so a late mount / remount can't re-trigger them against stale state.\n const latestBySubtypeRef = useRef(new Map<string, WidgetEvent>());\n\n const subscribe = useCallback((callback: WidgetEventSubscriber) => {\n subscribersRef.current.add(callback);\n\n // Replay the latest retained event for each subtype to the new subscriber.\n // The subscriber's own filter decides what it keeps.\n for (const event of latestBySubtypeRef.current.values()) {\n try {\n callback(event);\n } catch (err) {\n console.error('[WidgetEventBus] Replay subscriber error:', err);\n }\n }\n\n return () => {\n subscribersRef.current.delete(callback);\n };\n }, []);\n\n const publish = useCallback((input: PublishEventInput) => {\n const event: WidgetEvent = {\n id: crypto.randomUUID(),\n subtype: input.subtype,\n sourceWidget: input.sourceWidget,\n sourceProduct: input.sourceProduct,\n payload: input.payload,\n timestamp: Date.now(),\n tier: 'client',\n replayable: input.replayable ?? false,\n };\n\n // Only sticky state events are retained for replay to late subscribers.\n if (event.replayable) {\n latestBySubtypeRef.current.set(event.subtype, event);\n }\n\n for (const subscriber of subscribersRef.current) {\n try {\n subscriber(event);\n } catch (err) {\n console.error('[WidgetEventBus] Subscriber error:', err);\n }\n }\n }, []);\n\n return (\n <WidgetEventBusContext value={{ publish, subscribe }}>\n {children}\n </WidgetEventBusContext>\n );\n}\n\nconst NOOP_BUS: WidgetEventBusContextValue = {\n publish: () => {},\n subscribe: () => () => {},\n};\n\n/**\n * Access the raw event bus for publishing and subscribing.\n * Returns a no-op bus when used outside a WidgetEventProvider\n * (e.g., admin widget preview panel).\n * Prefer `useWidgetEvents` for subscribing with filters.\n */\nexport function useWidgetEventBus() {\n const context = use(WidgetEventBusContext);\n\n return context ?? NOOP_BUS;\n}\n"]}
1
+ {"version":3,"file":"widget-event-context.js","sourceRoot":"","sources":["../../src/hooks/widget-event-context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAa,aAAa,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAa3E,MAAM,qBAAqB,GAAG,aAAa,CACzC,IAAI,CACL,CAAC;AAMF;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAE,QAAQ,EAA4B;IACxE,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,GAAG,EAAyB,CAAC,CAAC;IAChE,yEAAyE;IACzE,2EAA2E;IAC3E,yEAAyE;IACzE,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,GAAG,EAAuB,CAAC,CAAC;IAElE,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,QAA+B,EAAE,EAAE;QAChE,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAErC,2EAA2E;QAC3E,qDAAqD;QACrD,KAAK,MAAM,KAAK,IAAI,kBAAkB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YACxD,IAAI,CAAC;gBACH,QAAQ,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,GAAG,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;QAED,OAAO,GAAG,EAAE;YACV,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,KAAwB,EAAE,EAAE;;QACvD,MAAM,KAAK,GAAgB;YACzB,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;YACvB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,IAAI,EAAE,QAAQ;YACd,UAAU,QAAE,KAAK,CAAC,UAAU,mCAAI,KAAK;SACtC,CAAC;QAEF,wEAAwE;QACxE,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACrB,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;QAED,KAAK,MAAM,UAAU,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;YAChD,IAAI,CAAC;gBACH,UAAU,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,KAAC,qBAAqB,IAAC,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,YACjD,QAAQ,GACa,CACzB,CAAC;AACJ,CAAC;AAED,MAAM,QAAQ,GAA+B;IAC3C,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC;IACjB,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,OAAO,GAAG,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAE3C,OAAO,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,QAAQ,CAAC;AAC7B,CAAC","sourcesContent":["'use client';\n\nimport { ReactNode, createContext, use, useCallback, useRef } from 'react';\n\nimport type {\n PublishEventInput,\n WidgetEvent,\n WidgetEventSubscriber,\n} from './widget-event-types';\n\ninterface WidgetEventBusContextValue {\n publish: (input: PublishEventInput) => void;\n subscribe: (callback: WidgetEventSubscriber) => () => void;\n}\n\nconst WidgetEventBusContext = createContext<WidgetEventBusContextValue | null>(\n null,\n);\n\ninterface WidgetEventProviderProps {\n children: ReactNode;\n}\n\n/**\n * Provides the widget event bus to the dashboard tree.\n * Wrap around DashboardGrid so all widgets can publish and subscribe.\n */\nexport function WidgetEventProvider({ children }: WidgetEventProviderProps) {\n const subscribersRef = useRef(new Set<WidgetEventSubscriber>());\n // Latest REPLAYABLE event per subtype, replayed to late subscribers so a\n // widget that mounts AFTER a producer published (e.g. Deal Alerts mounting\n // after My Pipeline already loaded) still receives the most recent event\n // instead of hanging forever waiting for one that already fired. Only sticky\n // state events opt in via `replayable`; transient commands are never retained\n // here so a late mount / remount can't re-trigger them against stale state.\n const latestBySubtypeRef = useRef(new Map<string, WidgetEvent>());\n\n const subscribe = useCallback((callback: WidgetEventSubscriber) => {\n subscribersRef.current.add(callback);\n\n // Replay the latest retained event for each subtype to the new subscriber.\n // The subscriber's own filter decides what it keeps.\n for (const event of latestBySubtypeRef.current.values()) {\n try {\n callback(event);\n } catch (err) {\n console.error('[WidgetEventBus] Replay subscriber error:', err);\n }\n }\n\n return () => {\n subscribersRef.current.delete(callback);\n };\n }, []);\n\n const publish = useCallback((input: PublishEventInput) => {\n const event: WidgetEvent = {\n id: crypto.randomUUID(),\n subtype: input.subtype,\n sourceWidget: input.sourceWidget,\n sourceProduct: input.sourceProduct,\n payload: input.payload,\n timestamp: Date.now(),\n tier: 'client',\n replayable: input.replayable ?? false,\n };\n\n // Only sticky state events are retained for replay to late subscribers.\n if (event.replayable) {\n latestBySubtypeRef.current.set(event.subtype, event);\n }\n\n for (const subscriber of subscribersRef.current) {\n try {\n subscriber(event);\n } catch (err) {\n console.error('[WidgetEventBus] Subscriber error:', err);\n }\n }\n }, []);\n\n return (\n <WidgetEventBusContext value={{ publish, subscribe }}>\n {children}\n </WidgetEventBusContext>\n );\n}\n\nconst NOOP_BUS: WidgetEventBusContextValue = {\n publish: () => {},\n subscribe: () => () => {},\n};\n\n/**\n * Access the raw event bus for publishing and subscribing.\n * Returns a no-op bus when used outside a WidgetEventProvider\n * (e.g., admin widget preview panel).\n * Prefer `useWidgetEvents` for subscribing with filters.\n */\nexport function useWidgetEventBus() {\n const context = use(WidgetEventBusContext);\n\n return context ?? NOOP_BUS;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"event-surfaces.js","sourceRoot":"","sources":["../../src/integration/event-surfaces.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,MAAM,UAAU,wBAAwB,CACtC,UAAiC;;IAEjC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,QAAQ,IAAI,MAAA,UAAU,CAAC,SAAS,mCAAI,EAAE,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CACX,cAAc,QAAQ,CAAC,EAAE,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,MAAA,UAAU,CAAC,MAAM,mCAAI,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG;YAChB,CAAC,kBAAkB,EAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAChE,CAAC,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;SACxC,CAAC;QAEX,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,SAAS,EAAE,CAAC;YACrC,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,CAAC;gBACH,OAAO,GAAG,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,IAAI,CACX,kBAAkB,KAAK,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACrF,CAAC;gBACF,SAAS;YACX,CAAC;YACD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;gBACnC,QAAQ,CAAC,IAAI,CACX,kBAAkB,KAAK,gBAAgB,MAAM,qBAAqB;oBAChE,iCAAiC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ;oBAC/D,8DAA8D;oBAC9D,mBAAmB,MAAM,wCAAwC;oBACjE,+DAA+D;oBAC/D,wBAAwB,CAC3B,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,uCAAuC,UAAU,CAAC,IAAI,MAAM;YAC1D,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACnD,CAAC;IACJ,CAAC;AACH,CAAC","sourcesContent":["/**\n * SDK-layer validation of the three event surfaces (webhooks, schedules,\n * OAuth) — the cross-field rules the dependency-pure schema package cannot\n * own, layered on top of the canonical parse the same way\n * `parsePartnerIntegration` layers host workspace rules:\n *\n * - every `schedules[].cron` must be valid 5-field cron syntax\n * (`parseCronExpression`);\n * - the OAuth endpoints' origins must be covered by the definition's own\n * `egress` list, because the code exchange runs through the integration's\n * declared egress — an origin the reviewer never saw is an origin the\n * exchange must not reach.\n */\nimport type { IntegrationDefinition } from '@ekanos/integration-schema';\n\nimport { isEgressAllowed } from '../context/egress';\nimport { parseCronExpression } from './cron';\n\nexport function assertEventSurfacesValid(\n definition: IntegrationDefinition,\n): void {\n const problems: string[] = [];\n\n for (const schedule of definition.schedules ?? []) {\n try {\n parseCronExpression(schedule.cron);\n } catch (error) {\n problems.push(\n `schedules[\"${schedule.id}\"]: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n if (definition.oauth) {\n const egress = definition.egress ?? [];\n const endpoints = [\n ['authorizationUrl', definition.oauth.provider.authorizationUrl],\n ['tokenUrl', definition.oauth.provider.tokenUrl],\n ] as const;\n\n for (const [field, url] of endpoints) {\n let covered = false;\n try {\n covered = isEgressAllowed(url, egress);\n } catch (error) {\n problems.push(\n `oauth.provider.${field}: ${error instanceof Error ? error.message : String(error)}`,\n );\n continue;\n }\n if (!covered) {\n const origin = new URL(url).origin;\n problems.push(\n `oauth.provider.${field}: its origin ${origin} is not covered by ` +\n `this definition's egress list ${JSON.stringify(egress)}. The ` +\n `OAuth code exchange runs through the integration's declared ` +\n `egress, so add \"${origin}\" (or a covering \"*.\" wildcard entry) ` +\n `to \\`egress\\` — widening egress is a security-posture change ` +\n `reviewed at promotion.`,\n );\n }\n }\n }\n\n if (problems.length > 0) {\n throw new Error(\n `Invalid integration definition for \"${definition.slug}\":\\n` +\n problems.map((line) => ` - ${line}`).join('\\n'),\n );\n }\n}\n"]}
1
+ {"version":3,"file":"event-surfaces.js","sourceRoot":"","sources":["../../src/integration/event-surfaces.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,MAAM,UAAU,wBAAwB,CACtC,UAAiC;;IAEjC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,QAAQ,UAAI,UAAU,CAAC,SAAS,mCAAI,EAAE,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CACX,cAAc,QAAQ,CAAC,EAAE,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,MAAM,SAAG,UAAU,CAAC,MAAM,mCAAI,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG;YAChB,CAAC,kBAAkB,EAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAChE,CAAC,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;SACxC,CAAC;QAEX,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,SAAS,EAAE,CAAC;YACrC,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,CAAC;gBACH,OAAO,GAAG,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,IAAI,CACX,kBAAkB,KAAK,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACrF,CAAC;gBACF,SAAS;YACX,CAAC;YACD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;gBACnC,QAAQ,CAAC,IAAI,CACX,kBAAkB,KAAK,gBAAgB,MAAM,qBAAqB;oBAChE,iCAAiC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ;oBAC/D,8DAA8D;oBAC9D,mBAAmB,MAAM,wCAAwC;oBACjE,+DAA+D;oBAC/D,wBAAwB,CAC3B,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,uCAAuC,UAAU,CAAC,IAAI,MAAM;YAC1D,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACnD,CAAC;IACJ,CAAC;AACH,CAAC","sourcesContent":["/**\n * SDK-layer validation of the three event surfaces (webhooks, schedules,\n * OAuth) — the cross-field rules the dependency-pure schema package cannot\n * own, layered on top of the canonical parse the same way\n * `parsePartnerIntegration` layers host workspace rules:\n *\n * - every `schedules[].cron` must be valid 5-field cron syntax\n * (`parseCronExpression`);\n * - the OAuth endpoints' origins must be covered by the definition's own\n * `egress` list, because the code exchange runs through the integration's\n * declared egress — an origin the reviewer never saw is an origin the\n * exchange must not reach.\n */\nimport type { IntegrationDefinition } from '@ekanos/integration-schema';\n\nimport { isEgressAllowed } from '../context/egress';\nimport { parseCronExpression } from './cron';\n\nexport function assertEventSurfacesValid(\n definition: IntegrationDefinition,\n): void {\n const problems: string[] = [];\n\n for (const schedule of definition.schedules ?? []) {\n try {\n parseCronExpression(schedule.cron);\n } catch (error) {\n problems.push(\n `schedules[\"${schedule.id}\"]: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n if (definition.oauth) {\n const egress = definition.egress ?? [];\n const endpoints = [\n ['authorizationUrl', definition.oauth.provider.authorizationUrl],\n ['tokenUrl', definition.oauth.provider.tokenUrl],\n ] as const;\n\n for (const [field, url] of endpoints) {\n let covered = false;\n try {\n covered = isEgressAllowed(url, egress);\n } catch (error) {\n problems.push(\n `oauth.provider.${field}: ${error instanceof Error ? error.message : String(error)}`,\n );\n continue;\n }\n if (!covered) {\n const origin = new URL(url).origin;\n problems.push(\n `oauth.provider.${field}: its origin ${origin} is not covered by ` +\n `this definition's egress list ${JSON.stringify(egress)}. The ` +\n `OAuth code exchange runs through the integration's declared ` +\n `egress, so add \"${origin}\" (or a covering \"*.\" wildcard entry) ` +\n `to \\`egress\\` — widening egress is a security-posture change ` +\n `reviewed at promotion.`,\n );\n }\n }\n }\n\n if (problems.length > 0) {\n throw new Error(\n `Invalid integration definition for \"${definition.slug}\":\\n` +\n problems.map((line) => ` - ${line}`).join('\\n'),\n );\n }\n}\n"]}
@@ -17,5 +17,5 @@
17
17
  export { defineIntegration } from './define-integration.js';
18
18
  export { parseCronExpression } from './cron.js';
19
19
  export { PublicActionError } from '../errors/public-action-error.js';
20
- export { IntegrationDefinitionSchema, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
21
- export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, ToolClassificationProposal, DefinitionCollisionInput, FirstPartyInventory, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookEvent, WebhookResult, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, OAuthProviderDeclaration, OAuthTokens, OnActivateHandler, } from '@ekanos/integration-schema';
20
+ export { DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
21
+ export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, PartnerActionDeclaration, ToolClassificationProposal, DefinitionCollisionInput, FirstPartyInventory, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookSignatureScheme, WebhookEvent, WebhookResult, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, OAuthProviderDeclaration, OAuthTokens, OnActivateHandler, } from '@ekanos/integration-schema';
@@ -23,5 +23,5 @@ export { parseCronExpression } from './cron.js';
23
23
  // discipline — see the class's own doc comment
24
24
  // (the widget-actions opaque-failure ruling).
25
25
  export { PublicActionError } from '../errors/public-action-error.js';
26
- export { IntegrationDefinitionSchema, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
26
+ export { DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
27
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integration/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,mEAAmE;AACnE,0EAA0E;AAC1E,0EAA0E;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,oEAAoE;AACpE,+CAA+C;AAC/C,8CAA8C;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE,OAAO,EACL,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * @ekanos/sdk/integration — the partner authoring contract.\n *\n * `defineIntegration()` is THE way a partner declares an integration\n * Partners never extend a base class.\n * The host adapts the returned definition internally via\n * `registerPartnerIntegration` in `@kit/integrations-core`, which re-parses\n * against the SAME canonical schema (`@ekanos/integration-schema`) at the\n * trust boundary — one contract, no structural twin (F3/F9).\n *\n * Dependency-pure and isomorphic: zod plus the schema package, no `@kit/*`,\n * no `server-only`.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { defineIntegration } from './define-integration';\n\n// Event-surface validation values: the 5-field cron parser backing\n// `defineIntegration()`'s schedule validation, exported so validators and\n// the harness can check/describe an expression the same way the SDK does.\nexport { parseCronExpression } from './cron';\n\n// The one escape hatch from the actions host route's opaque-failure\n// discipline — see the class's own doc comment\n// (the widget-actions opaque-failure ruling).\nexport { PublicActionError } from '../errors/public-action-error';\n\nexport {\n IntegrationDefinitionSchema,\n validateIntegrationDefinitions,\n} from '@ekanos/integration-schema';\n\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n ToolClassificationProposal,\n DefinitionCollisionInput,\n FirstPartyInventory,\n // Event surfaces (webhooks, schedules, OAuth): declared in the definition,\n // executed locally by the harness/testing helpers today; the host's real\n // transports (public ingress, scheduler, hosted OAuth callback) bind to\n // these exact declarations later with no partner code change.\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookEvent,\n WebhookResult,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n OAuthProviderDeclaration,\n OAuthTokens,\n // Activation lifecycle hook: cache seeding / eager validation at connect\n // time. Runs after first persist and after every activationData update;\n // v1 errors are non-fatal (logged as a warning, activation stays active).\n OnActivateHandler,\n} from '@ekanos/integration-schema';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integration/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,mEAAmE;AACnE,0EAA0E;AAC1E,0EAA0E;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,oEAAoE;AACpE,+CAA+C;AAC/C,8CAA8C;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE,OAAO,EACL,mCAAmC,EACnC,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * @ekanos/sdk/integration — the partner authoring contract.\n *\n * `defineIntegration()` is THE way a partner declares an integration\n * Partners never extend a base class.\n * The host adapts the returned definition internally via\n * `registerPartnerIntegration` in `@kit/integrations-core`, which re-parses\n * against the SAME canonical schema (`@ekanos/integration-schema`) at the\n * trust boundary — one contract, no structural twin (F3/F9).\n *\n * Dependency-pure and isomorphic: zod plus the schema package, no `@kit/*`,\n * no `server-only`.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { defineIntegration } from './define-integration';\n\n// Event-surface validation values: the 5-field cron parser backing\n// `defineIntegration()`'s schedule validation, exported so validators and\n// the harness can check/describe an expression the same way the SDK does.\nexport { parseCronExpression } from './cron';\n\n// The one escape hatch from the actions host route's opaque-failure\n// discipline — see the class's own doc comment\n// (the widget-actions opaque-failure ruling).\nexport { PublicActionError } from '../errors/public-action-error';\n\nexport {\n DEFAULT_SIGNATURE_TOLERANCE_SECONDS,\n IntegrationDefinitionSchema,\n validateIntegrationDefinitions,\n} from '@ekanos/integration-schema';\n\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n // Widget-invoked server handlers (`actions`): the declaration type a\n // partner needs to export a handler shared between an action and a tool.\n PartnerActionDeclaration,\n ToolClassificationProposal,\n DefinitionCollisionInput,\n FirstPartyInventory,\n // Event surfaces (webhooks, schedules, OAuth): declared in the definition,\n // executed locally by the harness/testing helpers today; the host's real\n // transports (public ingress, scheduler, hosted OAuth callback) bind to\n // these exact declarations later with no partner code change.\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookSignatureScheme,\n WebhookEvent,\n WebhookResult,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n OAuthProviderDeclaration,\n OAuthTokens,\n // Activation lifecycle hook: cache seeding / eager validation at connect\n // time. Runs after first persist and after every activationData update;\n // v1 errors are non-fatal (logged as a warning, activation stays active).\n OnActivateHandler,\n} from '@ekanos/integration-schema';\n"]}
@@ -28,11 +28,15 @@ export async function invokeWebhook(definition, webhookId, payload, options = {}
28
28
  throw new Error(`Integration "${definition.slug}" declares no webhook "${webhookId}". ` +
29
29
  `Declared webhook ids: ${listIds(((_b = definition.webhooks) !== null && _b !== void 0 ? _b : []).map((w) => w.id))}.`);
30
30
  }
31
- const parsed = webhook.payloadSchema.safeParse(payload);
32
- if (!parsed.success) {
33
- const issues = parsed.error.issues
31
+ // The host's webhook route validates through Standard Schema too, so a
32
+ // partner test exercises the exact call production makes.
33
+ const parsed = await webhook.payloadSchema['~standard'].validate(payload);
34
+ if (parsed.issues) {
35
+ const issues = parsed.issues
34
36
  .map((issue) => {
35
- const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
37
+ var _a;
38
+ const segments = ((_a = issue.path) !== null && _a !== void 0 ? _a : []).map((segment) => typeof segment === 'object' ? String(segment.key) : String(segment));
39
+ const path = segments.length > 0 ? segments.join('.') : '(root)';
36
40
  return ` - ${path}: ${issue.message}`;
37
41
  })
38
42
  .join('\n');
@@ -53,7 +57,7 @@ export async function invokeWebhook(definition, webhookId, payload, options = {}
53
57
  id: (_c = options.eventId) !== null && _c !== void 0 ? _c : `evt_${crypto.randomUUID()}`,
54
58
  receivedAt: (_d = options.receivedAt) !== null && _d !== void 0 ? _d : new Date().toISOString(),
55
59
  headers: (_e = options.headers) !== null && _e !== void 0 ? _e : {},
56
- payload: parsed.data,
60
+ payload: parsed.value,
57
61
  };
58
62
  const result = await webhook.handler(ctx, event);
59
63
  return { result, event, ctx };
@@ -1 +1 @@
1
- {"version":3,"file":"invoke.js","sourceRoot":"","sources":["../../src/testing/invoke.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local execution of the declared event surfaces — `invokeWebhook()` and\n * `invokeSchedule()` deliver to a definition's handlers exactly the way a\n * transport would, against a `createMockContext()` built FROM the definition\n * (its slug, storage schemas, and egress list), so a handler unit test\n * exercises the same schemas and the same egress allowlist production will.\n * `invokeActivate()` does the same for the `onActivate` lifecycle hook.\n *\n * Transport semantics reproduced here, so they cannot drift from the docs:\n * - the payload is parsed against `payloadSchema` BEFORE the handler runs;\n * an invalid payload throws and the handler never sees it;\n * - signature verification is the TRANSPORT's job — the local transport\n * logs it as skipped (one warn line on `ctx.logs`), it never verifies and\n * the handler never does either;\n * - a schedule invocation carries `{scheduledFor, invokedAt, trigger}` with\n * `trigger: 'manual'` by default (a human pressed the button);\n * - `invokeActivate()` simply awaits `onActivate(ctx)` and returns the\n * context it ran against — a throw PROPAGATES here. The non-fatal\n * (log-and-continue) handling described on `OnActivateHandler` is HOST\n * POLICY, applied by the transport that calls the hook in production\n * (and by the harness's activation surface locally); this helper is a\n * bare invoker; a test asserting the non-fatal behavior should catch its\n * own rejection.\n */\nimport type {\n IntegrationDefinition,\n ScheduleInvocation,\n ScheduleResult,\n StorageSchemas,\n WebhookEvent,\n WebhookResult,\n} from '@ekanos/integration-schema';\n\nimport {\n type MockContextOptions,\n type MockIntegrationContext,\n createMockContext,\n} from './mock-context';\n\n/**\n * Everything `createMockContext` takes except the fields the definition\n * itself is the authority on — the helpers derive `integration`,\n * `storageSchemas`, and `egress` from the definition so a test cannot\n * accidentally run a handler against schemas or an allowlist the definition\n * does not declare.\n */\nexport type DefinitionContextOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> = Omit<\n MockContextOptions<Schemas>,\n 'integration' | 'storageSchemas' | 'egress'\n>;\n\ninterface BaseInvokeOptions<Schemas extends StorageSchemas> {\n /**\n * Reuse an existing mock context so state (storage, secrets, logs)\n * accumulates across invocations — the harness does this. When set,\n * `context` wins and `contextOptions` must be omitted.\n */\n context?: MockIntegrationContext<Schemas>;\n /** Seeds and stubs for the context the helper creates. */\n contextOptions?: DefinitionContextOptions<Schemas>;\n}\n\nexport interface InvokeWebhookOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /** Delivery headers the event carries. Defaults to `{}`. */\n headers?: Record<string, string>;\n /** Transport-assigned event id. Defaults to a random UUID. */\n eventId?: string;\n /** ISO receipt time. Defaults to now. */\n receivedAt?: string;\n}\n\nexport interface InvokeScheduleOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /** Defaults to `'manual'` — a human pressed \"Run now\". */\n trigger?: ScheduleInvocation['trigger'];\n /** The tick this invocation stands for. Defaults to now. */\n scheduledFor?: string;\n}\n\nexport type InvokeActivateOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> = BaseInvokeOptions<Schemas>;\n\nexport interface ActivateInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n /** The context the handler ran against — assert on its recordings. */\n ctx: MockIntegrationContext<Schemas>;\n}\n\nexport interface WebhookInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n result: WebhookResult;\n /** The event the handler received (payload already schema-parsed). */\n event: WebhookEvent;\n /** The context the handler ran against — assert on its recordings. */\n ctx: MockIntegrationContext<Schemas>;\n}\n\nexport interface ScheduleInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n result: ScheduleResult;\n invocation: ScheduleInvocation;\n ctx: MockIntegrationContext<Schemas>;\n}\n\nfunction contextFor<Schemas extends StorageSchemas>(\n definition: IntegrationDefinition<Schemas>,\n options: BaseInvokeOptions<Schemas>,\n): MockIntegrationContext<Schemas> {\n if (options.context) {\n if (options.contextOptions) {\n throw new TypeError(\n 'Pass either `context` (reuse an existing mock context) or ' +\n '`contextOptions` (seed a fresh one), not both — seeds cannot be ' +\n 'applied to a context that already exists.',\n );\n }\n return options.context;\n }\n\n return createMockContext<Schemas>({\n ...(options.contextOptions ?? {}),\n integration: { slug: definition.slug },\n ...(definition.storage ? { storageSchemas: definition.storage } : {}),\n egress: definition.egress ?? [],\n });\n}\n\nfunction listIds(ids: readonly string[]): string {\n return ids.length > 0 ? ids.map((id) => `\"${id}\"`).join(', ') : '(none)';\n}\n\n/**\n * Delivers one payload to one declared webhook, the way a transport would.\n * Throws if the id is undeclared or the payload fails `payloadSchema`;\n * returns the handler's result plus the event and the context it ran\n * against. Never verifies signatures — that is the transport's job, and the\n * local transport records the skip as a `warn` log line.\n */\nexport async function invokeWebhook<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n webhookId: string,\n payload: unknown,\n options: InvokeWebhookOptions<Schemas> = {},\n): Promise<WebhookInvocationOutcome<Schemas>> {\n const webhook = (definition.webhooks ?? []).find((w) => w.id === webhookId);\n\n if (!webhook) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no webhook \"${webhookId}\". ` +\n `Declared webhook ids: ${listIds((definition.webhooks ?? []).map((w) => w.id))}.`,\n );\n }\n\n const parsed = webhook.payloadSchema.safeParse(payload);\n\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';\n return ` - ${path}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(\n `Payload rejected by webhook \"${webhookId}\"'s payloadSchema — the ` +\n `transport refuses such a delivery before the handler runs:\\n${issues}`,\n );\n }\n\n const ctx = contextFor(definition, options);\n\n if (webhook.signature !== 'none') {\n ctx.logger.warn(\n {\n webhookId,\n header: webhook.signature.header,\n secretName: webhook.signature.secretName,\n },\n 'Signature verification SKIPPED (local transport). The host ingress ' +\n 'verifies this header against the named secret before the handler ' +\n 'runs — handlers never verify signatures themselves.',\n );\n }\n\n const event: WebhookEvent = {\n id: options.eventId ?? `evt_${crypto.randomUUID()}`,\n receivedAt: options.receivedAt ?? new Date().toISOString(),\n headers: options.headers ?? {},\n payload: parsed.data,\n };\n\n const result = await webhook.handler(ctx, event);\n\n return { result, event, ctx };\n}\n\n/**\n * Fires one declared schedule, the way the scheduler would. Throws if the id\n * is undeclared; returns the handler's result plus the invocation and the\n * context it ran against.\n */\nexport async function invokeSchedule<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n scheduleId: string,\n options: InvokeScheduleOptions<Schemas> = {},\n): Promise<ScheduleInvocationOutcome<Schemas>> {\n const schedule = (definition.schedules ?? []).find(\n (s) => s.id === scheduleId,\n );\n\n if (!schedule) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no schedule \"${scheduleId}\". ` +\n `Declared schedule ids: ${listIds((definition.schedules ?? []).map((s) => s.id))}.`,\n );\n }\n\n const ctx = contextFor(definition, options);\n const now = new Date().toISOString();\n\n const invocation: ScheduleInvocation = {\n scheduledFor: options.scheduledFor ?? now,\n invokedAt: now,\n trigger: options.trigger ?? 'manual',\n };\n\n const result = await schedule.handler(ctx, invocation);\n\n return { result, invocation, ctx };\n}\n\n/**\n * Runs the definition's declared `onActivate` hook, the way the host would\n * after an activation persists or activationData updates. Throws if the\n * definition declares no `onActivate` — there is nothing to invoke, and a\n * silent no-op would let a test believe it exercised a hook that does not\n * exist. A throwing handler PROPAGATES from this helper: the non-fatal\n * (log-a-warning, keep the activation) handling is host policy applied by\n * whatever calls this in production/the harness, not by this bare invoker\n * (see the module doc comment).\n */\nexport async function invokeActivate<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n options: InvokeActivateOptions<Schemas> = {},\n): Promise<ActivateInvocationOutcome<Schemas>> {\n if (!definition.onActivate) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no onActivate hook — add ` +\n `one to defineIntegration() before invoking it.`,\n );\n }\n\n const ctx = contextFor(definition, options);\n\n await definition.onActivate(ctx);\n\n return { ctx };\n}\n"]}
1
+ {"version":3,"file":"invoke.js","sourceRoot":"","sources":["../../src/testing/invoke.ts"],"names":[],"mappings":"AAiCA,OAAO,EAGL,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AA4ExB,SAAS,UAAU,CACjB,UAA0C,EAC1C,OAAmC;;IAEnC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;YAC3B,MAAM,IAAI,SAAS,CACjB,4DAA4D;gBAC1D,kEAAkE;gBAClE,2CAA2C,CAC9C,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CAAC;IACzB,CAAC;IAED,OAAO,iBAAiB,6DACnB,OAAC,OAAO,CAAC,cAAc,mCAAI,EAAE,CAAC,KACjC,WAAW,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,KACnC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KACrE,MAAM,QAAE,UAAU,CAAC,MAAM,mCAAI,EAAE,IAC/B,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,GAAsB;IACrC,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC3E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAGjC,UAA0C,EAC1C,SAAiB,EACjB,OAAgB,EAChB,OAAO,GAAkC,EAAE;;IAE3C,MAAM,OAAO,GAAG,OAAC,UAAU,CAAC,QAAQ,mCAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC;IAE5E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,gBAAgB,UAAU,CAAC,IAAI,0BAA0B,SAAS,KAAK;YACrE,yBAAyB,OAAO,CAAC,OAAC,UAAU,CAAC,QAAQ,mCAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CACpF,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,0DAA0D;IAC1D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAE1E,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM;aACzB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;;YACb,MAAM,QAAQ,GAAG,OAAC,KAAK,CAAC,IAAI,mCAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAClD,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CACpE,CAAC;YACF,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;YACjE,OAAO,OAAO,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACzC,CAAC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,KAAK,CACb,gCAAgC,SAAS,0BAA0B;YACjE,+DAA+D,MAAM,EAAE,CAC1E,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAE5C,IAAI,OAAO,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QACjC,GAAG,CAAC,MAAM,CAAC,IAAI,CACb;YACE,SAAS;YACT,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,MAAM;YAChC,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC,UAAU;SACzC,EACD,qEAAqE;YACnE,mEAAmE;YACnE,qDAAqD,CACxD,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAiB;QAC1B,EAAE,QAAE,OAAO,CAAC,OAAO,mCAAI,OAAO,MAAM,CAAC,UAAU,EAAE,EAAE;QACnD,UAAU,QAAE,OAAO,CAAC,UAAU,mCAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC1D,OAAO,QAAE,OAAO,CAAC,OAAO,mCAAI,EAAE;QAC9B,OAAO,EAAE,MAAM,CAAC,KAAK;KACtB,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAEjD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAGlC,UAA0C,EAC1C,UAAkB,EAClB,OAAO,GAAmC,EAAE;;IAE5C,MAAM,QAAQ,GAAG,OAAC,UAAU,CAAC,SAAS,mCAAI,EAAE,CAAC,CAAC,IAAI,CAChD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,UAAU,CAC3B,CAAC;IAEF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,gBAAgB,UAAU,CAAC,IAAI,2BAA2B,UAAU,KAAK;YACvE,0BAA0B,OAAO,CAAC,OAAC,UAAU,CAAC,SAAS,mCAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CACtF,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,UAAU,GAAuB;QACrC,YAAY,QAAE,OAAO,CAAC,YAAY,mCAAI,GAAG;QACzC,SAAS,EAAE,GAAG;QACd,OAAO,QAAE,OAAO,CAAC,OAAO,mCAAI,QAAQ;KACrC,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAEvD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAGlC,UAA0C,EAC1C,OAAO,GAAmC,EAAE;IAE5C,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,gBAAgB,UAAU,CAAC,IAAI,sCAAsC;YACnE,gDAAgD,CACnD,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAE5C,MAAM,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAEjC,OAAO,EAAE,GAAG,EAAE,CAAC;AACjB,CAAC","sourcesContent":["/**\n * Local execution of the declared event surfaces — `invokeWebhook()` and\n * `invokeSchedule()` deliver to a definition's handlers exactly the way a\n * transport would, against a `createMockContext()` built FROM the definition\n * (its slug, storage schemas, and egress list), so a handler unit test\n * exercises the same schemas and the same egress allowlist production will.\n * `invokeActivate()` does the same for the `onActivate` lifecycle hook.\n *\n * Transport semantics reproduced here, so they cannot drift from the docs:\n * - the payload is parsed against `payloadSchema` BEFORE the handler runs;\n * an invalid payload throws and the handler never sees it;\n * - signature verification is the TRANSPORT's job — the local transport\n * logs it as skipped (one warn line on `ctx.logs`), it never verifies and\n * the handler never does either;\n * - a schedule invocation carries `{scheduledFor, invokedAt, trigger}` with\n * `trigger: 'manual'` by default (a human pressed the button);\n * - `invokeActivate()` simply awaits `onActivate(ctx)` and returns the\n * context it ran against — a throw PROPAGATES here. The non-fatal\n * (log-and-continue) handling described on `OnActivateHandler` is HOST\n * POLICY, applied by the transport that calls the hook in production\n * (and by the harness's activation surface locally); this helper is a\n * bare invoker; a test asserting the non-fatal behavior should catch its\n * own rejection.\n */\nimport type {\n IntegrationDefinition,\n ScheduleInvocation,\n ScheduleResult,\n StorageSchemas,\n WebhookEvent,\n WebhookResult,\n} from '@ekanos/integration-schema';\n\nimport {\n type MockContextOptions,\n type MockIntegrationContext,\n createMockContext,\n} from './mock-context';\n\n/**\n * Everything `createMockContext` takes except the fields the definition\n * itself is the authority on — the helpers derive `integration`,\n * `storageSchemas`, and `egress` from the definition so a test cannot\n * accidentally run a handler against schemas or an allowlist the definition\n * does not declare.\n */\nexport type DefinitionContextOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> = Omit<\n MockContextOptions<Schemas>,\n 'integration' | 'storageSchemas' | 'egress'\n>;\n\ninterface BaseInvokeOptions<Schemas extends StorageSchemas> {\n /**\n * Reuse an existing mock context so state (storage, secrets, logs)\n * accumulates across invocations — the harness does this. When set,\n * `context` wins and `contextOptions` must be omitted.\n */\n context?: MockIntegrationContext<Schemas>;\n /** Seeds and stubs for the context the helper creates. */\n contextOptions?: DefinitionContextOptions<Schemas>;\n}\n\nexport interface InvokeWebhookOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /** Delivery headers the event carries. Defaults to `{}`. */\n headers?: Record<string, string>;\n /** Transport-assigned event id. Defaults to a random UUID. */\n eventId?: string;\n /** ISO receipt time. Defaults to now. */\n receivedAt?: string;\n}\n\nexport interface InvokeScheduleOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> extends BaseInvokeOptions<Schemas> {\n /** Defaults to `'manual'` — a human pressed \"Run now\". */\n trigger?: ScheduleInvocation['trigger'];\n /** The tick this invocation stands for. Defaults to now. */\n scheduledFor?: string;\n}\n\nexport type InvokeActivateOptions<\n Schemas extends StorageSchemas = StorageSchemas,\n> = BaseInvokeOptions<Schemas>;\n\nexport interface ActivateInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n /** The context the handler ran against — assert on its recordings. */\n ctx: MockIntegrationContext<Schemas>;\n}\n\nexport interface WebhookInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n result: WebhookResult;\n /** The event the handler received (payload already schema-parsed). */\n event: WebhookEvent;\n /** The context the handler ran against — assert on its recordings. */\n ctx: MockIntegrationContext<Schemas>;\n}\n\nexport interface ScheduleInvocationOutcome<\n Schemas extends StorageSchemas = StorageSchemas,\n> {\n result: ScheduleResult;\n invocation: ScheduleInvocation;\n ctx: MockIntegrationContext<Schemas>;\n}\n\nfunction contextFor<Schemas extends StorageSchemas>(\n definition: IntegrationDefinition<Schemas>,\n options: BaseInvokeOptions<Schemas>,\n): MockIntegrationContext<Schemas> {\n if (options.context) {\n if (options.contextOptions) {\n throw new TypeError(\n 'Pass either `context` (reuse an existing mock context) or ' +\n '`contextOptions` (seed a fresh one), not both — seeds cannot be ' +\n 'applied to a context that already exists.',\n );\n }\n return options.context;\n }\n\n return createMockContext<Schemas>({\n ...(options.contextOptions ?? {}),\n integration: { slug: definition.slug },\n ...(definition.storage ? { storageSchemas: definition.storage } : {}),\n egress: definition.egress ?? [],\n });\n}\n\nfunction listIds(ids: readonly string[]): string {\n return ids.length > 0 ? ids.map((id) => `\"${id}\"`).join(', ') : '(none)';\n}\n\n/**\n * Delivers one payload to one declared webhook, the way a transport would.\n * Throws if the id is undeclared or the payload fails `payloadSchema`;\n * returns the handler's result plus the event and the context it ran\n * against. Never verifies signatures — that is the transport's job, and the\n * local transport records the skip as a `warn` log line.\n */\nexport async function invokeWebhook<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n webhookId: string,\n payload: unknown,\n options: InvokeWebhookOptions<Schemas> = {},\n): Promise<WebhookInvocationOutcome<Schemas>> {\n const webhook = (definition.webhooks ?? []).find((w) => w.id === webhookId);\n\n if (!webhook) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no webhook \"${webhookId}\". ` +\n `Declared webhook ids: ${listIds((definition.webhooks ?? []).map((w) => w.id))}.`,\n );\n }\n\n // The host's webhook route validates through Standard Schema too, so a\n // partner test exercises the exact call production makes.\n const parsed = await webhook.payloadSchema['~standard'].validate(payload);\n\n if (parsed.issues) {\n const issues = parsed.issues\n .map((issue) => {\n const segments = (issue.path ?? []).map((segment) =>\n typeof segment === 'object' ? String(segment.key) : String(segment),\n );\n const path = segments.length > 0 ? segments.join('.') : '(root)';\n return ` - ${path}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(\n `Payload rejected by webhook \"${webhookId}\"'s payloadSchema — the ` +\n `transport refuses such a delivery before the handler runs:\\n${issues}`,\n );\n }\n\n const ctx = contextFor(definition, options);\n\n if (webhook.signature !== 'none') {\n ctx.logger.warn(\n {\n webhookId,\n header: webhook.signature.header,\n secretName: webhook.signature.secretName,\n },\n 'Signature verification SKIPPED (local transport). The host ingress ' +\n 'verifies this header against the named secret before the handler ' +\n 'runs — handlers never verify signatures themselves.',\n );\n }\n\n const event: WebhookEvent = {\n id: options.eventId ?? `evt_${crypto.randomUUID()}`,\n receivedAt: options.receivedAt ?? new Date().toISOString(),\n headers: options.headers ?? {},\n payload: parsed.value,\n };\n\n const result = await webhook.handler(ctx, event);\n\n return { result, event, ctx };\n}\n\n/**\n * Fires one declared schedule, the way the scheduler would. Throws if the id\n * is undeclared; returns the handler's result plus the invocation and the\n * context it ran against.\n */\nexport async function invokeSchedule<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n scheduleId: string,\n options: InvokeScheduleOptions<Schemas> = {},\n): Promise<ScheduleInvocationOutcome<Schemas>> {\n const schedule = (definition.schedules ?? []).find(\n (s) => s.id === scheduleId,\n );\n\n if (!schedule) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no schedule \"${scheduleId}\". ` +\n `Declared schedule ids: ${listIds((definition.schedules ?? []).map((s) => s.id))}.`,\n );\n }\n\n const ctx = contextFor(definition, options);\n const now = new Date().toISOString();\n\n const invocation: ScheduleInvocation = {\n scheduledFor: options.scheduledFor ?? now,\n invokedAt: now,\n trigger: options.trigger ?? 'manual',\n };\n\n const result = await schedule.handler(ctx, invocation);\n\n return { result, invocation, ctx };\n}\n\n/**\n * Runs the definition's declared `onActivate` hook, the way the host would\n * after an activation persists or activationData updates. Throws if the\n * definition declares no `onActivate` — there is nothing to invoke, and a\n * silent no-op would let a test believe it exercised a hook that does not\n * exist. A throwing handler PROPAGATES from this helper: the non-fatal\n * (log-a-warning, keep the activation) handling is host policy applied by\n * whatever calls this in production/the harness, not by this bare invoker\n * (see the module doc comment).\n */\nexport async function invokeActivate<\n Schemas extends StorageSchemas = StorageSchemas,\n>(\n definition: IntegrationDefinition<Schemas>,\n options: InvokeActivateOptions<Schemas> = {},\n): Promise<ActivateInvocationOutcome<Schemas>> {\n if (!definition.onActivate) {\n throw new Error(\n `Integration \"${definition.slug}\" declares no onActivate hook — add ` +\n `one to defineIntegration() before invoking it.`,\n );\n }\n\n const ctx = contextFor(definition, options);\n\n await definition.onActivate(ctx);\n\n return { ctx };\n}\n"]}
@@ -77,12 +77,13 @@ function dumpScope(rows) {
77
77
  return out;
78
78
  }
79
79
  export function createMockContext(options) {
80
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
80
+ var _a, _b, _c, _d;
81
+ var _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
81
82
  const userId = options.userId !== undefined ? options.userId : 'mock-user-id';
82
- const actor = (_a = options.actor) !== null && _a !== void 0 ? _a : (userId !== null
83
+ const actor = (_e = options.actor) !== null && _e !== void 0 ? _e : (userId !== null
83
84
  ? { kind: 'user', userId }
84
85
  : { kind: 'machine', tokenId: 'mock-token-id', createdBy: null });
85
- const schemas = (_b = options.storageSchemas) !== null && _b !== void 0 ? _b : {};
86
+ const schemas = (_f = options.storageSchemas) !== null && _f !== void 0 ? _f : {};
86
87
  const accountRows = new Map();
87
88
  const userRows = new Map();
88
89
  const seedScope = (scope, rows, seed) => {
@@ -102,8 +103,8 @@ export function createMockContext(options) {
102
103
  });
103
104
  }
104
105
  };
105
- seedScope('account', accountRows, (_c = options.storage) === null || _c === void 0 ? void 0 : _c.account);
106
- seedScope('user', userRows, (_d = options.storage) === null || _d === void 0 ? void 0 : _d.user);
106
+ seedScope('account', accountRows, (_a = options.storage) === null || _a === void 0 ? void 0 : _a.account);
107
+ seedScope('user', userRows, (_b = options.storage) === null || _b === void 0 ? void 0 : _b.user);
107
108
  const guardUserScope = () => {
108
109
  if (actor.kind === 'machine') {
109
110
  throw new Error('ctx.storage.user is unavailable for machine actors: there is no ' +
@@ -128,8 +129,8 @@ export function createMockContext(options) {
128
129
  guard: guardUserScope,
129
130
  }),
130
131
  };
131
- const accountSecrets = new Map(Object.entries((_f = (_e = options.secrets) === null || _e === void 0 ? void 0 : _e.account) !== null && _f !== void 0 ? _f : {}));
132
- const adminSecrets = new Map(Object.entries((_h = (_g = options.secrets) === null || _g === void 0 ? void 0 : _g.admin) !== null && _h !== void 0 ? _h : {}));
132
+ const accountSecrets = new Map(Object.entries((_g = (_c = options.secrets) === null || _c === void 0 ? void 0 : _c.account) !== null && _g !== void 0 ? _g : {}));
133
+ const adminSecrets = new Map(Object.entries((_h = (_d = options.secrets) === null || _d === void 0 ? void 0 : _d.admin) !== null && _h !== void 0 ? _h : {}));
133
134
  const secrets = {
134
135
  async get(name) {
135
136
  var _a, _b;