Releases: hopdrive/eventkit
Release list
v0.6.0
Minor Changes
-
7b09345: Ship the observability console as a mountable component from the package:
hopdrive-eventkit/console(plushopdrive-eventkit/console/style.css).Instead of deploying the UI straight out of this repo (which made no sense once eventkit went open source), a consumer now stands it up with a tiny host wrapper that owns config + hosting and imports the console from the package. Scaffold one with
npx degit hopdrive/eventkit/console/template my-console.- New export
EventKitConsoletakes aconfigprop (graphqlEndpoint,headers,auth,basename,grafanaProxyPath). Nothing readsimport.meta.envanymore, so one built artifact runs against any endpoint. - Auth is injected by the wrapper. Pass
auth: { getHeaders, onUnauthenticated? }and the console builds its Apollo client from it, resolving auth per request (a rotating JWT stays fresh without remounting). The wrapper owns login; the console owns the transport wiring. - The console library externalizes
reactand its React-coupled UI libs (react-router-dom, @apollo/client, reactflow, recharts, framer-motion, @heroicons, @microlink/react-json-view, @tanstack/*, antd); the consumer's app build bundles them. Onlyreact+react-domare declared peers (optional), so a server/library consumer of the core installs neither. The wrapper template lists the React-coupled libs as real dependencies. - The Grafana Loki proxy is now a host-agnostic function (
grafanaProxyCore.ts) with a thin Netlify adapter, so any host can serve it.
Also folds in two console fixes carried on this branch: the
event_executionsstatus CHECK now allowsdetection_failed/handler_failed(legacy writer safety during migration), and the vite 8 / RolldownmanualChunksbuild fix. - New export
v0.5.0
Minor Changes
-
76025f4: Simplification + declarative-response pass: internals consolidated, the authoring surface extended (source-scoped
defineEvent), and the HTTP reply repositioned to the invocation layer as a self-describing declaration (breaking, pre-adoption).- Removed dead export
NotImplementedError(root and/core). It was a Phase-0 scaffolding relic: no runtime code path ever threw it, so nothing could ever catch it. Migration: delete any import of it — there is no replacement because there was no behavior. - The three Hasura sources (
hasuraEvent,hasuraCron,hasuraAction) now share onecallableSourceassembly helper instead of three copy-pasted factory blocks; their runtime shape (callable + attached authoring helpers + pluginname) is pinned by new tests. handle()anddryRun()share one intake pipeline (extract → normalize → augment), and the response seams share one error-mapping path — the invocation lifecycle now reads in one place.- Consolidated six copies of the
crypto.randomUUIDfallback and two copies ofisJobDefinitioninto single internal helpers; the two Netlify v2 platforms share the Web-Responserejection formatter. webhookfactory now carries the typed authoring helpers (additive):webhook.detector<TBody>(fn)/webhook.prepare<TBody>(fn)work on the bare factory value (uniform with the Hasura family), so an event module can typectx.bodywithout constructing the vendor-configured source that lives in the entry file. NewWebhookAuthoringinterface; runtime behavior unchanged (identity wrappers).- Source-scoped
defineEvent(additive): every source now carries a typed module builder —hasuraEvent.defineEvent<Row>({ … }),webhook.defineEvent<Body>({ … }),hasuraAction.defineEvent<Input>({ … }),hasuraCron.defineEvent<Payload>({ … }). The one type parameter on the outer call types every inline(ctx) => …seam (detector,prepare) with the source's enriched context, so detectors can be authored inline with no per-seam wrapper. At runtime it is coredefineEvent(name branding only). New core typeSourceEventModule(type-only). The generic rootdefineEventis unchanged and remains the full-inference path (ctx.preparedfromprepare, D32). - BREAKING (pre-adoption): the HTTP reply moved to the invocation layer —
kit.handler({ before, after })(ADR-026, re-amended). A module never declares an HTTP reply: modules own detection + jobs; one invocation has ONE wire reply and the handler declares it.aftertakes one of two self-naming modes:{ body }(a constant — data, not code, so it provably cannot wait on or be changed by the work) or{ fromResults: (result) => body }(arbitrary business logic over the PRESCRIBED typed rollup the runtime builds —InvocationResultwith every detector'sEventOutcomeand every job'sJobExecution; throwClientError/ActionErrorfor the error mapping). Optionalstatus/headers(ResponseWire— the web-standardResponseInitfields as data) ride beside the mode; platform adapters map everything (new Response(body, init)on v2,{ statusCode, headers, body }on classic/Lambda).afteris skipped on a framework error (the 500 retry contract) and never clobbers a pre-dispatch client rejection (webhookrejectUnverified).{ fromResults }is rejected under a background/202 platform — "202 first, then work" is a PLATFORM choice (netlifyBackgroundPlatform), not anaftermode. The earlier per-moduleresolve/respond/responsefields are all removed (registering them throws with the migration pointer); an action/webhook's work runs as JOBS and the reply is composed from their outputs.KitEventDescription.response/FlowResponseKindare removed from describe()/flow output (events no longer have response kinds); theexpectFlow(...).respondsWith(...)testing assertion is removed with them. New core typesHandlerResponse/ResponseBody/ResponseWire. registerEvent/registerEventsnow acceptEventModule<any, any, any>(type-level widening): a source-typed module registers without a variance cast under strict compiler flags (e.g.exactOptionalPropertyTypes). Runtime unchanged.docs/guide.html: documentedkit.dryRun()and the Hasura sources' factory/config form (inbound token discovery) in the API reference; replaced the webhook walkthrough with a real Stripepayment_intent.succeededexample (now authored withwebhook.defineEvent<StripePaymentEvent>and the reply declared atkit.handler({ after: { body } })) and thehmacVerifypreset (the previous example called.detectoron the bare factory — which didn't exist before this change — and showed retry semantics that don't match Stripe's redeliver-on-any-non-2xx contract).
- Removed dead export
v0.4.1
Patch Changes
-
4d3efd3: Fix silent terminal-state loss on the observability final flush — a
job_executions
row could stayrunningwithduration_ms: nullafter its invocation already read
completed. The final flush deleted the per-invocation buffer BEFORE awaiting the sink and
swallowed any error, so a partial batch (the invocations mutation lands, then the
events/jobs mutation throws a deterministic GraphQL-level error, which is never retried)
permanently and silently lost the job's terminal state.Three defenses:
flush()no longer discards state before it's durable — the buffer is deleted only
AFTER the sink resolves. A failed final flush now RETAINS the buffer, soonFlush(which
runs in the samehandle()finally, right afteronInvocationEnd) retries the terminal
write instead of dropping it.- Sink failures are surfaced, not swallowed — new
onSinkError?(err, { invocationId, final })option onobservability(...), defaulting to a[eventkit]console.warn.
Ignored understrict(the error is rethrown as before). Silent telemetry loss was the
worst failure mode for a telemetry system. graphqlSinkgraceful-degrades an events/jobs GraphQL error — a deterministic
GraphqlResponseErroron thejob_executions/event_executionsmutation retries ONCE
with the droppable payload columns shed (result,job_options, error stacks), mirroring
the existingsource_job_iddegrade, so the row lands lossy-but-terminal (status
preserved) rather than being lost entirely.
Additive and backward-compatible:
onSinkErrordefaults to the console warning, and the
sink degrade only triggers on a mutation that would otherwise have thrown.
v0.4.0
Minor Changes
-
7cb7ed3: Add a kit-level
prepare→ctx.provided— a first-class, once-per-invocation context
provider, so sharing a live ref (a GraphQL executor, an authenticated vendor client, a
resolved tenant config) across an invocation no longer requires hand-rolling an
augmentJobContextplugin.Declare it as a reserved key on
createEventKit's config:const kit = createEventKit(hasuraEvent, { prepare: () => ({ executor: createFetchExecutor({ url, adminSecret }) }), }) .use(netlifyV2Platform) .registerEvents(events);
It runs once per invocation (after
normalize, before detection) and its returned
object is attached asctx.provided— the SAME instance — on every detector, module
prepare, and job of that invocation.- Request-scoped, never serialized. Unlike
envelope.meta(persisted to observability),
providedis safe for live objects. Defaults to{}when unset. - Distinct from the existing scopes: kit
prepare→ctx.provided(per invocation,
whole stack); moduleprepare→ctx.input/ctx.prepared(per event); per-job
input(per job, merges highest). - Also runs under
dryRunso detectors that readctx.providedbehave identically — keep it
cheap. A throw aborts the invocation cleanly, reported as the newErrorPhasevalue
'prepare'; no jobs run.
New public types:
KitPrepareContext,KitPrepareFunction.DetectorContext,
HandlerContext,InvocationContext, andJobContextgain aprovided: Record<string, unknown>field. Additive and backward-compatible (providedis{}when no kitprepare
is configured). - Request-scoped, never serialized. Unlike
v0.3.0
Minor Changes
-
a1763eb: Netlify Functions 2.0 support + observability lineage fix (surfaced by the hoprides v2 migration).
Add
netlifyV2BackgroundPlatform— the modern Netlify v2(Request, Context) → Response
shape for a function declaredexport const config = { background: true }(no-background
filename suffix). It reusesnetlifyV2Platform's Web-Request plumbing but runs as a
deferred-response platform:deferredResponse: true(blocks result-drivenrespondmodules),
a ~15-minute default budget, and a202Response. Import fromhopdrive-eventkit/platforms.Fix orphaned observability origins — the plugin buffered a job's
job_executionrow in the
parent invocation's memory and only flushed it at invocation end. A long-running job's DB
write-backs spawned child invocations (in separate processes) that hit the sink before the
parent flushed, so theirsource_job_idFK failed and the sink's graceful-degrade silently
dropped the link — rendering them as false extra "origins" in the console flow graph. The job's
row is now persisted (statusrunning) duringonJobStart, before the job body runs (the
runtime awaitsonJobStart), so the parent row is durable before any side effect can spawn a
child. Idempotent (sink upserts); gated by a newpersistJobsAtStartoption (default true).Preserve
rawBodyon the v2 platforms (surfaced by the sparkserv migration) — the webhook
source's HMACverifyneeds the exact request bytes, butnetlifyV2Platform/
netlifyV2BackgroundPlatformconsumed the body viareq.json()and never exposedrawBody, so
signature verification always failed on v2. The adapters now read the WebRequestbody once as
text, cache the exact bytes (WeakMap, no mutation of the Request), return the parsed JSON as the
payload, and expose the bytes asrequest.meta.rawBody. Backward-compatible (rawBody was simply
absent before); JSON parsing tolerates a non-JSON body (returns the raw string).Drop the
uuiddependency (surfaced by the event-handlers migration) — the runtime used
it only for v4 generation, and uuid v14 is ESM-only: the CJS build carried a nested copy that
require()refuses at runtime (ERR_REQUIRE_ESM under netlify dev) and Jest consumers cannot
parse without a moduleNameMapper.crypto.randomUUID()(built into Node ≥20, browsers, and
workers) replaces it; eventkit now has zero runtime dependencies.
v0.2.1
Patch Changes
-
c5d3657: Docs & tooling housekeeping after the npm rename to
hopdrive-eventkit:- Correct all README and
docs/guide.htmlimport/install examples to the published
name (hopdrive-eventkit, e.g.import { … } from 'hopdrive-eventkit/plugins') so
copy-paste works for consumers. - Derive the
docs-compiledoc-drift gate's scan regex frompackage.json"name"
instead of a hardcoded scope, so a future rename can't silently turn the gate into a
zero-match no-op. The gate now validates 50 documented import names again. - Console app: bump
jsondiffpatch0.6.2 → 0.7.2 (Dependabot; dev-only, not part of the
published package).
- Correct all README and