@voltro/plugin-webhooks 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,51 @@
1
+ import { TableLike } from '@voltro/database';
2
+
3
+ /**
4
+ * One row per delivery attempt — NOT per emit. An emit fans out to
5
+ * N targets; each target then runs ≤ `maxAttempts` deliveries. The
6
+ * primary key is `(deliveryId, attempt)`; `deliveryId` is shared
7
+ * across retries of the SAME (event, payload, target) tuple so the
8
+ * dashboard groups them.
9
+ *
10
+ * Status lifecycle: `pending` (queued — the target was paused at emit
11
+ * time, or the attempt is rate-deferred to the next window) →
12
+ * `inFlight` → `succeeded` | `failed` | `retryScheduled`. On retry the
13
+ * workflow creates a new `(deliveryId, attempt+1)` row; on
14
+ * resume/deferral the SAME attempt-1 row transitions out of `pending`.
15
+ */
16
+ export declare const _voltroWebhookDeliveriesTable: TableLike;
17
+
18
+ /**
19
+ * Fixed-window rate-limit counters — one row per (scope, minute
20
+ * bucket), where scope is `target:<targetId>` (per-target
21
+ * `rateLimitPerMinute`) or `event:<eventId>` (an outgoing event's
22
+ * `globalRateLimit`). The delivery workflow claims a slot via a CAS
23
+ * loop over `count` (see `rateLimit.ts`) BEFORE every wire POST, so
24
+ * the cap holds across replicas — the counter lives here, never in
25
+ * process memory.
26
+ *
27
+ * Deliberately NOT tenant-scoped: rows carry only a scope key + an
28
+ * integer count (no payload, no secret, no tenant data), and the
29
+ * background delivery workflow that writes them has no request
30
+ * subject. The deterministic PK `<scope>@<bucket>` is the
31
+ * `insertIgnore` conflict target for the first-in-window create race.
32
+ */
33
+ export declare const _voltroWebhookRateWindowsTable: TableLike;
34
+
35
+ /**
36
+ * One row per subscribed delivery target. Created via
37
+ * `webhooks.subscribe(...)`. Read-only from app code; mutate via
38
+ * the `webhooks` service so the framework can run validation +
39
+ * generate secrets + invalidate caches.
40
+ */
41
+ export declare const _voltroWebhookTargetsTable: TableLike;
42
+
43
+ /**
44
+ * Return the framework-managed tables the webhooks plugin adds.
45
+ * Consumed by the plugin entry's `registerSchema` hook so the
46
+ * bootstrap migration creates the tables BEFORE any user
47
+ * migration runs.
48
+ */
49
+ export declare const webhookTables: () => ReadonlyArray<TableLike>;
50
+
51
+ export { }
package/dist/mixin.js ADDED
@@ -0,0 +1,68 @@
1
+ import { boolean as e, id as t, integer as n, json as r, registerRetention as i, retentionTtlMsFromEnv as a, table as o, text as s, timestamp as c } from "@voltro/database";
2
+ import { tenant as l } from "@voltro/plugin-multitenancy/mixin";
3
+ //#region src/mixin.ts
4
+ var u = o("_voltro_webhook_targets", {
5
+ id: t({ prefix: "whtarget" }),
6
+ event: s(),
7
+ url: s(),
8
+ secret: s(),
9
+ signing: r(),
10
+ retry: r(),
11
+ filter: r().nullable(),
12
+ headers: r().nullable(),
13
+ rateLimitPerMinute: n().nullable(),
14
+ active: e().default(!0),
15
+ format: s().oneOf([
16
+ "json",
17
+ "form",
18
+ "xml"
19
+ ]).default("json"),
20
+ autoDisableAfter: n().nullable(),
21
+ consecutiveFailures: n().default(0),
22
+ autoDisabledAt: c().nullable(),
23
+ autoDisableReason: s().nullable(),
24
+ payloadVersion: n().default(1),
25
+ description: s().nullable()
26
+ }).with(l()), d = o("_voltro_webhook_deliveries", {
27
+ id: t({ prefix: "whdeliv" }),
28
+ deliveryId: s(),
29
+ targetId: s(),
30
+ event: s(),
31
+ eventId: s().nullable(),
32
+ attempt: n(),
33
+ status: s().oneOf([
34
+ "pending",
35
+ "inFlight",
36
+ "succeeded",
37
+ "failed",
38
+ "retryScheduled"
39
+ ]),
40
+ payload: r(),
41
+ responseStatus: n().nullable(),
42
+ responseBody: s().nullable(),
43
+ errorMessage: s().nullable(),
44
+ latencyMs: n().nullable(),
45
+ scheduledAt: c(),
46
+ nextAttemptAt: c().nullable()
47
+ }).with(l()), f = o("_voltro_webhook_rate_windows", {
48
+ id: t({ prefix: "whrate" }),
49
+ scope: s().maxLength(191),
50
+ bucket: n(),
51
+ count: n().default(0),
52
+ createdAt: c(),
53
+ updatedAt: c()
54
+ }), p = () => (i({
55
+ table: "_voltro_webhook_deliveries",
56
+ timeColumn: "createdAt",
57
+ ttlMs: a(process.env.VOLTRO_WEBHOOK_DELIVERIES_TTL_HOURS, 2160)
58
+ }), i({
59
+ table: "_voltro_webhook_rate_windows",
60
+ timeColumn: "createdAt",
61
+ ttlMs: 24 * 36e5
62
+ }), [
63
+ u,
64
+ d,
65
+ f
66
+ ]);
67
+ //#endregion
68
+ export { d as _voltroWebhookDeliveriesTable, f as _voltroWebhookRateWindowsTable, u as _voltroWebhookTargetsTable, p as webhookTables };
@@ -0,0 +1,166 @@
1
+ import { Schema } from 'effect';
2
+
3
+ declare interface CustomSignatureScheme {
4
+ readonly _tag: 'custom';
5
+ readonly header: string;
6
+ readonly sign: (rawBody: Uint8Array, secret: string) => string;
7
+ readonly verify: (rawBody: Uint8Array, secret: string, signatureHeader: string) => boolean;
8
+ }
9
+
10
+ export declare const genericProvider: (options?: {
11
+ readonly signatureHeader?: string;
12
+ readonly replayWindowSeconds?: number;
13
+ }) => WebhookProviderDescriptor;
14
+
15
+ export declare const githubProvider: () => WebhookProviderDescriptor;
16
+
17
+ declare type HmacAlgorithm = 'hmacSha256' | 'hmacSha1';
18
+
19
+ declare interface HmacSignatureScheme {
20
+ readonly _tag: 'hmac';
21
+ readonly algorithm: HmacAlgorithm;
22
+ /** Header the signature is written to / read from. Stripe uses
23
+ * `Stripe-Signature`, GitHub uses `X-Hub-Signature-256`, generic
24
+ * apps use `X-Webhook-Signature`. */
25
+ readonly header: string;
26
+ /** When `true`, the signed payload is `<timestamp>.<rawBody>` and
27
+ * the header carries both (`t=...,v1=...`). Recipients reject
28
+ * signatures whose `t` is older than `replayWindowSeconds`. */
29
+ readonly includeTimestamp: boolean;
30
+ /** Window during which a signed payload is replay-safe. Older
31
+ * signatures get rejected. Default 5 minutes. Only relevant when
32
+ * `includeTimestamp: true`. */
33
+ readonly replayWindowSeconds?: number;
34
+ /** Encoding the signature is rendered in. `hex` is most common;
35
+ * Slack uses `hex` prefixed with `v0=`. */
36
+ readonly encoding?: 'hex' | 'base64';
37
+ /** Prefix written before the hex/base64 signature in the header
38
+ * value. Slack: `v0=`. GitHub: `sha256=`. Default empty. */
39
+ readonly versionPrefix?: string;
40
+ /** Optional secret-rotation hint: a SECOND secret accepted during
41
+ * verification but never used for outgoing signing. Lets you
42
+ * rotate the primary secret without breaking in-flight inbound
43
+ * deliveries. */
44
+ readonly previousSecret?: string;
45
+ }
46
+
47
+ declare interface IncomingWebhookContext<Body> {
48
+ /** The fully-decoded body, validated against `payload`. */
49
+ readonly body: Body;
50
+ /** Raw bytes — needed to recompute signatures. Already used by
51
+ * the framework's middleware to verify the inbound signature
52
+ * before this handler runs; passed along in case the user wants
53
+ * to compute additional MAC's for downstream relays. */
54
+ readonly rawBody: Uint8Array;
55
+ /** Request headers (lowercased keys). The middleware has already
56
+ * consumed signature / idempotency headers. */
57
+ readonly headers: Readonly<Record<string, string>>;
58
+ /** Idempotency key extracted by the middleware (provider-specific
59
+ * extraction — Stripe uses `Stripe-Signature`'s `t=`, GitHub uses
60
+ * `X-GitHub-Delivery`, generic uses `Idempotency-Key`). */
61
+ readonly idempotencyKey: string;
62
+ /** Workflow facade supplied by the framework runtime. Use this for
63
+ * verified external incoming calls that should start or signal
64
+ * durable workflows after signature + idempotency checks pass. */
65
+ readonly workflows?: IncomingWorkflowFacade;
66
+ }
67
+
68
+ declare interface IncomingWebhookDescriptor<Body> {
69
+ readonly _tag: 'incomingWebhook';
70
+ readonly id: WebhookId;
71
+ /** URL path the framework mounts the route on. Defaults to
72
+ * `/webhooks/<id>` if absent. Use a custom path for legacy
73
+ * integrations (`/integrations/stripe/v1`). */
74
+ readonly path?: `/${string}`;
75
+ /** Signature scheme used to authenticate inbound requests. Reject
76
+ * on mismatch with a 401. `undefined` (the default) means NO
77
+ * verification — only acceptable when behind a separate trust
78
+ * boundary (gateway + IP allow-list). The dashboard surfaces a
79
+ * warning for unsigned incoming webhooks. */
80
+ readonly signature?: SignatureScheme;
81
+ /** Idempotency key extraction. Default `'Idempotency-Key'` header.
82
+ * Provider-templates override this to match the provider's wire
83
+ * format. */
84
+ readonly idempotency?: {
85
+ /** Header name OR a function that reads from headers/body. */
86
+ readonly from: string | ((headers: Readonly<Record<string, string>>, rawBody: Uint8Array) => string | undefined);
87
+ /** TTL the framework retains the key for de-dup. Default 7 days. */
88
+ readonly ttl?: '5m' | '1h' | '6h' | '1d' | '7d' | '30d';
89
+ };
90
+ /** Validated body schema. The framework decodes the request body
91
+ * AFTER signature verification, BEFORE the handler runs. */
92
+ readonly payload: Schema.Schema<Body>;
93
+ /** Body parser — JSON by default. Stripe / GitHub send `application/json`
94
+ * but other providers use `application/x-www-form-urlencoded`. */
95
+ readonly bodyType?: 'json' | 'form' | 'raw';
96
+ /** Provider preset — when set, the framework fills `signature` +
97
+ * `idempotency` + `bodyType` from the provider's known shape.
98
+ * Explicit fields above always win. */
99
+ readonly provider?: WebhookProviderDescriptor;
100
+ /** Typed handler. Returns void on success, throws to reject. The
101
+ * HTTP status is 2xx for success, 4xx for typed validation
102
+ * errors, 5xx for handler exceptions. The framework retries
103
+ * 5xx-classified failures via the provider's expected behavior
104
+ * (most providers retry their own POST on 5xx). */
105
+ readonly handler: (context: IncomingWebhookContext<Body>) => Promise<void> | void;
106
+ }
107
+
108
+ declare interface IncomingWorkflowFacade {
109
+ start<Payload = unknown>(workflowName: string, payload: Payload): Promise<{
110
+ readonly id: string;
111
+ readonly workflowName: string;
112
+ readonly executionId: string;
113
+ readonly status: 'running';
114
+ }>;
115
+ signal(target: {
116
+ readonly id?: string;
117
+ readonly executionId?: string;
118
+ readonly workflowName?: string;
119
+ }, signalName: string, payload?: unknown): Promise<{
120
+ readonly eventId: string;
121
+ }>;
122
+ update(target: {
123
+ readonly id?: string;
124
+ readonly executionId?: string;
125
+ readonly workflowName?: string;
126
+ }, updateName: string, payload?: unknown, options?: {
127
+ readonly timeoutMs?: number;
128
+ readonly pollIntervalMs?: number;
129
+ }): Promise<{
130
+ readonly eventId: string;
131
+ readonly updateId: string;
132
+ readonly completedEventId: string;
133
+ readonly result: unknown;
134
+ }>;
135
+ }
136
+
137
+ declare type SignatureScheme = HmacSignatureScheme | CustomSignatureScheme;
138
+
139
+ export declare const slackProvider: () => WebhookProviderDescriptor;
140
+
141
+ export declare const stripeProvider: (options?: {
142
+ /** Override the replay window. Stripe's default is 5 min;
143
+ * callers running tests sometimes want a wider window. */
144
+ readonly replayWindowSeconds?: number;
145
+ }) => WebhookProviderDescriptor;
146
+
147
+ /** Stable identifier for an event or webhook. Drives the database
148
+ * primary key, the inspect endpoint URL, the dashboard listing. Use
149
+ * dotted-camelCase (`order.completed`, `user.signedUp`). */
150
+ declare type WebhookId = string;
151
+
152
+ declare interface WebhookProviderDescriptor {
153
+ readonly _tag: 'webhookProvider';
154
+ readonly id: string;
155
+ /** Display name for the dashboard. */
156
+ readonly name: string;
157
+ readonly signature: SignatureScheme;
158
+ readonly idempotency: NonNullable<IncomingWebhookDescriptor<unknown>['idempotency']>;
159
+ readonly bodyType: 'json' | 'form' | 'raw';
160
+ /** Optional discriminator: provider-specific event-type extraction
161
+ * (e.g. Stripe's top-level `type` field). Used by typed handlers
162
+ * to narrow on payload variant. */
163
+ readonly eventTypeFrom?: (body: unknown) => string | undefined;
164
+ }
165
+
166
+ export { }
@@ -0,0 +1,88 @@
1
+ import { a as e, l as t, n, t as r } from "../signing-BOUxHdAo.js";
2
+ import { createHmac as i, timingSafeEqual as a } from "node:crypto";
3
+ //#region src/providers/stripe.ts
4
+ var o = (n) => t({
5
+ id: "stripe",
6
+ name: "Stripe",
7
+ signature: {
8
+ ...e(),
9
+ ...n?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: n.replayWindowSeconds }
10
+ },
11
+ idempotency: {
12
+ from: (e, t) => {
13
+ try {
14
+ return JSON.parse(new TextDecoder().decode(t)).id;
15
+ } catch {
16
+ return;
17
+ }
18
+ },
19
+ ttl: "30d"
20
+ },
21
+ bodyType: "json",
22
+ eventTypeFrom: (e) => {
23
+ if (typeof e == "object" && e && "type" in e) {
24
+ let t = e.type;
25
+ return typeof t == "string" ? t : void 0;
26
+ }
27
+ }
28
+ }), s = () => t({
29
+ id: "github",
30
+ name: "GitHub",
31
+ signature: n(),
32
+ idempotency: {
33
+ from: "x-github-delivery",
34
+ ttl: "7d"
35
+ },
36
+ bodyType: "json",
37
+ eventTypeFrom: (e) => {}
38
+ }), c = () => ({
39
+ _tag: "custom",
40
+ header: "X-Slack-Signature",
41
+ sign: (e, t) => {
42
+ let n = `v0:${Math.floor(Date.now() / 1e3)}:${new TextDecoder().decode(e)}`;
43
+ return `v0=${i("sha256", t).update(n).digest("hex")}`;
44
+ },
45
+ verify: (e, t, n) => {
46
+ let r = n.indexOf("|");
47
+ if (r === -1) return !1;
48
+ let o = n.slice(0, r), s = n.slice(r + 1);
49
+ if (!/^\d+$/.test(o)) return !1;
50
+ let c = Math.floor(Date.now() / 1e3);
51
+ if (Math.abs(c - Number(o)) > 300) return !1;
52
+ let l = `v0:${o}:${new TextDecoder().decode(e)}`, u = `v0=${i("sha256", t).update(l).digest("hex")}`, d = Buffer.from(u, "utf8"), f = Buffer.from(s, "utf8");
53
+ return d.length === f.length ? a(d, f) : (a(d, Buffer.alloc(d.length)), !1);
54
+ }
55
+ }), l = () => t({
56
+ id: "slack",
57
+ name: "Slack",
58
+ signature: c(),
59
+ idempotency: {
60
+ from: "x-slack-request-timestamp",
61
+ ttl: "5m"
62
+ },
63
+ bodyType: "json",
64
+ eventTypeFrom: (e) => {
65
+ if (typeof e == "object" && e && "event" in e) {
66
+ let t = e.event;
67
+ if (typeof t == "object" && t && "type" in t) {
68
+ let e = t.type;
69
+ return typeof e == "string" ? e : void 0;
70
+ }
71
+ }
72
+ }
73
+ }), u = (e) => t({
74
+ id: "generic",
75
+ name: "Generic",
76
+ signature: {
77
+ ...r(),
78
+ ...e?.signatureHeader === void 0 ? {} : { header: e.signatureHeader },
79
+ ...e?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: e.replayWindowSeconds }
80
+ },
81
+ idempotency: {
82
+ from: "idempotency-key",
83
+ ttl: "7d"
84
+ },
85
+ bodyType: "json"
86
+ });
87
+ //#endregion
88
+ export { u as genericProvider, s as githubProvider, l as slackProvider, o as stripeProvider };
@@ -0,0 +1,120 @@
1
+ import { createHmac as e, timingSafeEqual as t } from "node:crypto";
2
+ //#region src/dsl.ts
3
+ var n = (e) => ({
4
+ _tag: "outgoingEvent",
5
+ ...e
6
+ }), r = (e) => ({
7
+ _tag: "incomingWebhook",
8
+ ...e
9
+ }), i = (e) => ({
10
+ _tag: "webhookProvider",
11
+ ...e
12
+ }), a = (e) => {
13
+ if (typeof e != "object" || !e) return !1;
14
+ let t = e._tag;
15
+ return t === "outgoingEvent" || t === "incomingWebhook" || t === "webhookProvider";
16
+ }, o = (e) => e === "hmacSha256" ? "sha256" : "sha1", s = (t, n, r, i = "hex") => {
17
+ let a = e(o(t), n);
18
+ return a.update(r), a.digest(i);
19
+ }, c = (e, t, n) => {
20
+ if (e._tag === "custom") return {
21
+ headerName: e.header,
22
+ headerValue: e.sign(t, n)
23
+ };
24
+ let r = e.encoding ?? "hex", i = e.versionPrefix ?? "";
25
+ if (e.includeTimestamp) {
26
+ let a = Math.floor(Date.now() / 1e3), o = Buffer.concat([Buffer.from(`${a}.`, "utf8"), Buffer.from(t)]), c = s(e.algorithm, n, o, r);
27
+ return {
28
+ headerName: e.header,
29
+ headerValue: `t=${a},${i}${c}`,
30
+ timestamp: a
31
+ };
32
+ }
33
+ let a = s(e.algorithm, n, t, r);
34
+ return {
35
+ headerName: e.header,
36
+ headerValue: `${i}${a}`
37
+ };
38
+ }, l = (e, t, n, r) => {
39
+ if (!r) return {
40
+ ok: !1,
41
+ reason: "missing signature header"
42
+ };
43
+ if (e._tag === "custom") return e.verify(t, n, r) ? { ok: !0 } : {
44
+ ok: !1,
45
+ reason: "custom verifier rejected"
46
+ };
47
+ let i = e.encoding ?? "hex", a = e.versionPrefix ?? "", o = (n) => {
48
+ if (e.includeTimestamp) {
49
+ let o = r.split(",").map((e) => e.trim()), c = o.find((e) => e.startsWith("t="));
50
+ if (!c) return {
51
+ ok: !1,
52
+ reason: "no timestamp in signature header"
53
+ };
54
+ let l = Number(c.slice(2));
55
+ if (!Number.isFinite(l)) return {
56
+ ok: !1,
57
+ reason: "invalid timestamp"
58
+ };
59
+ let d = e.replayWindowSeconds ?? 300, f = Math.floor(Date.now() / 1e3);
60
+ if (Math.abs(f - l) > d) return {
61
+ ok: !1,
62
+ reason: `replay window exceeded (${Math.abs(f - l)}s > ${d}s)`
63
+ };
64
+ let p = a.length > 0 ? o.find((e) => e.startsWith(a))?.slice(a.length) : o.find((e) => !e.startsWith("t="));
65
+ if (!p) return {
66
+ ok: !1,
67
+ reason: "no signature in header"
68
+ };
69
+ let m = Buffer.concat([Buffer.from(`${l}.`, "utf8"), Buffer.from(t)]);
70
+ return u(s(e.algorithm, n, m, i), p) ? { ok: !0 } : {
71
+ ok: !1,
72
+ reason: "signature mismatch"
73
+ };
74
+ }
75
+ let o = a.length > 0 && r.startsWith(a) ? r.slice(a.length) : r;
76
+ return u(s(e.algorithm, n, t, i), o) ? { ok: !0 } : {
77
+ ok: !1,
78
+ reason: "signature mismatch"
79
+ };
80
+ }, c = o(n);
81
+ if (c.ok) return c;
82
+ if (e.previousSecret !== void 0) {
83
+ let t = o(e.previousSecret);
84
+ if (t.ok) return t;
85
+ }
86
+ return c;
87
+ }, u = (e, n) => {
88
+ let r = Buffer.from(e, "utf8"), i = Buffer.from(n, "utf8");
89
+ return r.length === i.length ? t(r, i) : (t(r, Buffer.alloc(r.length)), !1);
90
+ }, d = (e = "Stripe-Signature") => ({
91
+ _tag: "hmac",
92
+ algorithm: "hmacSha256",
93
+ header: e,
94
+ includeTimestamp: !0,
95
+ replayWindowSeconds: 300,
96
+ encoding: "hex",
97
+ versionPrefix: "v1="
98
+ }), f = () => ({
99
+ _tag: "hmac",
100
+ algorithm: "hmacSha256",
101
+ header: "X-Hub-Signature-256",
102
+ includeTimestamp: !1,
103
+ encoding: "hex",
104
+ versionPrefix: "sha256="
105
+ }), p = () => ({
106
+ _tag: "custom",
107
+ header: "X-Slack-Signature",
108
+ sign: (e, t) => `v0=${s("hmacSha256", t, `v0:${Math.floor(Date.now() / 1e3)}:${Buffer.from(e).toString("utf8")}`)}`,
109
+ verify: (e, t, n) => !1
110
+ }), m = () => ({
111
+ _tag: "hmac",
112
+ algorithm: "hmacSha256",
113
+ header: "X-Webhook-Signature",
114
+ includeTimestamp: !0,
115
+ replayWindowSeconds: 300,
116
+ encoding: "hex",
117
+ versionPrefix: "v1="
118
+ });
119
+ //#endregion
120
+ export { d as a, n as c, p as i, i as l, f as n, l as o, c as r, r as s, m as t, a as u };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@voltro/plugin-webhooks",
3
+ "version": "0.1.0",
4
+ "description": "Webhooks plugin — first-class outgoing (multi-target, durable-workflow delivery, HMAC signing, retry policies, rate limits) + incoming (signature verification, idempotency, provider templates for Stripe / GitHub / Slack / generic).",
5
+ "keywords": [
6
+ "voltro",
7
+ "typescript",
8
+ "framework"
9
+ ],
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "homepage": "https://voltro.dev",
12
+ "bugs": {
13
+ "email": "support@voltro.dev"
14
+ },
15
+ "author": {
16
+ "name": "Voltro UG",
17
+ "url": "https://voltro.dev"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./mixin": {
27
+ "types": "./dist/mixin.d.ts",
28
+ "import": "./dist/mixin.js",
29
+ "default": "./dist/mixin.js"
30
+ },
31
+ "./providers": {
32
+ "types": "./dist/providers/index.d.ts",
33
+ "import": "./dist/providers/index.js",
34
+ "default": "./dist/providers/index.js"
35
+ },
36
+ "./errors": {
37
+ "types": "./dist/errors.d.ts",
38
+ "import": "./dist/errors.js",
39
+ "default": "./dist/errors.js"
40
+ }
41
+ },
42
+ "main": "./dist/index.js",
43
+ "module": "./dist/index.js",
44
+ "types": "./dist/index.d.ts",
45
+ "sideEffects": false,
46
+ "engines": {
47
+ "node": ">=24.0.0"
48
+ },
49
+ "dependencies": {
50
+ "@effect/workflow": "^0.18.2",
51
+ "@voltro/database": "0.1.0",
52
+ "@voltro/integration-http": "0.1.0",
53
+ "@voltro/logger": "0.1.0",
54
+ "@voltro/plugin-multitenancy": "0.1.0",
55
+ "@voltro/protocol": "0.1.0",
56
+ "@voltro/runtime": "0.1.0"
57
+ },
58
+ "peerDependencies": {
59
+ "effect": "^3.21.4"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
+ }