@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 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: **146** across 7 entrypoints.
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` (13 exports)
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` (25 exports)
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"]}
@@ -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';
@@ -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
- export { invokeWebhook, invokeSchedule } from './invoke.js';
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,eAAe;AACf,OAAO,EAAE,aAAa,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.\nexport { invokeWebhook, invokeSchedule } from './invoke';\nexport type {\n DefinitionContextOptions,\n InvokeWebhookOptions,\n InvokeScheduleOptions,\n WebhookInvocationOutcome,\n ScheduleInvocationOutcome,\n} from './invoke';\n"]}
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"]}
@@ -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 {};
@@ -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",
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.3",
53
- "@ekanos/ui": "0.1.3",
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
  },