@open-mercato/webhooks 0.9.1-develop.7499.1.fcec456e4e → 0.9.1-develop.7515.1.e2712c8b00

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.
@@ -1,2 +1,2 @@
1
- [build:webhooks] found 76 entry points
1
+ [build:webhooks] found 77 entry points
2
2
  [build:webhooks] built successfully
@@ -6,6 +6,7 @@ import { webhookUpdateSchema } from "../../../data/validators.js";
6
6
  import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
7
7
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
8
8
  import { invalidateWebhookSubscriptionCacheFor } from "../../../lib/subscription-cache.js";
9
+ import { applyWebhookActiveState } from "../../../lib/activation.js";
9
10
  const metadata = {
10
11
  GET: { requireAuth: true, requireFeatures: ["webhooks.view"] },
11
12
  PUT: { requireAuth: true, requireFeatures: ["webhooks.manage"] },
@@ -85,7 +86,7 @@ async function PUT(request, context) {
85
86
  if (input.rateLimitPerMinute !== void 0) webhook.rateLimitPerMinute = input.rateLimitPerMinute;
86
87
  if (input.autoDisableThreshold !== void 0) webhook.autoDisableThreshold = input.autoDisableThreshold;
87
88
  if (input.integrationId !== void 0) webhook.integrationId = input.integrationId;
88
- if (input.isActive !== void 0) webhook.isActive = input.isActive;
89
+ if (input.isActive !== void 0) applyWebhookActiveState(webhook, input.isActive);
89
90
  await em.flush();
90
91
  await invalidateWebhookSubscriptionCacheFor(scope.container, webhook.tenantId);
91
92
  await emitWebhooksEvent("webhooks.webhook.updated", {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/webhooks/api/webhooks/%5Bid%5D/route.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { emitWebhooksEvent } from '../../../events'\nimport { findScopedWebhook, json, resolveWebhookRequestScope, serializeWebhookDetail } from '../../helpers'\nimport { webhookUpdateSchema } from '../../../data/validators'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { invalidateWebhookSubscriptionCacheFor } from '../../../lib/subscription-cache'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['webhooks.view'] },\n PUT: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n}\n\ninterface RouteContext {\n params: Promise<{ id: string }>\n}\n\nconst webhookDetailResponseSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n url: z.string(),\n subscribedEvents: z.array(z.string()),\n httpMethod: z.string(),\n isActive: z.boolean(),\n deliveryStrategy: z.string(),\n maxRetries: z.number(),\n consecutiveFailures: z.number(),\n lastSuccessAt: z.string().nullable(),\n lastFailureAt: z.string().nullable(),\n createdAt: z.string(),\n updatedAt: z.string(),\n customHeaders: z.record(z.string(), z.string()).nullable(),\n strategyConfig: z.record(z.string(), z.unknown()).nullable(),\n timeoutMs: z.number(),\n rateLimitPerMinute: z.number(),\n autoDisableThreshold: z.number(),\n integrationId: z.string().nullable(),\n maskedSecret: z.string(),\n previousSecretSetAt: z.string().nullable(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\nconst deleteResponseSchema = z.object({ success: z.literal(true) })\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n const scope = await resolveWebhookRequestScope(request)\n if (scope instanceof Response) return scope\n\n const params = await context.params\n const webhook = await findScopedWebhook(scope.em.fork(), scope, params.id)\n\n if (!webhook) {\n return json({ error: 'Webhook not found' }, { status: 404 })\n }\n\n return json(serializeWebhookDetail(webhook))\n}\n\nexport async function PUT(request: Request, context: RouteContext): Promise<Response> {\n const scope = await resolveWebhookRequestScope(request)\n if (scope instanceof Response) return scope\n\n const params = await context.params\n const em = scope.em.fork()\n const webhook = await findScopedWebhook(em, scope, params.id)\n\n if (!webhook) {\n return json({ error: 'Webhook not found' }, { status: 404 })\n }\n\n try {\n await enforceCommandOptimisticLockWithGuards(scope.container, {\n resourceKind: 'webhooks.endpoint',\n resourceId: webhook.id,\n current: webhook.updatedAt ?? null,\n request,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return json(err.body, { status: err.status })\n throw err\n }\n\n const parsed = webhookUpdateSchema.safeParse(await request.json().catch(() => null))\n if (!parsed.success) {\n return json({ error: 'Invalid request payload' }, { status: 400 })\n }\n\n const input = parsed.data\n if (input.name !== undefined) webhook.name = input.name\n if (input.description !== undefined) webhook.description = input.description\n if (input.url !== undefined) webhook.url = input.url\n if (input.subscribedEvents !== undefined) webhook.subscribedEvents = input.subscribedEvents\n if (input.httpMethod !== undefined) webhook.httpMethod = input.httpMethod\n if (input.customHeaders !== undefined) webhook.customHeaders = input.customHeaders\n if (input.deliveryStrategy !== undefined) webhook.deliveryStrategy = input.deliveryStrategy\n if (input.strategyConfig !== undefined) webhook.strategyConfig = input.strategyConfig\n if (input.maxRetries !== undefined) webhook.maxRetries = input.maxRetries\n if (input.timeoutMs !== undefined) webhook.timeoutMs = input.timeoutMs\n if (input.rateLimitPerMinute !== undefined) webhook.rateLimitPerMinute = input.rateLimitPerMinute\n if (input.autoDisableThreshold !== undefined) webhook.autoDisableThreshold = input.autoDisableThreshold\n if (input.integrationId !== undefined) webhook.integrationId = input.integrationId\n if (input.isActive !== undefined) webhook.isActive = input.isActive\n\n await em.flush()\n\n await invalidateWebhookSubscriptionCacheFor(scope.container, webhook.tenantId)\n\n await emitWebhooksEvent('webhooks.webhook.updated', {\n webhookId: webhook.id,\n organizationId: webhook.organizationId,\n tenantId: webhook.tenantId,\n }, { persistent: true })\n\n return json(serializeWebhookDetail(webhook))\n}\n\nexport async function DELETE(request: Request, context: RouteContext): Promise<Response> {\n const scope = await resolveWebhookRequestScope(request)\n if (scope instanceof Response) return scope\n\n const params = await context.params\n const em = scope.em.fork()\n const webhook = await findScopedWebhook(em, scope, params.id)\n const { translate } = await resolveTranslations()\n\n if (!webhook) {\n return json({ error: translate('webhooks.errors.notFound', 'Webhook not found') }, { status: 404 })\n }\n\n try {\n await enforceCommandOptimisticLockWithGuards(scope.container, {\n resourceKind: 'webhooks.endpoint',\n resourceId: webhook.id,\n current: webhook.updatedAt ?? null,\n request,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return json(err.body, { status: err.status })\n throw err\n }\n\n webhook.deletedAt = new Date()\n await em.flush()\n\n await invalidateWebhookSubscriptionCacheFor(scope.container, webhook.tenantId)\n\n await emitWebhooksEvent('webhooks.webhook.deleted', {\n webhookId: webhook.id,\n organizationId: webhook.organizationId,\n tenantId: webhook.tenantId,\n }, { persistent: true })\n\n return json({ success: true })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Get webhook detail',\n description: 'Returns a single webhook configuration for the current tenant scope.',\n methods: {\n GET: {\n summary: 'Get webhook',\n description: 'Returns webhook configuration, masked secret metadata, and delivery settings.',\n pathParams: z.object({ id: z.string().uuid() }),\n responses: [{ status: 200, description: 'Webhook detail', schema: webhookDetailResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Webhook not found', schema: errorSchema },\n ],\n },\n PUT: {\n summary: 'Update webhook',\n description: 'Updates a single webhook configuration.',\n pathParams: z.object({ id: z.string().uuid() }),\n requestBody: {\n contentType: 'application/json',\n schema: webhookUpdateSchema,\n description: 'Webhook fields to update.',\n },\n responses: [{ status: 200, description: 'Webhook updated', schema: webhookDetailResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Webhook not found', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete webhook',\n description: 'Soft-deletes a webhook endpoint.',\n pathParams: z.object({ id: z.string().uuid() }),\n responses: [{ status: 200, description: 'Webhook deleted', schema: deleteResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Webhook not found', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { emitWebhooksEvent } from '../../../events'\nimport { findScopedWebhook, json, resolveWebhookRequestScope, serializeWebhookDetail } from '../../helpers'\nimport { webhookUpdateSchema } from '../../../data/validators'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { invalidateWebhookSubscriptionCacheFor } from '../../../lib/subscription-cache'\nimport { applyWebhookActiveState } from '../../../lib/activation'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['webhooks.view'] },\n PUT: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n}\n\ninterface RouteContext {\n params: Promise<{ id: string }>\n}\n\nconst webhookDetailResponseSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n url: z.string(),\n subscribedEvents: z.array(z.string()),\n httpMethod: z.string(),\n isActive: z.boolean(),\n deliveryStrategy: z.string(),\n maxRetries: z.number(),\n consecutiveFailures: z.number(),\n lastSuccessAt: z.string().nullable(),\n lastFailureAt: z.string().nullable(),\n createdAt: z.string(),\n updatedAt: z.string(),\n customHeaders: z.record(z.string(), z.string()).nullable(),\n strategyConfig: z.record(z.string(), z.unknown()).nullable(),\n timeoutMs: z.number(),\n rateLimitPerMinute: z.number(),\n autoDisableThreshold: z.number(),\n integrationId: z.string().nullable(),\n maskedSecret: z.string(),\n previousSecretSetAt: z.string().nullable(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\nconst deleteResponseSchema = z.object({ success: z.literal(true) })\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n const scope = await resolveWebhookRequestScope(request)\n if (scope instanceof Response) return scope\n\n const params = await context.params\n const webhook = await findScopedWebhook(scope.em.fork(), scope, params.id)\n\n if (!webhook) {\n return json({ error: 'Webhook not found' }, { status: 404 })\n }\n\n return json(serializeWebhookDetail(webhook))\n}\n\nexport async function PUT(request: Request, context: RouteContext): Promise<Response> {\n const scope = await resolveWebhookRequestScope(request)\n if (scope instanceof Response) return scope\n\n const params = await context.params\n const em = scope.em.fork()\n const webhook = await findScopedWebhook(em, scope, params.id)\n\n if (!webhook) {\n return json({ error: 'Webhook not found' }, { status: 404 })\n }\n\n try {\n await enforceCommandOptimisticLockWithGuards(scope.container, {\n resourceKind: 'webhooks.endpoint',\n resourceId: webhook.id,\n current: webhook.updatedAt ?? null,\n request,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return json(err.body, { status: err.status })\n throw err\n }\n\n const parsed = webhookUpdateSchema.safeParse(await request.json().catch(() => null))\n if (!parsed.success) {\n return json({ error: 'Invalid request payload' }, { status: 400 })\n }\n\n const input = parsed.data\n if (input.name !== undefined) webhook.name = input.name\n if (input.description !== undefined) webhook.description = input.description\n if (input.url !== undefined) webhook.url = input.url\n if (input.subscribedEvents !== undefined) webhook.subscribedEvents = input.subscribedEvents\n if (input.httpMethod !== undefined) webhook.httpMethod = input.httpMethod\n if (input.customHeaders !== undefined) webhook.customHeaders = input.customHeaders\n if (input.deliveryStrategy !== undefined) webhook.deliveryStrategy = input.deliveryStrategy\n if (input.strategyConfig !== undefined) webhook.strategyConfig = input.strategyConfig\n if (input.maxRetries !== undefined) webhook.maxRetries = input.maxRetries\n if (input.timeoutMs !== undefined) webhook.timeoutMs = input.timeoutMs\n if (input.rateLimitPerMinute !== undefined) webhook.rateLimitPerMinute = input.rateLimitPerMinute\n if (input.autoDisableThreshold !== undefined) webhook.autoDisableThreshold = input.autoDisableThreshold\n if (input.integrationId !== undefined) webhook.integrationId = input.integrationId\n if (input.isActive !== undefined) applyWebhookActiveState(webhook, input.isActive)\n\n await em.flush()\n\n await invalidateWebhookSubscriptionCacheFor(scope.container, webhook.tenantId)\n\n await emitWebhooksEvent('webhooks.webhook.updated', {\n webhookId: webhook.id,\n organizationId: webhook.organizationId,\n tenantId: webhook.tenantId,\n }, { persistent: true })\n\n return json(serializeWebhookDetail(webhook))\n}\n\nexport async function DELETE(request: Request, context: RouteContext): Promise<Response> {\n const scope = await resolveWebhookRequestScope(request)\n if (scope instanceof Response) return scope\n\n const params = await context.params\n const em = scope.em.fork()\n const webhook = await findScopedWebhook(em, scope, params.id)\n const { translate } = await resolveTranslations()\n\n if (!webhook) {\n return json({ error: translate('webhooks.errors.notFound', 'Webhook not found') }, { status: 404 })\n }\n\n try {\n await enforceCommandOptimisticLockWithGuards(scope.container, {\n resourceKind: 'webhooks.endpoint',\n resourceId: webhook.id,\n current: webhook.updatedAt ?? null,\n request,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return json(err.body, { status: err.status })\n throw err\n }\n\n webhook.deletedAt = new Date()\n await em.flush()\n\n await invalidateWebhookSubscriptionCacheFor(scope.container, webhook.tenantId)\n\n await emitWebhooksEvent('webhooks.webhook.deleted', {\n webhookId: webhook.id,\n organizationId: webhook.organizationId,\n tenantId: webhook.tenantId,\n }, { persistent: true })\n\n return json({ success: true })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Get webhook detail',\n description: 'Returns a single webhook configuration for the current tenant scope.',\n methods: {\n GET: {\n summary: 'Get webhook',\n description: 'Returns webhook configuration, masked secret metadata, and delivery settings.',\n pathParams: z.object({ id: z.string().uuid() }),\n responses: [{ status: 200, description: 'Webhook detail', schema: webhookDetailResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Webhook not found', schema: errorSchema },\n ],\n },\n PUT: {\n summary: 'Update webhook',\n description: 'Updates a single webhook configuration.',\n pathParams: z.object({ id: z.string().uuid() }),\n requestBody: {\n contentType: 'application/json',\n schema: webhookUpdateSchema,\n description: 'Webhook fields to update.',\n },\n responses: [{ status: 200, description: 'Webhook updated', schema: webhookDetailResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Webhook not found', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete webhook',\n description: 'Soft-deletes a webhook endpoint.',\n pathParams: z.object({ id: z.string().uuid() }),\n responses: [{ status: 200, description: 'Webhook deleted', schema: deleteResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Webhook not found', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,7 @@ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
7
7
  import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
8
8
  import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
9
9
  import { invalidateWebhookSubscriptionCacheFor } from "../../lib/subscription-cache.js";
10
+ import { applyWebhookActiveState } from "../../lib/activation.js";
10
11
  function json(payload, init = { status: 200 }) {
11
12
  return new Response(JSON.stringify(payload), {
12
13
  ...init,
@@ -119,7 +120,7 @@ const crud = makeCrudRoute({
119
120
  if (input.rateLimitPerMinute !== void 0) entity.rateLimitPerMinute = input.rateLimitPerMinute;
120
121
  if (input.autoDisableThreshold !== void 0) entity.autoDisableThreshold = input.autoDisableThreshold;
121
122
  if (input.integrationId !== void 0) entity.integrationId = input.integrationId;
122
- if (input.isActive !== void 0) entity.isActive = input.isActive;
123
+ if (input.isActive !== void 0) applyWebhookActiveState(entity, input.isActive);
123
124
  }
124
125
  },
125
126
  del: { idFrom: "query" },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/webhooks/api/webhooks/route.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { WebhookEntity } from '../../data/entities'\nimport { webhookCreateSchema, webhookUpdateSchema, webhookListQuerySchema } from '../../data/validators'\nimport { generateWebhookSecret } from '@open-mercato/shared/lib/webhooks'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport type { WebhookCreateInput, WebhookUpdateInput } from '../../data/validators'\nimport { invalidateWebhookSubscriptionCacheFor } from '../../lib/subscription-cache'\n\ntype WebhookCrudCtx = CrudCtx & {\n __webhookSecret?: string\n}\n\nfunction json(payload: unknown, init: ResponseInit = { status: 200 }) {\n return new Response(JSON.stringify(payload), {\n ...init,\n headers: { 'content-type': 'application/json', ...(init.headers || {}) },\n })\n}\n\nconst webhookListItemSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n url: z.string(),\n subscribedEvents: z.array(z.string()),\n httpMethod: z.string(),\n isActive: z.boolean(),\n deliveryStrategy: z.string(),\n maxRetries: z.number(),\n consecutiveFailures: z.number(),\n lastSuccessAt: z.string().nullable(),\n lastFailureAt: z.string().nullable(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst webhookCollectionResponseSchema = z.object({\n items: z.array(webhookListItemSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().positive(),\n pageSize: z.number().int().positive(),\n totalPages: z.number().int().nonnegative(),\n})\n\nconst webhookCreateResponseSchema = z.object({\n id: z.string(),\n name: z.string(),\n url: z.string(),\n secret: z.string(),\n subscribedEvents: z.array(z.string()),\n isActive: z.boolean(),\n})\n\nconst webhookDetailResponseSchema = webhookListItemSchema.extend({\n customHeaders: z.record(z.string(), z.string()).nullable(),\n strategyConfig: z.record(z.string(), z.unknown()).nullable(),\n timeoutMs: z.number(),\n rateLimitPerMinute: z.number(),\n autoDisableThreshold: z.number(),\n integrationId: z.string().nullable(),\n})\n\nconst deleteResponseSchema = z.object({ success: z.literal(true) })\nconst errorSchema = z.object({ error: z.string() })\n\nfunction resolveTenantIdForInvalidation(entity: unknown, ctx: CrudCtx): string | null {\n const entityTenantId = (entity as { tenantId?: unknown } | null)?.tenantId\n if (typeof entityTenantId === 'string' && entityTenantId.length > 0) return entityTenantId\n return ctx.auth?.tenantId ?? null\n}\n\nconst crud = makeCrudRoute<WebhookCreateInput, WebhookUpdateInput, z.infer<typeof webhookListQuerySchema>>({\n metadata: {\n GET: { requireAuth: true, requireFeatures: ['webhooks.view'] },\n POST: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n },\n orm: { entity: WebhookEntity, orgField: 'organizationId' },\n events: { module: 'webhooks', entity: 'webhook', persistent: true },\n list: { schema: webhookListQuerySchema },\n create: {\n schema: webhookCreateSchema,\n mapToEntity: (input, ctx) => {\n const scopedCtx = ctx as WebhookCrudCtx\n const secret = scopedCtx.__webhookSecret\n if (!secret) throw new Error('Webhook secret not prepared')\n return {\n name: input.name,\n description: input.description ?? null,\n url: input.url,\n secret,\n subscribedEvents: input.subscribedEvents,\n httpMethod: input.httpMethod ?? 'POST',\n customHeaders: input.customHeaders ?? null,\n deliveryStrategy: input.deliveryStrategy ?? 'http',\n strategyConfig: input.strategyConfig ?? null,\n maxRetries: input.maxRetries ?? 10,\n timeoutMs: input.timeoutMs ?? 15000,\n rateLimitPerMinute: input.rateLimitPerMinute ?? 0,\n autoDisableThreshold: input.autoDisableThreshold ?? 100,\n integrationId: input.integrationId ?? null,\n organizationId: ctx.auth?.orgId ?? '',\n tenantId: ctx.auth?.tenantId ?? '',\n }\n },\n response: (entity) => ({\n id: entity.id,\n name: entity.name,\n url: entity.url,\n secret: (entity as WebhookEntity & { __revealSecret?: string }).__revealSecret ?? '***',\n subscribedEvents: entity.subscribedEvents,\n isActive: entity.isActive,\n }),\n },\n update: {\n schema: webhookUpdateSchema,\n applyToEntity: (entity, input) => {\n if (input.name !== undefined) entity.name = input.name\n if (input.description !== undefined) entity.description = input.description\n if (input.url !== undefined) entity.url = input.url\n if (input.subscribedEvents !== undefined) entity.subscribedEvents = input.subscribedEvents\n if (input.httpMethod !== undefined) entity.httpMethod = input.httpMethod\n if (input.customHeaders !== undefined) entity.customHeaders = input.customHeaders\n if (input.deliveryStrategy !== undefined) entity.deliveryStrategy = input.deliveryStrategy\n if (input.strategyConfig !== undefined) entity.strategyConfig = input.strategyConfig\n if (input.maxRetries !== undefined) entity.maxRetries = input.maxRetries\n if (input.timeoutMs !== undefined) entity.timeoutMs = input.timeoutMs\n if (input.rateLimitPerMinute !== undefined) entity.rateLimitPerMinute = input.rateLimitPerMinute\n if (input.autoDisableThreshold !== undefined) entity.autoDisableThreshold = input.autoDisableThreshold\n if (input.integrationId !== undefined) entity.integrationId = input.integrationId\n if (input.isActive !== undefined) entity.isActive = input.isActive\n },\n },\n del: { idFrom: 'query' },\n hooks: {\n beforeList: async (query, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n\n const page = Math.max(parseInt(query.page ?? '1', 10) || 1, 1)\n const pageSize = Math.min(Math.max(parseInt(query.pageSize ?? '20', 10) || 20, 1), 100)\n const search = (query.search ?? '').trim().toLowerCase()\n\n const organizationIds = Array.isArray(ctx.organizationIds) ? ctx.organizationIds : null\n if (organizationIds && organizationIds.length === 0) {\n throw json({ items: [], total: 0, page, pageSize, totalPages: 0 })\n }\n\n const em = ctx.container.resolve('em') as EntityManager\n const qb = em.createQueryBuilder(WebhookEntity, 'w')\n qb.where({ deletedAt: null })\n qb.andWhere({ tenantId: auth.tenantId })\n\n if (organizationIds && organizationIds.length > 0) {\n qb.andWhere({ organizationId: { $in: organizationIds } })\n } else if (auth.orgId) {\n qb.andWhere({ organizationId: auth.orgId })\n }\n\n if (search) {\n const pattern = `%${escapeLikePattern(search)}%`\n qb.andWhere({\n $or: [\n { name: { $ilike: pattern } },\n { url: { $ilike: pattern } },\n ],\n })\n }\n\n if (query.isActive !== undefined && query.isActive !== '') {\n const active = parseBooleanToken(query.isActive)\n if (active !== null) {\n qb.andWhere({ isActive: active })\n }\n }\n\n qb.orderBy({ createdAt: 'desc' })\n qb.limit(pageSize).offset((page - 1) * pageSize)\n const [items, total] = await qb.getResultAndCount()\n\n const payload = {\n items: items.map((item) => ({\n id: item.id,\n name: item.name,\n description: item.description ?? null,\n url: item.url,\n subscribedEvents: item.subscribedEvents,\n httpMethod: item.httpMethod,\n isActive: item.isActive,\n deliveryStrategy: item.deliveryStrategy,\n maxRetries: item.maxRetries,\n consecutiveFailures: item.consecutiveFailures,\n lastSuccessAt: item.lastSuccessAt?.toISOString() ?? null,\n lastFailureAt: item.lastFailureAt?.toISOString() ?? null,\n createdAt: item.createdAt.toISOString(),\n updatedAt: item.updatedAt.toISOString(),\n })),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n }\n\n throw json(payload)\n },\n beforeCreate: async (input, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n if (!auth?.orgId) throw json({ error: translate('webhooks.errors.orgRequired', 'Organization context required') }, { status: 400 })\n\n const scopedCtx = ctx as WebhookCrudCtx\n scopedCtx.__webhookSecret = generateWebhookSecret()\n\n return input\n },\n afterCreate: async (entity, ctx) => {\n const scopedCtx = ctx as WebhookCrudCtx\n if (scopedCtx.__webhookSecret) {\n ;(entity as WebhookEntity & { __revealSecret?: string }).__revealSecret = scopedCtx.__webhookSecret\n }\n await invalidateWebhookSubscriptionCacheFor(ctx.container, resolveTenantIdForInvalidation(entity, ctx))\n },\n afterUpdate: async (entity, ctx) => {\n await invalidateWebhookSubscriptionCacheFor(ctx.container, resolveTenantIdForInvalidation(entity, ctx))\n },\n afterDelete: async (_id, ctx) => {\n await invalidateWebhookSubscriptionCacheFor(ctx.container, ctx.auth?.tenantId ?? null)\n },\n beforeDelete: async (id, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n\n const em = ctx.container.resolve('em') as EntityManager\n const record = await em.findOne(WebhookEntity, { id, deletedAt: null, tenantId: auth.tenantId })\n if (!record) throw json({ error: translate('webhooks.errors.notFound', 'Webhook not found') }, { status: 404 })\n\n const allowedIds = ctx.organizationScope?.allowedIds ?? null\n if (record.organizationId && Array.isArray(allowedIds) && allowedIds.length > 0) {\n if (!allowedIds.includes(record.organizationId)) {\n throw json({ error: translate('webhooks.errors.forbidden', 'Forbidden') }, { status: 403 })\n }\n }\n },\n },\n})\n\nexport const metadata = crud.metadata\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage webhooks',\n description: 'CRUD operations for webhook endpoints following the Standard Webhooks specification.',\n methods: {\n GET: {\n summary: 'List webhooks',\n description: 'Returns paginated webhooks for the current tenant and organization.',\n query: webhookListQuerySchema,\n responses: [{ status: 200, description: 'Webhook collection', schema: webhookCollectionResponseSchema }],\n errors: [\n { status: 400, description: 'Tenant context missing', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Create webhook',\n description: 'Creates a new webhook endpoint. A signing secret (whsec_ prefixed) is auto-generated and returned once.',\n requestBody: { contentType: 'application/json', schema: webhookCreateSchema, description: 'Webhook configuration.' },\n responses: [{ status: 201, description: 'Webhook created', schema: webhookCreateResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n PUT: {\n summary: 'Update webhook',\n description: 'Updates an existing webhook configuration.',\n requestBody: { contentType: 'application/json', schema: webhookUpdateSchema, description: 'Fields to update.' },\n responses: [{ status: 200, description: 'Webhook updated', schema: webhookDetailResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorSchema },\n { status: 404, description: 'Not found', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete webhook',\n description: 'Soft-deletes a webhook endpoint.',\n query: z.object({ id: z.string().uuid().describe('Webhook ID to delete') }),\n responses: [{ status: 200, description: 'Deleted', schema: deleteResponseSchema }],\n errors: [\n { status: 404, description: 'Not found', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { WebhookEntity } from '../../data/entities'\nimport { webhookCreateSchema, webhookUpdateSchema, webhookListQuerySchema } from '../../data/validators'\nimport { generateWebhookSecret } from '@open-mercato/shared/lib/webhooks'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport type { WebhookCreateInput, WebhookUpdateInput } from '../../data/validators'\nimport { invalidateWebhookSubscriptionCacheFor } from '../../lib/subscription-cache'\nimport { applyWebhookActiveState } from '../../lib/activation'\n\ntype WebhookCrudCtx = CrudCtx & {\n __webhookSecret?: string\n}\n\nfunction json(payload: unknown, init: ResponseInit = { status: 200 }) {\n return new Response(JSON.stringify(payload), {\n ...init,\n headers: { 'content-type': 'application/json', ...(init.headers || {}) },\n })\n}\n\nconst webhookListItemSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n url: z.string(),\n subscribedEvents: z.array(z.string()),\n httpMethod: z.string(),\n isActive: z.boolean(),\n deliveryStrategy: z.string(),\n maxRetries: z.number(),\n consecutiveFailures: z.number(),\n lastSuccessAt: z.string().nullable(),\n lastFailureAt: z.string().nullable(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst webhookCollectionResponseSchema = z.object({\n items: z.array(webhookListItemSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().positive(),\n pageSize: z.number().int().positive(),\n totalPages: z.number().int().nonnegative(),\n})\n\nconst webhookCreateResponseSchema = z.object({\n id: z.string(),\n name: z.string(),\n url: z.string(),\n secret: z.string(),\n subscribedEvents: z.array(z.string()),\n isActive: z.boolean(),\n})\n\nconst webhookDetailResponseSchema = webhookListItemSchema.extend({\n customHeaders: z.record(z.string(), z.string()).nullable(),\n strategyConfig: z.record(z.string(), z.unknown()).nullable(),\n timeoutMs: z.number(),\n rateLimitPerMinute: z.number(),\n autoDisableThreshold: z.number(),\n integrationId: z.string().nullable(),\n})\n\nconst deleteResponseSchema = z.object({ success: z.literal(true) })\nconst errorSchema = z.object({ error: z.string() })\n\nfunction resolveTenantIdForInvalidation(entity: unknown, ctx: CrudCtx): string | null {\n const entityTenantId = (entity as { tenantId?: unknown } | null)?.tenantId\n if (typeof entityTenantId === 'string' && entityTenantId.length > 0) return entityTenantId\n return ctx.auth?.tenantId ?? null\n}\n\nconst crud = makeCrudRoute<WebhookCreateInput, WebhookUpdateInput, z.infer<typeof webhookListQuerySchema>>({\n metadata: {\n GET: { requireAuth: true, requireFeatures: ['webhooks.view'] },\n POST: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['webhooks.manage'] },\n },\n orm: { entity: WebhookEntity, orgField: 'organizationId' },\n events: { module: 'webhooks', entity: 'webhook', persistent: true },\n list: { schema: webhookListQuerySchema },\n create: {\n schema: webhookCreateSchema,\n mapToEntity: (input, ctx) => {\n const scopedCtx = ctx as WebhookCrudCtx\n const secret = scopedCtx.__webhookSecret\n if (!secret) throw new Error('Webhook secret not prepared')\n return {\n name: input.name,\n description: input.description ?? null,\n url: input.url,\n secret,\n subscribedEvents: input.subscribedEvents,\n httpMethod: input.httpMethod ?? 'POST',\n customHeaders: input.customHeaders ?? null,\n deliveryStrategy: input.deliveryStrategy ?? 'http',\n strategyConfig: input.strategyConfig ?? null,\n maxRetries: input.maxRetries ?? 10,\n timeoutMs: input.timeoutMs ?? 15000,\n rateLimitPerMinute: input.rateLimitPerMinute ?? 0,\n autoDisableThreshold: input.autoDisableThreshold ?? 100,\n integrationId: input.integrationId ?? null,\n organizationId: ctx.auth?.orgId ?? '',\n tenantId: ctx.auth?.tenantId ?? '',\n }\n },\n response: (entity) => ({\n id: entity.id,\n name: entity.name,\n url: entity.url,\n secret: (entity as WebhookEntity & { __revealSecret?: string }).__revealSecret ?? '***',\n subscribedEvents: entity.subscribedEvents,\n isActive: entity.isActive,\n }),\n },\n update: {\n schema: webhookUpdateSchema,\n applyToEntity: (entity, input) => {\n if (input.name !== undefined) entity.name = input.name\n if (input.description !== undefined) entity.description = input.description\n if (input.url !== undefined) entity.url = input.url\n if (input.subscribedEvents !== undefined) entity.subscribedEvents = input.subscribedEvents\n if (input.httpMethod !== undefined) entity.httpMethod = input.httpMethod\n if (input.customHeaders !== undefined) entity.customHeaders = input.customHeaders\n if (input.deliveryStrategy !== undefined) entity.deliveryStrategy = input.deliveryStrategy\n if (input.strategyConfig !== undefined) entity.strategyConfig = input.strategyConfig\n if (input.maxRetries !== undefined) entity.maxRetries = input.maxRetries\n if (input.timeoutMs !== undefined) entity.timeoutMs = input.timeoutMs\n if (input.rateLimitPerMinute !== undefined) entity.rateLimitPerMinute = input.rateLimitPerMinute\n if (input.autoDisableThreshold !== undefined) entity.autoDisableThreshold = input.autoDisableThreshold\n if (input.integrationId !== undefined) entity.integrationId = input.integrationId\n if (input.isActive !== undefined) applyWebhookActiveState(entity, input.isActive)\n },\n },\n del: { idFrom: 'query' },\n hooks: {\n beforeList: async (query, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n\n const page = Math.max(parseInt(query.page ?? '1', 10) || 1, 1)\n const pageSize = Math.min(Math.max(parseInt(query.pageSize ?? '20', 10) || 20, 1), 100)\n const search = (query.search ?? '').trim().toLowerCase()\n\n const organizationIds = Array.isArray(ctx.organizationIds) ? ctx.organizationIds : null\n if (organizationIds && organizationIds.length === 0) {\n throw json({ items: [], total: 0, page, pageSize, totalPages: 0 })\n }\n\n const em = ctx.container.resolve('em') as EntityManager\n const qb = em.createQueryBuilder(WebhookEntity, 'w')\n qb.where({ deletedAt: null })\n qb.andWhere({ tenantId: auth.tenantId })\n\n if (organizationIds && organizationIds.length > 0) {\n qb.andWhere({ organizationId: { $in: organizationIds } })\n } else if (auth.orgId) {\n qb.andWhere({ organizationId: auth.orgId })\n }\n\n if (search) {\n const pattern = `%${escapeLikePattern(search)}%`\n qb.andWhere({\n $or: [\n { name: { $ilike: pattern } },\n { url: { $ilike: pattern } },\n ],\n })\n }\n\n if (query.isActive !== undefined && query.isActive !== '') {\n const active = parseBooleanToken(query.isActive)\n if (active !== null) {\n qb.andWhere({ isActive: active })\n }\n }\n\n qb.orderBy({ createdAt: 'desc' })\n qb.limit(pageSize).offset((page - 1) * pageSize)\n const [items, total] = await qb.getResultAndCount()\n\n const payload = {\n items: items.map((item) => ({\n id: item.id,\n name: item.name,\n description: item.description ?? null,\n url: item.url,\n subscribedEvents: item.subscribedEvents,\n httpMethod: item.httpMethod,\n isActive: item.isActive,\n deliveryStrategy: item.deliveryStrategy,\n maxRetries: item.maxRetries,\n consecutiveFailures: item.consecutiveFailures,\n lastSuccessAt: item.lastSuccessAt?.toISOString() ?? null,\n lastFailureAt: item.lastFailureAt?.toISOString() ?? null,\n createdAt: item.createdAt.toISOString(),\n updatedAt: item.updatedAt.toISOString(),\n })),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n }\n\n throw json(payload)\n },\n beforeCreate: async (input, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n if (!auth?.orgId) throw json({ error: translate('webhooks.errors.orgRequired', 'Organization context required') }, { status: 400 })\n\n const scopedCtx = ctx as WebhookCrudCtx\n scopedCtx.__webhookSecret = generateWebhookSecret()\n\n return input\n },\n afterCreate: async (entity, ctx) => {\n const scopedCtx = ctx as WebhookCrudCtx\n if (scopedCtx.__webhookSecret) {\n ;(entity as WebhookEntity & { __revealSecret?: string }).__revealSecret = scopedCtx.__webhookSecret\n }\n await invalidateWebhookSubscriptionCacheFor(ctx.container, resolveTenantIdForInvalidation(entity, ctx))\n },\n afterUpdate: async (entity, ctx) => {\n await invalidateWebhookSubscriptionCacheFor(ctx.container, resolveTenantIdForInvalidation(entity, ctx))\n },\n afterDelete: async (_id, ctx) => {\n await invalidateWebhookSubscriptionCacheFor(ctx.container, ctx.auth?.tenantId ?? null)\n },\n beforeDelete: async (id, ctx) => {\n const auth = ctx.auth\n const { translate } = await resolveTranslations()\n if (!auth?.tenantId) throw json({ error: translate('webhooks.errors.tenantRequired', 'Tenant context required') }, { status: 400 })\n\n const em = ctx.container.resolve('em') as EntityManager\n const record = await em.findOne(WebhookEntity, { id, deletedAt: null, tenantId: auth.tenantId })\n if (!record) throw json({ error: translate('webhooks.errors.notFound', 'Webhook not found') }, { status: 404 })\n\n const allowedIds = ctx.organizationScope?.allowedIds ?? null\n if (record.organizationId && Array.isArray(allowedIds) && allowedIds.length > 0) {\n if (!allowedIds.includes(record.organizationId)) {\n throw json({ error: translate('webhooks.errors.forbidden', 'Forbidden') }, { status: 403 })\n }\n }\n },\n },\n})\n\nexport const metadata = crud.metadata\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage webhooks',\n description: 'CRUD operations for webhook endpoints following the Standard Webhooks specification.',\n methods: {\n GET: {\n summary: 'List webhooks',\n description: 'Returns paginated webhooks for the current tenant and organization.',\n query: webhookListQuerySchema,\n responses: [{ status: 200, description: 'Webhook collection', schema: webhookCollectionResponseSchema }],\n errors: [\n { status: 400, description: 'Tenant context missing', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Create webhook',\n description: 'Creates a new webhook endpoint. A signing secret (whsec_ prefixed) is auto-generated and returned once.',\n requestBody: { contentType: 'application/json', schema: webhookCreateSchema, description: 'Webhook configuration.' },\n responses: [{ status: 201, description: 'Webhook created', schema: webhookCreateResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n PUT: {\n summary: 'Update webhook',\n description: 'Updates an existing webhook configuration.',\n requestBody: { contentType: 'application/json', schema: webhookUpdateSchema, description: 'Fields to update.' },\n responses: [{ status: 200, description: 'Webhook updated', schema: webhookDetailResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorSchema },\n { status: 404, description: 'Not found', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete webhook',\n description: 'Soft-deletes a webhook endpoint.',\n query: z.object({ id: z.string().uuid().describe('Webhook ID to delete') }),\n responses: [{ status: 200, description: 'Deleted', schema: deleteResponseSchema }],\n errors: [\n { status: 404, description: 'Not found', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,8 @@
1
+ function applyWebhookActiveState(webhook, isActive) {
2
+ if (isActive && !webhook.isActive) webhook.consecutiveFailures = 0;
3
+ webhook.isActive = isActive;
4
+ }
5
+ export {
6
+ applyWebhookActiveState
7
+ };
8
+ //# sourceMappingURL=activation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/webhooks/lib/activation.ts"],
4
+ "sourcesContent": ["import type { WebhookEntity } from '../data/entities'\n\nexport function applyWebhookActiveState(\n webhook: Pick<WebhookEntity, 'isActive' | 'consecutiveFailures'>,\n isActive: boolean,\n): void {\n if (isActive && !webhook.isActive) webhook.consecutiveFailures = 0\n webhook.isActive = isActive\n}\n"],
5
+ "mappings": "AAEO,SAAS,wBACd,SACA,UACM;AACN,MAAI,YAAY,CAAC,QAAQ,SAAU,SAAQ,sBAAsB;AACjE,UAAQ,WAAW;AACrB;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/webhooks",
3
- "version": "0.9.1-develop.7499.1.fcec456e4e",
3
+ "version": "0.9.1-develop.7515.1.e2712c8b00",
4
4
  "license": "MIT",
5
5
  "description": "Webhooks module for Open Mercato — Standard Webhooks compliant outbound/inbound delivery",
6
6
  "type": "module",
@@ -70,20 +70,20 @@
70
70
  }
71
71
  },
72
72
  "dependencies": {
73
- "@open-mercato/cache": "0.9.1-develop.7499.1.fcec456e4e",
74
- "@open-mercato/core": "0.9.1-develop.7499.1.fcec456e4e",
75
- "@open-mercato/queue": "0.9.1-develop.7499.1.fcec456e4e",
76
- "@open-mercato/ui": "0.9.1-develop.7499.1.fcec456e4e",
73
+ "@open-mercato/cache": "0.9.1-develop.7515.1.e2712c8b00",
74
+ "@open-mercato/core": "0.9.1-develop.7515.1.e2712c8b00",
75
+ "@open-mercato/queue": "0.9.1-develop.7515.1.e2712c8b00",
76
+ "@open-mercato/ui": "0.9.1-develop.7515.1.e2712c8b00",
77
77
  "svix": "^1.99.1"
78
78
  },
79
79
  "peerDependencies": {
80
80
  "@mikro-orm/postgresql": "^7.0.14",
81
- "@open-mercato/shared": "0.9.1-develop.7499.1.fcec456e4e",
81
+ "@open-mercato/shared": "0.9.1-develop.7515.1.e2712c8b00",
82
82
  "react": "^19.0.0",
83
83
  "react-dom": "^19.0.0"
84
84
  },
85
85
  "devDependencies": {
86
- "@open-mercato/shared": "0.9.1-develop.7499.1.fcec456e4e",
86
+ "@open-mercato/shared": "0.9.1-develop.7515.1.e2712c8b00",
87
87
  "@types/jest": "^30.0.0",
88
88
  "@types/react": "^19.2.18",
89
89
  "@types/react-dom": "^19.2.5",
@@ -0,0 +1,86 @@
1
+ /** @jest-environment node */
2
+
3
+ const WEBHOOK_ID = '123e4567-e89b-12d3-a456-426614174072'
4
+
5
+ const webhookRecord = {
6
+ id: WEBHOOK_ID,
7
+ isActive: false,
8
+ consecutiveFailures: 2,
9
+ autoDisableThreshold: 2,
10
+ organizationId: 'org-1',
11
+ tenantId: 'tenant-1',
12
+ updatedAt: new Date('2026-06-01T10:00:00.000Z'),
13
+ }
14
+
15
+ const mockEm = {
16
+ fork: jest.fn(() => mockEm),
17
+ flush: jest.fn(async () => undefined),
18
+ }
19
+
20
+ const mockContainer = {
21
+ resolve: (token: string) => {
22
+ if (token === 'em') return mockEm
23
+ if (token === 'commandOptimisticLockGuardService') throw new Error('not registered')
24
+ return null
25
+ },
26
+ }
27
+
28
+ jest.mock('../../../../events', () => ({
29
+ emitWebhooksEvent: jest.fn(async () => undefined),
30
+ }))
31
+
32
+ jest.mock('../../../../lib/subscription-cache', () => ({
33
+ invalidateWebhookSubscriptionCacheFor: jest.fn(async () => undefined),
34
+ }))
35
+
36
+ jest.mock('../../../helpers', () => ({
37
+ json: (payload: unknown, init: ResponseInit = { status: 200 }) =>
38
+ new Response(JSON.stringify(payload), {
39
+ ...init,
40
+ headers: { 'content-type': 'application/json' },
41
+ }),
42
+ resolveWebhookRequestScope: jest.fn(async () => ({
43
+ container: mockContainer,
44
+ em: mockEm,
45
+ tenantId: 'tenant-1',
46
+ organizationId: 'org-1',
47
+ })),
48
+ findScopedWebhook: jest.fn(async () => webhookRecord),
49
+ serializeWebhookDetail: (item: { id: string; isActive: boolean; consecutiveFailures: number }) => ({
50
+ id: item.id,
51
+ isActive: item.isActive,
52
+ consecutiveFailures: item.consecutiveFailures,
53
+ }),
54
+ }))
55
+
56
+ import { PUT } from '../route'
57
+
58
+ const context = { params: Promise.resolve({ id: WEBHOOK_ID }) }
59
+
60
+ function putRequest(body: unknown) {
61
+ return new Request(`http://localhost/api/webhooks/${WEBHOOK_ID}`, {
62
+ method: 'PUT',
63
+ headers: { 'content-type': 'application/json' },
64
+ body: JSON.stringify(body),
65
+ })
66
+ }
67
+
68
+ describe('PUT /api/webhooks/:id re-activation', () => {
69
+ beforeEach(() => {
70
+ webhookRecord.isActive = false
71
+ webhookRecord.consecutiveFailures = 2
72
+ })
73
+
74
+ it('resets consecutiveFailures when an auto-disabled webhook is turned back on', async () => {
75
+ const res = await PUT(putRequest({ isActive: true }), context)
76
+ expect(res.status).toBe(200)
77
+ expect(await res.json()).toEqual({ id: WEBHOOK_ID, isActive: true, consecutiveFailures: 0 })
78
+ expect(mockEm.flush).toHaveBeenCalled()
79
+ })
80
+
81
+ it('keeps consecutiveFailures on an update that does not re-activate the webhook', async () => {
82
+ const res = await PUT(putRequest({ name: 'Renamed' }), context)
83
+ expect(res.status).toBe(200)
84
+ expect(await res.json()).toEqual({ id: WEBHOOK_ID, isActive: false, consecutiveFailures: 2 })
85
+ })
86
+ })
@@ -7,6 +7,7 @@ import { webhookUpdateSchema } from '../../../data/validators'
7
7
  import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
8
8
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
9
9
  import { invalidateWebhookSubscriptionCacheFor } from '../../../lib/subscription-cache'
10
+ import { applyWebhookActiveState } from '../../../lib/activation'
10
11
 
11
12
  export const metadata = {
12
13
  GET: { requireAuth: true, requireFeatures: ['webhooks.view'] },
@@ -103,7 +104,7 @@ export async function PUT(request: Request, context: RouteContext): Promise<Resp
103
104
  if (input.rateLimitPerMinute !== undefined) webhook.rateLimitPerMinute = input.rateLimitPerMinute
104
105
  if (input.autoDisableThreshold !== undefined) webhook.autoDisableThreshold = input.autoDisableThreshold
105
106
  if (input.integrationId !== undefined) webhook.integrationId = input.integrationId
106
- if (input.isActive !== undefined) webhook.isActive = input.isActive
107
+ if (input.isActive !== undefined) applyWebhookActiveState(webhook, input.isActive)
107
108
 
108
109
  await em.flush()
109
110
 
@@ -11,6 +11,7 @@ import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern
11
11
  import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
12
12
  import type { WebhookCreateInput, WebhookUpdateInput } from '../../data/validators'
13
13
  import { invalidateWebhookSubscriptionCacheFor } from '../../lib/subscription-cache'
14
+ import { applyWebhookActiveState } from '../../lib/activation'
14
15
 
15
16
  type WebhookCrudCtx = CrudCtx & {
16
17
  __webhookSecret?: string
@@ -135,7 +136,7 @@ const crud = makeCrudRoute<WebhookCreateInput, WebhookUpdateInput, z.infer<typeo
135
136
  if (input.rateLimitPerMinute !== undefined) entity.rateLimitPerMinute = input.rateLimitPerMinute
136
137
  if (input.autoDisableThreshold !== undefined) entity.autoDisableThreshold = input.autoDisableThreshold
137
138
  if (input.integrationId !== undefined) entity.integrationId = input.integrationId
138
- if (input.isActive !== undefined) entity.isActive = input.isActive
139
+ if (input.isActive !== undefined) applyWebhookActiveState(entity, input.isActive)
139
140
  },
140
141
  },
141
142
  del: { idFrom: 'query' },
@@ -0,0 +1,21 @@
1
+ import { applyWebhookActiveState } from '../activation'
2
+
3
+ describe('applyWebhookActiveState', () => {
4
+ it('resets the failure counter when an inactive webhook is re-activated', () => {
5
+ const webhook = { isActive: false, consecutiveFailures: 2 }
6
+ applyWebhookActiveState(webhook, true)
7
+ expect(webhook).toEqual({ isActive: true, consecutiveFailures: 0 })
8
+ })
9
+
10
+ it('keeps the failure counter when an already active webhook is saved as active', () => {
11
+ const webhook = { isActive: true, consecutiveFailures: 1 }
12
+ applyWebhookActiveState(webhook, true)
13
+ expect(webhook).toEqual({ isActive: true, consecutiveFailures: 1 })
14
+ })
15
+
16
+ it('keeps the failure counter when a webhook is deactivated', () => {
17
+ const webhook = { isActive: true, consecutiveFailures: 1 }
18
+ applyWebhookActiveState(webhook, false)
19
+ expect(webhook).toEqual({ isActive: false, consecutiveFailures: 1 })
20
+ })
21
+ })
@@ -0,0 +1,9 @@
1
+ import type { WebhookEntity } from '../data/entities'
2
+
3
+ export function applyWebhookActiveState(
4
+ webhook: Pick<WebhookEntity, 'isActive' | 'consecutiveFailures'>,
5
+ isActive: boolean,
6
+ ): void {
7
+ if (isActive && !webhook.isActive) webhook.consecutiveFailures = 0
8
+ webhook.isActive = isActive
9
+ }