@ekanos/sdk 0.1.3 → 0.1.4
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.
- package/README.md +72 -0
- package/api-report.md +7 -3
- package/dist/integration/index.d.ts +1 -1
- package/dist/integration/index.js.map +1 -1
- package/dist/integration/types.d.ts +1 -1
- package/dist/integration/types.js.map +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/invoke.d.ts +25 -1
- package/dist/testing/invoke.js +19 -0
- package/dist/testing/invoke.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -911,6 +911,53 @@ host scheduler uses. Numeric values only, with `*`, lists (`1,15`), ranges
|
|
|
911
911
|
day-of-month 1-31, month 1-12, day-of-week 0-7 (0 and 7 both Sunday). **No
|
|
912
912
|
names** (`JAN`, `MON`), no `@daily` macros, no seconds field.
|
|
913
913
|
|
|
914
|
+
### `onActivate` (activation)
|
|
915
|
+
|
|
916
|
+
The gap this closes: the sanctioned widget-data path is schedule → writes
|
|
917
|
+
`clientReadable` storage → widget reads it, and nothing server-side runs at
|
|
918
|
+
connect time — so a cache-backed dashboard is EMPTY until the first schedule
|
|
919
|
+
tick (up to a full interval), and stays STALE after an activation-data change
|
|
920
|
+
until the next tick. `onActivate` is the fix: a hook that runs server-side (a)
|
|
921
|
+
once after an activation is first persisted, and (b) again after
|
|
922
|
+
activationData is updated.
|
|
923
|
+
|
|
924
|
+
```ts
|
|
925
|
+
import type { OnActivateHandler } from '@ekanos/sdk/integration';
|
|
926
|
+
|
|
927
|
+
export const seedOnConnect: OnActivateHandler<MyStorage> = async (ctx) => {
|
|
928
|
+
const apiKey = await ctx.secrets.get(ACME_API_KEY);
|
|
929
|
+
if (!apiKey) return; // Nothing to seed yet — not an error.
|
|
930
|
+
|
|
931
|
+
const response = await ctx.fetch('https://api.acme.example/v1/summary', {
|
|
932
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
933
|
+
});
|
|
934
|
+
if (!response.ok) {
|
|
935
|
+
throw new Error(`Acme returned ${response.status} while seeding the cache.`);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
await ctx.storage.account.set('cache/summary', await response.json());
|
|
939
|
+
};
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
Same `ctx`, same enforcement (egress, storage, secrets) as `schedules[].handler`.
|
|
943
|
+
|
|
944
|
+
**v1 errors are NON-FATAL.** A throw is logged and shown to the user as a
|
|
945
|
+
warning; the activation stays connected. This hook is for **cache seeding and
|
|
946
|
+
eager validation**, not a connect gate — it cannot reject a connection. A
|
|
947
|
+
fail-the-connect credential validator is a deliberately separate, not-yet-built
|
|
948
|
+
field; do not repurpose `onActivate` as one.
|
|
949
|
+
|
|
950
|
+
**Pair it with a fingerprint, or the cache still goes stale.** A cache
|
|
951
|
+
invalidated only by age keeps serving the *previous* activation's values after
|
|
952
|
+
(b) fires, until whatever `onActivate` writes lands. Store a fingerprint of the
|
|
953
|
+
activation fields a cached value depended on — every field that changes the
|
|
954
|
+
**values**, not just the ones that select which data to fetch (units, currency
|
|
955
|
+
and locale leave a cache key untouched while making every number wrong) —
|
|
956
|
+
alongside the value, and treat a mismatch (or an absent fingerprint) on read as
|
|
957
|
+
a cache miss. Declare a new fingerprint field on a storage schema as
|
|
958
|
+
`.optional()`: a required field throws `StorageValidationError` on every
|
|
959
|
+
pre-existing row, which for a `clientReadable` key reaches the widget.
|
|
960
|
+
|
|
914
961
|
### `oauth`
|
|
915
962
|
|
|
916
963
|
You declare the provider; **the transport owns the flow.** Authorize redirect,
|
|
@@ -1081,6 +1128,25 @@ Both throw if the id is not declared, and `invokeWebhook` throws if the payload
|
|
|
1081
1128
|
fails `payloadSchema` — the handler never runs. The local transport records the
|
|
1082
1129
|
signature skip as one `warn` line on `ctx.logs`.
|
|
1083
1130
|
|
|
1131
|
+
`invokeActivate()` does the same for `onActivate` — no payload or invocation to
|
|
1132
|
+
build, just the handler against the definition-derived context:
|
|
1133
|
+
|
|
1134
|
+
```ts
|
|
1135
|
+
import { invokeActivate } from '@ekanos/sdk/testing';
|
|
1136
|
+
|
|
1137
|
+
const { ctx } = await invokeActivate(integration, {
|
|
1138
|
+
contextOptions: { secrets: { account: { acme_api_key: 'test_key' } } },
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
expect(ctx.dumpStorage().account['cache/summary']).toBeDefined();
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
Throws if `onActivate` is not declared, or if the handler itself throws — this
|
|
1145
|
+
helper is a bare invoker, not the host. The v1 non-fatal handling
|
|
1146
|
+
(log-and-continue) is applied by whatever calls the hook in production or in
|
|
1147
|
+
the harness, so a test asserting that behavior should catch the rejection
|
|
1148
|
+
itself.
|
|
1149
|
+
|
|
1084
1150
|
## Entrypoints
|
|
1085
1151
|
|
|
1086
1152
|
| Import | Runs on | Contents |
|
|
@@ -1128,6 +1194,7 @@ icons render as nothing at all — that is expected, not a bug in your code.
|
|
|
1128
1194
|
| storage keys | `[a-z0-9_-]+` with at most one `/` | `settings/location` |
|
|
1129
1195
|
| `egress[]` | https origin, optional one leading `*.`, optional port | `https://*.acme.example` |
|
|
1130
1196
|
| OAuth endpoints | absolute https, no embedded credentials, origin in `egress` | — |
|
|
1197
|
+
| `onActivate` | must be a function `(ctx) => Promise<void>` | — |
|
|
1131
1198
|
| `examplePayload`, `outputExample` | plain JSON only | — |
|
|
1132
1199
|
|
|
1133
1200
|
Duplicate widget ids, tool names, webhook ids or schedule ids inside one
|
|
@@ -1146,6 +1213,11 @@ whether it is you or us costs an afternoon.
|
|
|
1146
1213
|
context whose default is the browser's `fetch`. The `@ekanos/harness` README
|
|
1147
1214
|
has the whole ~20-line pattern; the dev harness's live mode mounts it for you.
|
|
1148
1215
|
- **No local OAuth loop.** See [`oauth`](#oauth) above.
|
|
1216
|
+
- **`onActivate` is declared and testable, but the host does not call it yet.**
|
|
1217
|
+
It runs in the `@ekanos/harness` activation surface and under
|
|
1218
|
+
`invokeActivate()`, but production has no wired call site as of this
|
|
1219
|
+
writing — declare it, test it locally, and it starts running the moment host
|
|
1220
|
+
wiring lands with no change on your side.
|
|
1149
1221
|
- **`useActivateIntegration()` is inert without the host's provider.** It reads
|
|
1150
1222
|
server actions out of `IntegrationActivationProvider`. Nothing warns you at
|
|
1151
1223
|
build time.
|
package/api-report.md
CHANGED
|
@@ -9,7 +9,7 @@ This is the complete surface of every entrypoint. The authoring guide
|
|
|
9
9
|
for these symbols is README.md. Entrypoints marked WORKSPACE-ONLY are
|
|
10
10
|
not reachable from a published install — see the note in each section.
|
|
11
11
|
|
|
12
|
-
Total exported names: **
|
|
12
|
+
Total exported names: **150** across 7 entrypoints.
|
|
13
13
|
|
|
14
14
|
## `@ekanos/sdk` (32 exports)
|
|
15
15
|
|
|
@@ -153,11 +153,13 @@ Total exported names: **146** across 7 entrypoints.
|
|
|
153
153
|
| `requireContext` | function |
|
|
154
154
|
| `resolveStorageKeyDeclaration` | function |
|
|
155
155
|
|
|
156
|
-
## `@ekanos/sdk/testing` (
|
|
156
|
+
## `@ekanos/sdk/testing` (16 exports)
|
|
157
157
|
|
|
158
158
|
| Export | Kind |
|
|
159
159
|
| --- | --- |
|
|
160
|
+
| `ActivateInvocationOutcome` | interface |
|
|
160
161
|
| `DefinitionContextOptions` | type alias |
|
|
162
|
+
| `InvokeActivateOptions` | type alias |
|
|
161
163
|
| `InvokeScheduleOptions` | interface |
|
|
162
164
|
| `InvokeWebhookOptions` | interface |
|
|
163
165
|
| `MockContextOptions` | interface |
|
|
@@ -168,10 +170,11 @@ Total exported names: **146** across 7 entrypoints.
|
|
|
168
170
|
| `ScheduleInvocationOutcome` | interface |
|
|
169
171
|
| `WebhookInvocationOutcome` | interface |
|
|
170
172
|
| `createMockContext` | function |
|
|
173
|
+
| `invokeActivate` | function |
|
|
171
174
|
| `invokeSchedule` | function |
|
|
172
175
|
| `invokeWebhook` | function |
|
|
173
176
|
|
|
174
|
-
## `@ekanos/sdk/integration` (
|
|
177
|
+
## `@ekanos/sdk/integration` (26 exports)
|
|
175
178
|
|
|
176
179
|
| Export | Kind |
|
|
177
180
|
| --- | --- |
|
|
@@ -185,6 +188,7 @@ Total exported names: **146** across 7 entrypoints.
|
|
|
185
188
|
| `IntegrationProposals` | type alias |
|
|
186
189
|
| `OAuthProviderDeclaration` | interface |
|
|
187
190
|
| `OAuthTokens` | interface |
|
|
191
|
+
| `OnActivateHandler` | type alias |
|
|
188
192
|
| `PartnerOAuthDeclaration` | interface |
|
|
189
193
|
| `PartnerScheduleDeclaration` | interface |
|
|
190
194
|
| `PartnerToolModule` | interface |
|
|
@@ -17,4 +17,4 @@
|
|
|
17
17
|
export { defineIntegration } from './define-integration.js';
|
|
18
18
|
export { parseCronExpression } from './cron.js';
|
|
19
19
|
export { IntegrationDefinitionSchema, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
|
|
20
|
-
export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, ToolClassificationProposal, DefinitionCollisionInput, FirstPartyInventory, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookEvent, WebhookResult, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, OAuthProviderDeclaration, OAuthTokens, } from '@ekanos/integration-schema';
|
|
20
|
+
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';
|
|
@@ -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,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\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} 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,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\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"]}
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* one contract and no structural twin — F9). This file re-exports them under
|
|
5
5
|
* `@ekanos/sdk/integration`.
|
|
6
6
|
*/
|
|
7
|
-
export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, ToolClassificationProposal, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookEvent, WebhookResult, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, OAuthProviderDeclaration, OAuthTokens, } from '@ekanos/integration-schema';
|
|
7
|
+
export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, ToolClassificationProposal, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookEvent, WebhookResult, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, OAuthProviderDeclaration, OAuthTokens, OnActivateHandler, } from '@ekanos/integration-schema';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/integration/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The partner authoring types are owned by `@ekanos/integration-schema` (the\n * dependency-pure package `@kit/integrations-core` also imports, so there is\n * one contract and no structural twin — F9). This file re-exports them under\n * `@ekanos/sdk/integration`.\n */\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n ToolClassificationProposal,\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookEvent,\n WebhookResult,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n OAuthProviderDeclaration,\n OAuthTokens,\n} from '@ekanos/integration-schema';\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/integration/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The partner authoring types are owned by `@ekanos/integration-schema` (the\n * dependency-pure package `@kit/integrations-core` also imports, so there is\n * one contract and no structural twin — F9). This file re-exports them under\n * `@ekanos/sdk/integration`.\n */\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n ToolClassificationProposal,\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookEvent,\n WebhookResult,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n OAuthProviderDeclaration,\n OAuthTokens,\n OnActivateHandler,\n} from '@ekanos/integration-schema';\n"]}
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -12,5 +12,5 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export { createMockContext } from './mock-context.js';
|
|
14
14
|
export type { MockContextOptions, MockFetchHandler, MockIntegrationContext, RecordedFetchCall, RecordedLog, } from './mock-context.js';
|
|
15
|
-
export { invokeWebhook, invokeSchedule } from './invoke.js';
|
|
16
|
-
export type { DefinitionContextOptions, InvokeWebhookOptions, InvokeScheduleOptions, WebhookInvocationOutcome, ScheduleInvocationOutcome, } from './invoke.js';
|
|
15
|
+
export { invokeWebhook, invokeSchedule, invokeActivate } from './invoke.js';
|
|
16
|
+
export type { DefinitionContextOptions, InvokeWebhookOptions, InvokeScheduleOptions, InvokeActivateOptions, WebhookInvocationOutcome, ScheduleInvocationOutcome, ActivateInvocationOutcome, } from './invoke.js';
|
package/dist/testing/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export { createMockContext } from './mock-context.js';
|
|
|
14
14
|
// Event-surface invocation: run declared webhook/schedule handlers locally,
|
|
15
15
|
// with the same payload-validation and context-derivation semantics the real
|
|
16
16
|
// transports will have. Used by partner unit tests and the harness Triggers
|
|
17
|
-
// panel alike.
|
|
18
|
-
|
|
17
|
+
// panel alike. `invokeActivate` does the same for the `onActivate` lifecycle
|
|
18
|
+
// hook, used by partner unit tests and the harness's activation surface.
|
|
19
|
+
export { invokeWebhook, invokeSchedule, invokeActivate } from './invoke.js';
|
|
19
20
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AASnD,4EAA4E;AAC5E,6EAA6E;AAC7E,4EAA4E;AAC5E,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AASnD,4EAA4E;AAC5E,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC","sourcesContent":["/**\n * @ekanos/sdk/testing — the partner test harness surface.\n *\n * `createMockContext()` is the day-1 in-memory `IntegrationContext`\n * (proposal §4): partners code and test against `ctx` here before anything\n * lands in a sandbox. Self-contained — no Supabase, no Vault, no network —\n * and enforcement (storage schemas, secret tiers, egress) is the same\n * shared implementation from `@ekanos/sdk/context` that production uses.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { createMockContext } from './mock-context';\nexport type {\n MockContextOptions,\n MockFetchHandler,\n MockIntegrationContext,\n RecordedFetchCall,\n RecordedLog,\n} from './mock-context';\n\n// Event-surface invocation: run declared webhook/schedule handlers locally,\n// with the same payload-validation and context-derivation semantics the real\n// transports will have. Used by partner unit tests and the harness Triggers\n// panel alike. `invokeActivate` does the same for the `onActivate` lifecycle\n// hook, used by partner unit tests and the harness's activation surface.\nexport { invokeWebhook, invokeSchedule, invokeActivate } from './invoke';\nexport type {\n DefinitionContextOptions,\n InvokeWebhookOptions,\n InvokeScheduleOptions,\n InvokeActivateOptions,\n WebhookInvocationOutcome,\n ScheduleInvocationOutcome,\n ActivateInvocationOutcome,\n} from './invoke';\n"]}
|
package/dist/testing/invoke.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* transport would, against a `createMockContext()` built FROM the definition
|
|
5
5
|
* (its slug, storage schemas, and egress list), so a handler unit test
|
|
6
6
|
* exercises the same schemas and the same egress allowlist production will.
|
|
7
|
+
* `invokeActivate()` does the same for the `onActivate` lifecycle hook.
|
|
7
8
|
*
|
|
8
9
|
* Transport semantics reproduced here, so they cannot drift from the docs:
|
|
9
10
|
* - the payload is parsed against `payloadSchema` BEFORE the handler runs;
|
|
@@ -12,7 +13,14 @@
|
|
|
12
13
|
* logs it as skipped (one warn line on `ctx.logs`), it never verifies and
|
|
13
14
|
* the handler never does either;
|
|
14
15
|
* - a schedule invocation carries `{scheduledFor, invokedAt, trigger}` with
|
|
15
|
-
* `trigger: 'manual'` by default (a human pressed the button)
|
|
16
|
+
* `trigger: 'manual'` by default (a human pressed the button);
|
|
17
|
+
* - `invokeActivate()` simply awaits `onActivate(ctx)` and returns the
|
|
18
|
+
* context it ran against — a throw PROPAGATES here. The non-fatal
|
|
19
|
+
* (log-and-continue) handling described on `OnActivateHandler` is HOST
|
|
20
|
+
* POLICY, applied by the transport that calls the hook in production
|
|
21
|
+
* (and by the harness's activation surface locally); this helper is a
|
|
22
|
+
* bare invoker; a test asserting the non-fatal behavior should catch its
|
|
23
|
+
* own rejection.
|
|
16
24
|
*/
|
|
17
25
|
import type { IntegrationDefinition, ScheduleInvocation, ScheduleResult, StorageSchemas, WebhookEvent, WebhookResult } from '@ekanos/integration-schema';
|
|
18
26
|
import { type MockContextOptions, type MockIntegrationContext } from './mock-context.js';
|
|
@@ -48,6 +56,11 @@ export interface InvokeScheduleOptions<Schemas extends StorageSchemas = StorageS
|
|
|
48
56
|
/** The tick this invocation stands for. Defaults to now. */
|
|
49
57
|
scheduledFor?: string;
|
|
50
58
|
}
|
|
59
|
+
export type InvokeActivateOptions<Schemas extends StorageSchemas = StorageSchemas> = BaseInvokeOptions<Schemas>;
|
|
60
|
+
export interface ActivateInvocationOutcome<Schemas extends StorageSchemas = StorageSchemas> {
|
|
61
|
+
/** The context the handler ran against — assert on its recordings. */
|
|
62
|
+
ctx: MockIntegrationContext<Schemas>;
|
|
63
|
+
}
|
|
51
64
|
export interface WebhookInvocationOutcome<Schemas extends StorageSchemas = StorageSchemas> {
|
|
52
65
|
result: WebhookResult;
|
|
53
66
|
/** The event the handler received (payload already schema-parsed). */
|
|
@@ -74,4 +87,15 @@ export declare function invokeWebhook<Schemas extends StorageSchemas = StorageSc
|
|
|
74
87
|
* context it ran against.
|
|
75
88
|
*/
|
|
76
89
|
export declare function invokeSchedule<Schemas extends StorageSchemas = StorageSchemas>(definition: IntegrationDefinition<Schemas>, scheduleId: string, options?: InvokeScheduleOptions<Schemas>): Promise<ScheduleInvocationOutcome<Schemas>>;
|
|
90
|
+
/**
|
|
91
|
+
* Runs the definition's declared `onActivate` hook, the way the host would
|
|
92
|
+
* after an activation persists or activationData updates. Throws if the
|
|
93
|
+
* definition declares no `onActivate` — there is nothing to invoke, and a
|
|
94
|
+
* silent no-op would let a test believe it exercised a hook that does not
|
|
95
|
+
* exist. A throwing handler PROPAGATES from this helper: the non-fatal
|
|
96
|
+
* (log-a-warning, keep the activation) handling is host policy applied by
|
|
97
|
+
* whatever calls this in production/the harness, not by this bare invoker
|
|
98
|
+
* (see the module doc comment).
|
|
99
|
+
*/
|
|
100
|
+
export declare function invokeActivate<Schemas extends StorageSchemas = StorageSchemas>(definition: IntegrationDefinition<Schemas>, options?: InvokeActivateOptions<Schemas>): Promise<ActivateInvocationOutcome<Schemas>>;
|
|
77
101
|
export {};
|
package/dist/testing/invoke.js
CHANGED
|
@@ -80,4 +80,23 @@ export async function invokeSchedule(definition, scheduleId, options = {}) {
|
|
|
80
80
|
const result = await schedule.handler(ctx, invocation);
|
|
81
81
|
return { result, invocation, ctx };
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Runs the definition's declared `onActivate` hook, the way the host would
|
|
85
|
+
* after an activation persists or activationData updates. Throws if the
|
|
86
|
+
* definition declares no `onActivate` — there is nothing to invoke, and a
|
|
87
|
+
* silent no-op would let a test believe it exercised a hook that does not
|
|
88
|
+
* exist. A throwing handler PROPAGATES from this helper: the non-fatal
|
|
89
|
+
* (log-a-warning, keep the activation) handling is host policy applied by
|
|
90
|
+
* whatever calls this in production/the harness, not by this bare invoker
|
|
91
|
+
* (see the module doc comment).
|
|
92
|
+
*/
|
|
93
|
+
export async function invokeActivate(definition, options = {}) {
|
|
94
|
+
if (!definition.onActivate) {
|
|
95
|
+
throw new Error(`Integration "${definition.slug}" declares no onActivate hook — add ` +
|
|
96
|
+
`one to defineIntegration() before invoking it.`);
|
|
97
|
+
}
|
|
98
|
+
const ctx = contextFor(definition, options);
|
|
99
|
+
await definition.onActivate(ctx);
|
|
100
|
+
return { ctx };
|
|
101
|
+
}
|
|
83
102
|
//# sourceMappingURL=invoke.js.map
|
|
@@ -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 *\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 */\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 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"]}
|
|
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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekanos/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The official SDK for building Ekanos integrations.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ekanos/integration-schema": "0.1.
|
|
53
|
-
"@ekanos/ui": "0.1.
|
|
52
|
+
"@ekanos/integration-schema": "0.1.4",
|
|
53
|
+
"@ekanos/ui": "0.1.4",
|
|
54
54
|
"@supabase/supabase-js": "2.87.1",
|
|
55
55
|
"server-only": "^0.0.1"
|
|
56
56
|
},
|