@things-factory/webhook 10.1.113 → 10.1.116
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/dist-server/service/index.d.ts +4 -1
- package/dist-server/service/index.js +4 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/outbox/landed-probe.d.ts +26 -0
- package/dist-server/service/outbox/landed-probe.js +63 -0
- package/dist-server/service/outbox/landed-probe.js.map +1 -0
- package/dist-server/service/outbox/pump-loop.d.ts +9 -5
- package/dist-server/service/outbox/pump-loop.js +18 -34
- package/dist-server/service/outbox/pump-loop.js.map +1 -1
- package/dist-server/service/outbox/pump.d.ts +10 -0
- package/dist-server/service/outbox/pump.js +12 -0
- package/dist-server/service/outbox/pump.js.map +1 -1
- package/dist-server/service/webhook-destination/webhook-destination-resolver.d.ts +20 -0
- package/dist-server/service/webhook-destination/webhook-destination-resolver.js +30 -23
- package/dist-server/service/webhook-destination/webhook-destination-resolver.js.map +1 -1
- package/dist-server/service/webhook-outbox-event/webhook-outbox-resolver.d.ts +67 -0
- package/dist-server/service/webhook-outbox-event/webhook-outbox-resolver.js +338 -0
- package/dist-server/service/webhook-outbox-event/webhook-outbox-resolver.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-destination-resolver.js","sourceRoot":"","sources":["../../../server/service/webhook-destination/webhook-destination-resolver.ts"],"names":[],"mappings":";;;;AAAA,+CAA6E;AAE7E,iDAAmE;AAEnE,+DAA0D;AAE1D;;;;;;;;;;;;;;;;;;GAkBG;AAEI,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IAK/B,AAAN,KAAK,CAAC,mBAAmB,CAAQ,OAAwB;QACvD,OAAO,MAAM,IAAA,qBAAa,EAAC,wCAAkB,CAAC,CAAC,IAAI,CAAC;YAClD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE;YAClD,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;SACvB,CAAC,CAAA;IACJ,CAAC;IAIK,AAAN,KAAK,CAAC,kBAAkB,CACT,IAAY,EAClB,OAAwB;QAE/B,OAAO,MAAM,IAAA,qBAAa,EAAC,wCAAkB,CAAC,CAAC,OAAO,CAAC;YACrD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE;SACzD,CAAC,CAAA;IACJ,CAAC;IAED;;;;;;OAMG;IAGG,AAAN,KAAK,CAAC,sBAAsB,CACb,IAAY,EACW,KAAU,EACvC,OAAwB;QAE/B,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,wCAAkB,CAAC,CAAA;QAC9C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;QAElF;;;;WAIG;QACH,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;YACzB,CAAC,CAAC,EAAE,GAAG,CAAE,QAAQ,EAAE,MAAc,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE;YAC3D,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAA;QAEpB,OAAO,MAAM,IAAI,CAAC,IAAI,CACpB,IAAI,CAAC,MAAM,CAAC;YACV,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;YACnB,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAS;YAC/B,IAAI;YACJ,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,QAAQ,EAAE,QAAQ;YAC9C,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,QAAQ,EAAE,WAAW;YACvD,MAAM;YACN;;;eAGG;YACH,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,MAAM,IAAI,IAAI;YAChD,OAAO,EAAE,QAAQ,EAAE,OAAO,IAAK,OAAO,CAAC,KAAK,CAAC,IAAY;YACzD,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAW;SACnC,CAAC,CACH,CAAA;IACH,CAAC;IAED;;;;;;;OAOG;IAGG,AAAN,KAAK,CAAC,2BAA2B,CAClB,IAAY,EACV,MAAe,EACvB,OAAwB;QAE/B,MAAM,MAAM,GAAG,MAAM,IAAA,qBAAa,EAAC,wCAAkB,CAAC,CAAC,MAAM,CAC3D,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAS,EAAE,IAAI,EAAE,EACxD,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAW,EAAE,CAC/C,CAAA;QACD,OAAO,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAA;IAC1B,CAAC;CACF,CAAA;AA1FY,gEAA0B;AAK/B;IAJL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,wCAAkB,CAAC,EAAE;QACtC,WAAW,EAAE,qGAAqG;KACnH,CAAC;IACyB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;qEAK/B;AAIK;IAFL,IAAA,wBAAS,EAAC,uGAAuG,CAAC;IAClH,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,wCAAkB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wEAAwE,EAAE,CAAC;IAE7I,mBAAA,IAAA,kBAAG,EAAC,MAAM,CAAC,CAAA;IACX,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;oEAKP;AAWK;IAFL,IAAA,wBAAS,EAAC,0GAA0G,CAAC;IACrH,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,wCAAkB,EAAE,EAAE,WAAW,EAAE,sGAAsG,EAAE,CAAC;IAE9J,mBAAA,IAAA,kBAAG,EAAC,MAAM,CAAC,CAAA;IACX,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,oBAAY,CAAC,CAAA;IAClC,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;wEAgCP;AAYK;IAFL,IAAA,wBAAS,EAAC,0GAA0G,CAAC;IACrH,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,sFAAsF,EAAE,CAAC;IAEnI,mBAAA,IAAA,kBAAG,EAAC,MAAM,CAAC,CAAA;IACX,mBAAA,IAAA,kBAAG,EAAC,QAAQ,CAAC,CAAA;IACb,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;6EAOP;qCAzFU,0BAA0B;IADtC,IAAA,uBAAQ,GAAE;GACE,0BAA0B,CA0FtC","sourcesContent":["import { Arg, Ctx, Directive, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { getRepository, ScalarObject } from '@things-factory/shell'\n\nimport { WebhookDestination } from './webhook-destination'\n\n/*\n * ═══════════════════════════════════════════════════════════════════════════\n * Reading and writing the destinations of one tenant.\n *\n * ── A list, not \"the\" destination ──────────────────────────────────────────\n * The screen this replaces asked for one row (`UNIQUE(domain_id)`), so its query took no\n * arguments and its editor had no way to say which peer it was editing. That holds exactly\n * until a tenant mirrors into two twins, or pushes to a twin and an ERP — and then the second\n * peer cannot be entered at all (ADR-0041).\n *\n * ── Secrets are not read or written here ───────────────────────────────────\n * Signing and verifying keys come from the environment, per peer. A mutation that accepted\n * them would put them in a request body, a resolver log and an audit row; a query that\n * returned them would hand them to anyone who can open the settings screen.\n *\n * What the screen may say about a key is whether one is *present* — that is a fact about\n * configuration, not the value.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n@Resolver()\nexport class WebhookDestinationResolver {\n @Directive('@privilege(category: \"webhook\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => [WebhookDestination], {\n description: 'Every peer this tenant pushes facts to. Secrets are never included — only whether a key is present.'\n })\n async webhookDestinations(@Ctx() context: ResolverContext): Promise<WebhookDestination[]> {\n return await getRepository(WebhookDestination).find({\n where: { domain: { id: context.state.domain.id } },\n order: { peer: 'ASC' }\n })\n }\n\n @Directive('@privilege(category: \"webhook\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => WebhookDestination, { nullable: true, description: 'One peer\\'s destination, or null if this tenant has none by that name.' })\n async webhookDestination(\n @Arg('peer') peer: string,\n @Ctx() context: ResolverContext\n ): Promise<WebhookDestination | null> {\n return await getRepository(WebhookDestination).findOne({\n where: { domain: { id: context.state.domain.id }, peer }\n })\n }\n\n /**\n * Create or update one peer's destination.\n *\n * `patch` is merged field by field so a caller may send only what changed. A screen that\n * has to send every field in order to change one either reads the row first — and writes\n * back a value that moved in between — or blanks the fields it did not ask about.\n */\n @Directive('@privilege(category: \"webhook\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => WebhookDestination, { description: 'Create or update the destination for one peer. Fields absent from the patch keep their stored value.' })\n async saveWebhookDestination(\n @Arg('peer') peer: string,\n @Arg('patch', type => ScalarObject) patch: any,\n @Ctx() context: ResolverContext\n ): Promise<WebhookDestination> {\n const domainId = context.state.domain.id\n const repo = getRepository(WebhookDestination)\n const existing = await repo.findOne({ where: { domain: { id: domainId }, peer } })\n\n /*\n * `params` merges by key for the same reason the patch does — a peer's options are added\n * to over time, and replacing the whole object drops the ones this screen version does\n * not know about.\n */\n const params = patch.params\n ? { ...((existing?.params as any) ?? {}), ...patch.params }\n : existing?.params\n\n return await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n peer,\n endpoint: patch.endpoint ?? existing?.endpoint,\n description: patch.description ?? existing?.description,\n params,\n /*\n * `active` is a decision, so `false` has to be writable — `??` rather than `||`,\n * or turning a peer off would silently do nothing.\n */\n active: patch.active ?? existing?.active ?? true,\n creator: existing?.creator ?? (context.state.user as any),\n updater: context.state.user as any\n })\n )\n }\n\n /**\n * Stop pushing to a peer without forgetting it.\n *\n * Deliberately not a delete: the numbering and the queue are keyed by peer, and removing\n * the row would leave both orphaned. A destination that is turned off resumes where it\n * left off; one that is deleted starts over, and the peer reads that as a stream of\n * numbers it has already seen.\n */\n @Directive('@privilege(category: \"webhook\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Turn pushing to one peer off or on. The queue and the numbering are kept either way.' })\n async setWebhookDestinationActive(\n @Arg('peer') peer: string,\n @Arg('active') active: boolean,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const result = await getRepository(WebhookDestination).update(\n { domain: { id: context.state.domain.id } as any, peer },\n { active, updater: context.state.user as any }\n )\n return !!result.affected\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"webhook-destination-resolver.js","sourceRoot":"","sources":["../../../server/service/webhook-destination/webhook-destination-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager } from 'typeorm'\nimport { Arg, Ctx, Directive, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { getRepository, ScalarObject } from '@things-factory/shell'\n\nimport { WebhookDestination } from './webhook-destination'\n\n/*\n * ═══════════════════════════════════════════════════════════════════════════\n * Reading and writing the destinations of one tenant.\n *\n * ── A list, not \"the\" destination ──────────────────────────────────────────\n * The screen this replaces asked for one row (`UNIQUE(domain_id)`), so its query took no\n * arguments and its editor had no way to say which peer it was editing. That holds exactly\n * until a tenant mirrors into two twins, or pushes to a twin and an ERP — and then the second\n * peer cannot be entered at all (ADR-0041).\n *\n * ── Secrets are not read or written here ───────────────────────────────────\n * Signing and verifying keys come from the environment, per peer. A mutation that accepted\n * them would put them in a request body, a resolver log and an audit row; a query that\n * returned them would hand them to anyone who can open the settings screen.\n *\n * What the screen may say about a key is whether one is *present* — that is a fact about\n * configuration, not the value.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n/**\n * One change a person made to a destination — before and after, in the writing transaction.\n *\n * ADR-0078 decision 1: who changed where facts go, when, and from what to what, is kept. The module does not\n * own a product's change history, so it hands the change to whoever listens, inside the same transaction; the\n * product writes it into its own history (operato-plant: `fact-change`). A listener that throws rolls the\n * change back — a change that cannot be recorded is not made.\n */\nexport interface WebhookDestinationChange {\n domainId: string\n peer: string\n user?: unknown\n /** Absent when the destination is new. */\n before?: Pick<WebhookDestination, 'endpoint' | 'description' | 'active' | 'params'>\n after: Pick<WebhookDestination, 'endpoint' | 'description' | 'active' | 'params'>\n}\n\nexport type WebhookDestinationListener = (tx: EntityManager, change: WebhookDestinationChange) => Promise<void> | void\n\nconst destinationListeners = new Set<WebhookDestinationListener>()\n\n/** Listen to destination changes. Returns a function that stops listening. */\nexport function onWebhookDestinationChange(listener: WebhookDestinationListener): () => void {\n destinationListeners.add(listener)\n return () => destinationListeners.delete(listener)\n}\n\nconst shapeOf = (row?: Partial<WebhookDestination>) =>\n row ? { endpoint: row.endpoint, description: row.description, active: row.active, params: row.params } : undefined\n\nasync function tellListeners(tx: EntityManager, change: WebhookDestinationChange): Promise<void> {\n for (const listener of destinationListeners) await listener(tx, change)\n}\n\n@Resolver()\nexport class WebhookDestinationResolver {\n @Directive('@privilege(category: \"webhook\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => [WebhookDestination], {\n description: 'Every peer this tenant pushes facts to. Secrets are never included — only whether a key is present.'\n })\n async webhookDestinations(@Ctx() context: ResolverContext): Promise<WebhookDestination[]> {\n return await getRepository(WebhookDestination).find({\n where: { domain: { id: context.state.domain.id } },\n order: { peer: 'ASC' }\n })\n }\n\n @Directive('@privilege(category: \"webhook\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => WebhookDestination, { nullable: true, description: 'One peer\\'s destination, or null if this tenant has none by that name.' })\n async webhookDestination(\n @Arg('peer') peer: string,\n @Ctx() context: ResolverContext\n ): Promise<WebhookDestination | null> {\n return await getRepository(WebhookDestination).findOne({\n where: { domain: { id: context.state.domain.id }, peer }\n })\n }\n\n /**\n * Create or update one peer's destination.\n *\n * `patch` is merged field by field so a caller may send only what changed. A screen that\n * has to send every field in order to change one either reads the row first — and writes\n * back a value that moved in between — or blanks the fields it did not ask about.\n */\n @Directive('@privilege(category: \"webhook\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => WebhookDestination, { description: 'Create or update the destination for one peer. Fields absent from the patch keep their stored value.' })\n async saveWebhookDestination(\n @Arg('peer') peer: string,\n @Arg('patch', type => ScalarObject) patch: any,\n @Ctx() context: ResolverContext\n ): Promise<WebhookDestination> {\n const domainId = context.state.domain.id\n const tx: EntityManager = context.state.tx\n const repo = tx.getRepository(WebhookDestination)\n const existing = await repo.findOne({ where: { domain: { id: domainId }, peer } })\n\n /*\n * `params` merges by key for the same reason the patch does — a peer's options are added\n * to over time, and replacing the whole object drops the ones this screen version does\n * not know about.\n */\n const params = patch.params\n ? { ...((existing?.params as any) ?? {}), ...patch.params }\n : existing?.params\n\n const saved = await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n peer,\n endpoint: patch.endpoint ?? existing?.endpoint,\n description: patch.description ?? existing?.description,\n params,\n /*\n * `active` is a decision, so `false` has to be writable — `??` rather than `||`,\n * or turning a peer off would silently do nothing.\n */\n active: patch.active ?? existing?.active ?? true,\n creator: existing?.creator ?? (context.state.user as any),\n updater: context.state.user as any\n })\n )\n\n await tellListeners(tx, { domainId, peer, user: context.state.user, before: shapeOf(existing), after: shapeOf(saved) })\n return saved\n }\n\n /**\n * Stop pushing to a peer without forgetting it.\n *\n * Deliberately not a delete: the numbering and the queue are keyed by peer, and removing\n * the row would leave both orphaned. A destination that is turned off resumes where it\n * left off; one that is deleted starts over, and the peer reads that as a stream of\n * numbers it has already seen.\n */\n @Directive('@privilege(category: \"webhook\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'Turn pushing to one peer off or on. The queue and the numbering are kept either way.' })\n async setWebhookDestinationActive(\n @Arg('peer') peer: string,\n @Arg('active') active: boolean,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n const tx: EntityManager = context.state.tx\n const repo = tx.getRepository(WebhookDestination)\n const existing = await repo.findOne({ where: { domain: { id: domainId }, peer } })\n if (!existing) return false\n\n await repo.update({ domain: { id: domainId } as any, peer }, { active, updater: context.state.user as any })\n if (existing.active !== active) {\n await tellListeners(tx, { domainId, peer, user: context.state.user, before: shapeOf(existing), after: { ...shapeOf(existing), active } })\n }\n return true\n }\n}\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { EntityManager } from 'typeorm';
|
|
2
|
+
import { Domain } from '@things-factory/shell';
|
|
3
|
+
import { WebhookOutboxEvent } from './webhook-outbox-event';
|
|
4
|
+
/** The most one call hands back. A constant, so nothing is cut short without saying so. */
|
|
5
|
+
export declare const WEBHOOK_RESUME_MAX = 500;
|
|
6
|
+
export declare class WebhookOutboxEventList {
|
|
7
|
+
items: WebhookOutboxEvent[];
|
|
8
|
+
hasMore: boolean;
|
|
9
|
+
nextSince: number;
|
|
10
|
+
}
|
|
11
|
+
export declare enum WebhookSeqState {
|
|
12
|
+
Present = "present",
|
|
13
|
+
Lost = "lost",
|
|
14
|
+
Unissued = "unissued",
|
|
15
|
+
Unknown = "unknown"
|
|
16
|
+
}
|
|
17
|
+
export declare class WebhookSeqEntry {
|
|
18
|
+
seq: number;
|
|
19
|
+
state: WebhookSeqState;
|
|
20
|
+
at?: string;
|
|
21
|
+
record?: unknown;
|
|
22
|
+
}
|
|
23
|
+
export declare class WebhookSeqReport {
|
|
24
|
+
scope: string;
|
|
25
|
+
entries: WebhookSeqEntry[];
|
|
26
|
+
truncated?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare class WebhookOutboxCounts {
|
|
29
|
+
pending: number;
|
|
30
|
+
sent: number;
|
|
31
|
+
failed: number;
|
|
32
|
+
}
|
|
33
|
+
export declare class WebhookStanding {
|
|
34
|
+
peer: string;
|
|
35
|
+
scope: string;
|
|
36
|
+
counts: WebhookOutboxCounts;
|
|
37
|
+
oldestPendingAt?: string;
|
|
38
|
+
waitingMs?: number;
|
|
39
|
+
triedOften: number;
|
|
40
|
+
triedOftenOver: number;
|
|
41
|
+
hasDestination: boolean;
|
|
42
|
+
active?: boolean;
|
|
43
|
+
heldSince?: string;
|
|
44
|
+
heldCount?: number;
|
|
45
|
+
heldRetryAfterMs?: number;
|
|
46
|
+
}
|
|
47
|
+
export declare class WebhookRequeueReport {
|
|
48
|
+
requeued: number;
|
|
49
|
+
stillFailed: number;
|
|
50
|
+
}
|
|
51
|
+
/** Put refused facts of one stream back in the queue, keeping why on each. */
|
|
52
|
+
export declare function requeueFailedWebhookFacts(manager: EntityManager, domain: Domain, stream: {
|
|
53
|
+
peer: string;
|
|
54
|
+
scope: string;
|
|
55
|
+
}, request: {
|
|
56
|
+
reason: string;
|
|
57
|
+
matching?: string;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
requeued: number;
|
|
60
|
+
stillFailed: number;
|
|
61
|
+
}>;
|
|
62
|
+
export declare class WebhookOutboxResolver {
|
|
63
|
+
webhookOutboxEvents(peer: string, scope: string, since: number, context: ResolverContext, limit?: number): Promise<WebhookOutboxEventList>;
|
|
64
|
+
webhookSeqReport(peer: string, scope: string, from: number, to: number, context: ResolverContext): Promise<WebhookSeqReport>;
|
|
65
|
+
webhookStanding(peer: string, scope: string, context: ResolverContext): Promise<WebhookStanding>;
|
|
66
|
+
requeueFailedWebhookFacts(peer: string, scope: string, reason: string, context: ResolverContext, matching?: string): Promise<WebhookRequeueReport>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebhookOutboxResolver = exports.WebhookRequeueReport = exports.WebhookStanding = exports.WebhookOutboxCounts = exports.WebhookSeqReport = exports.WebhookSeqEntry = exports.WebhookSeqState = exports.WebhookOutboxEventList = exports.WEBHOOK_RESUME_MAX = void 0;
|
|
4
|
+
exports.requeueFailedWebhookFacts = requeueFailedWebhookFacts;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const type_graphql_1 = require("type-graphql");
|
|
7
|
+
const typeorm_1 = require("typeorm");
|
|
8
|
+
const shell_1 = require("@things-factory/shell");
|
|
9
|
+
const page_1 = require("../outbox/page");
|
|
10
|
+
const requeue_note_1 = require("../outbox/requeue-note");
|
|
11
|
+
const seq_report_1 = require("../outbox/seq-report");
|
|
12
|
+
const standing_1 = require("../outbox/standing");
|
|
13
|
+
const webhook_destination_1 = require("../webhook-destination/webhook-destination");
|
|
14
|
+
const webhook_sequence_1 = require("../webhook-sequence/webhook-sequence");
|
|
15
|
+
const webhook_outbox_event_1 = require("./webhook-outbox-event");
|
|
16
|
+
/*
|
|
17
|
+
* The doors on the queue itself, for every product that appends (ADR-0078 decision 1 · ADR-0110 adoption 2,
|
|
18
|
+
* step 4). They moved from operato-plant, where they answered for one product and one unnamed peer; here a
|
|
19
|
+
* stream is `(peer, scope)`.
|
|
20
|
+
*
|
|
21
|
+
* webhookOutboxEvents the receiver backfills what it missed — records after a number, in order
|
|
22
|
+
* webhookSeqReport what the producer knows about a run of numbers — present, lost, not issued yet
|
|
23
|
+
* webhookStanding how the stream stands — counts, the oldest waiting fact, and the destination:
|
|
24
|
+
* switched off by a person, or held by the receiver (decision 2 addendum)
|
|
25
|
+
* requeueFailedWebhookFacts put refused facts back, saying why
|
|
26
|
+
*
|
|
27
|
+
* The records leave as they were written — reasons for decisions and who decided travel in them — so all of
|
|
28
|
+
* these sit behind the same privilege as the destination itself.
|
|
29
|
+
*/
|
|
30
|
+
/** The most one call hands back. A constant, so nothing is cut short without saying so. */
|
|
31
|
+
exports.WEBHOOK_RESUME_MAX = 500;
|
|
32
|
+
let WebhookOutboxEventList = class WebhookOutboxEventList {
|
|
33
|
+
};
|
|
34
|
+
exports.WebhookOutboxEventList = WebhookOutboxEventList;
|
|
35
|
+
tslib_1.__decorate([
|
|
36
|
+
(0, type_graphql_1.Field)(type => [webhook_outbox_event_1.WebhookOutboxEvent], { description: 'Facts in sequence order.' }),
|
|
37
|
+
tslib_1.__metadata("design:type", Array)
|
|
38
|
+
], WebhookOutboxEventList.prototype, "items", void 0);
|
|
39
|
+
tslib_1.__decorate([
|
|
40
|
+
(0, type_graphql_1.Field)({ description: 'True when the limit truncated the result.' }),
|
|
41
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
42
|
+
], WebhookOutboxEventList.prototype, "hasMore", void 0);
|
|
43
|
+
tslib_1.__decorate([
|
|
44
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, {
|
|
45
|
+
description: 'The sequence to pass as `since` next time. An empty page returns the `since` asked, so a cursor never moves back.'
|
|
46
|
+
}),
|
|
47
|
+
tslib_1.__metadata("design:type", Number)
|
|
48
|
+
], WebhookOutboxEventList.prototype, "nextSince", void 0);
|
|
49
|
+
exports.WebhookOutboxEventList = WebhookOutboxEventList = tslib_1.__decorate([
|
|
50
|
+
(0, type_graphql_1.ObjectType)({ description: 'A page of queued facts for one stream, with whether more remain.' })
|
|
51
|
+
], WebhookOutboxEventList);
|
|
52
|
+
var WebhookSeqState;
|
|
53
|
+
(function (WebhookSeqState) {
|
|
54
|
+
WebhookSeqState["Present"] = "present";
|
|
55
|
+
WebhookSeqState["Lost"] = "lost";
|
|
56
|
+
WebhookSeqState["Unissued"] = "unissued";
|
|
57
|
+
WebhookSeqState["Unknown"] = "unknown";
|
|
58
|
+
})(WebhookSeqState || (exports.WebhookSeqState = WebhookSeqState = {}));
|
|
59
|
+
(0, type_graphql_1.registerEnumType)(WebhookSeqState, {
|
|
60
|
+
name: 'WebhookSeqState',
|
|
61
|
+
description: 'What the producer knows about one sequence number.'
|
|
62
|
+
});
|
|
63
|
+
let WebhookSeqEntry = class WebhookSeqEntry {
|
|
64
|
+
};
|
|
65
|
+
exports.WebhookSeqEntry = WebhookSeqEntry;
|
|
66
|
+
tslib_1.__decorate([
|
|
67
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'The sequence number.' }),
|
|
68
|
+
tslib_1.__metadata("design:type", Number)
|
|
69
|
+
], WebhookSeqEntry.prototype, "seq", void 0);
|
|
70
|
+
tslib_1.__decorate([
|
|
71
|
+
(0, type_graphql_1.Field)(type => WebhookSeqState, { description: 'present, lost, unissued or unknown.' }),
|
|
72
|
+
tslib_1.__metadata("design:type", String)
|
|
73
|
+
], WebhookSeqEntry.prototype, "state", void 0);
|
|
74
|
+
tslib_1.__decorate([
|
|
75
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'When it happened. Only when present.' }),
|
|
76
|
+
tslib_1.__metadata("design:type", String)
|
|
77
|
+
], WebhookSeqEntry.prototype, "at", void 0);
|
|
78
|
+
tslib_1.__decorate([
|
|
79
|
+
(0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'The fact itself. Only when present.' }),
|
|
80
|
+
tslib_1.__metadata("design:type", Object)
|
|
81
|
+
], WebhookSeqEntry.prototype, "record", void 0);
|
|
82
|
+
exports.WebhookSeqEntry = WebhookSeqEntry = tslib_1.__decorate([
|
|
83
|
+
(0, type_graphql_1.ObjectType)({ description: 'What the producer knows about one sequence number.' })
|
|
84
|
+
], WebhookSeqEntry);
|
|
85
|
+
let WebhookSeqReport = class WebhookSeqReport {
|
|
86
|
+
};
|
|
87
|
+
exports.WebhookSeqReport = WebhookSeqReport;
|
|
88
|
+
tslib_1.__decorate([
|
|
89
|
+
(0, type_graphql_1.Field)({ description: 'The scope this run is counted in.' }),
|
|
90
|
+
tslib_1.__metadata("design:type", String)
|
|
91
|
+
], WebhookSeqReport.prototype, "scope", void 0);
|
|
92
|
+
tslib_1.__decorate([
|
|
93
|
+
(0, type_graphql_1.Field)(type => [WebhookSeqEntry], { description: 'One entry per number, in order.' }),
|
|
94
|
+
tslib_1.__metadata("design:type", Array)
|
|
95
|
+
], WebhookSeqReport.prototype, "entries", void 0);
|
|
96
|
+
tslib_1.__decorate([
|
|
97
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'True when the limit cut the run short.' }),
|
|
98
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
99
|
+
], WebhookSeqReport.prototype, "truncated", void 0);
|
|
100
|
+
exports.WebhookSeqReport = WebhookSeqReport = tslib_1.__decorate([
|
|
101
|
+
(0, type_graphql_1.ObjectType)({ description: 'A run of sequence numbers in one stream and what the producer knows about each.' })
|
|
102
|
+
], WebhookSeqReport);
|
|
103
|
+
let WebhookOutboxCounts = class WebhookOutboxCounts {
|
|
104
|
+
};
|
|
105
|
+
exports.WebhookOutboxCounts = WebhookOutboxCounts;
|
|
106
|
+
tslib_1.__decorate([
|
|
107
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Waiting to be delivered.' }),
|
|
108
|
+
tslib_1.__metadata("design:type", Number)
|
|
109
|
+
], WebhookOutboxCounts.prototype, "pending", void 0);
|
|
110
|
+
tslib_1.__decorate([
|
|
111
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Taken by the peer.' }),
|
|
112
|
+
tslib_1.__metadata("design:type", Number)
|
|
113
|
+
], WebhookOutboxCounts.prototype, "sent", void 0);
|
|
114
|
+
tslib_1.__decorate([
|
|
115
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Refused for good.' }),
|
|
116
|
+
tslib_1.__metadata("design:type", Number)
|
|
117
|
+
], WebhookOutboxCounts.prototype, "failed", void 0);
|
|
118
|
+
exports.WebhookOutboxCounts = WebhookOutboxCounts = tslib_1.__decorate([
|
|
119
|
+
(0, type_graphql_1.ObjectType)({ description: 'How many facts stand in each state.' })
|
|
120
|
+
], WebhookOutboxCounts);
|
|
121
|
+
let WebhookStanding = class WebhookStanding {
|
|
122
|
+
};
|
|
123
|
+
exports.WebhookStanding = WebhookStanding;
|
|
124
|
+
tslib_1.__decorate([
|
|
125
|
+
(0, type_graphql_1.Field)({ description: 'The peer this reading is about.' }),
|
|
126
|
+
tslib_1.__metadata("design:type", String)
|
|
127
|
+
], WebhookStanding.prototype, "peer", void 0);
|
|
128
|
+
tslib_1.__decorate([
|
|
129
|
+
(0, type_graphql_1.Field)({ description: 'The scope this reading is about.' }),
|
|
130
|
+
tslib_1.__metadata("design:type", String)
|
|
131
|
+
], WebhookStanding.prototype, "scope", void 0);
|
|
132
|
+
tslib_1.__decorate([
|
|
133
|
+
(0, type_graphql_1.Field)(type => WebhookOutboxCounts, { description: 'Facts in each state.' }),
|
|
134
|
+
tslib_1.__metadata("design:type", WebhookOutboxCounts)
|
|
135
|
+
], WebhookStanding.prototype, "counts", void 0);
|
|
136
|
+
tslib_1.__decorate([
|
|
137
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'When the oldest waiting fact was written. Absent when nothing waits.' }),
|
|
138
|
+
tslib_1.__metadata("design:type", String)
|
|
139
|
+
], WebhookStanding.prototype, "oldestPendingAt", void 0);
|
|
140
|
+
tslib_1.__decorate([
|
|
141
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'How long that fact has waited, in ms. Absent when nothing waits — never 0.' }),
|
|
142
|
+
tslib_1.__metadata("design:type", Number)
|
|
143
|
+
], WebhookStanding.prototype, "waitingMs", void 0);
|
|
144
|
+
tslib_1.__decorate([
|
|
145
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Waiting facts tried more than `triedOftenOver` times.' }),
|
|
146
|
+
tslib_1.__metadata("design:type", Number)
|
|
147
|
+
], WebhookStanding.prototype, "triedOften", void 0);
|
|
148
|
+
tslib_1.__decorate([
|
|
149
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'The threshold the count above uses.' }),
|
|
150
|
+
tslib_1.__metadata("design:type", Number)
|
|
151
|
+
], WebhookStanding.prototype, "triedOftenOver", void 0);
|
|
152
|
+
tslib_1.__decorate([
|
|
153
|
+
(0, type_graphql_1.Field)({ description: 'False when there is no destination for this peer at all.' }),
|
|
154
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
155
|
+
], WebhookStanding.prototype, "hasDestination", void 0);
|
|
156
|
+
tslib_1.__decorate([
|
|
157
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'False when a person switched the destination off. Facts pile up until it is on.' }),
|
|
158
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
159
|
+
], WebhookStanding.prototype, "active", void 0);
|
|
160
|
+
tslib_1.__decorate([
|
|
161
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Since when the receiver has been holding us back (asking to wait). Absent when it is not.' }),
|
|
162
|
+
tslib_1.__metadata("design:type", String)
|
|
163
|
+
], WebhookStanding.prototype, "heldSince", void 0);
|
|
164
|
+
tslib_1.__decorate([
|
|
165
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'How many times in a row the receiver asked to wait.' }),
|
|
166
|
+
tslib_1.__metadata("design:type", Number)
|
|
167
|
+
], WebhookStanding.prototype, "heldCount", void 0);
|
|
168
|
+
tslib_1.__decorate([
|
|
169
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'The last wait the receiver asked for, in ms.' }),
|
|
170
|
+
tslib_1.__metadata("design:type", Number)
|
|
171
|
+
], WebhookStanding.prototype, "heldRetryAfterMs", void 0);
|
|
172
|
+
exports.WebhookStanding = WebhookStanding = tslib_1.__decorate([
|
|
173
|
+
(0, type_graphql_1.ObjectType)({ description: 'How one stream stands right now.' })
|
|
174
|
+
], WebhookStanding);
|
|
175
|
+
let WebhookRequeueReport = class WebhookRequeueReport {
|
|
176
|
+
};
|
|
177
|
+
exports.WebhookRequeueReport = WebhookRequeueReport;
|
|
178
|
+
tslib_1.__decorate([
|
|
179
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'How many refused facts went back to the queue.' }),
|
|
180
|
+
tslib_1.__metadata("design:type", Number)
|
|
181
|
+
], WebhookRequeueReport.prototype, "requeued", void 0);
|
|
182
|
+
tslib_1.__decorate([
|
|
183
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'How many refused facts this stream still holds.' }),
|
|
184
|
+
tslib_1.__metadata("design:type", Number)
|
|
185
|
+
], WebhookRequeueReport.prototype, "stillFailed", void 0);
|
|
186
|
+
exports.WebhookRequeueReport = WebhookRequeueReport = tslib_1.__decorate([
|
|
187
|
+
(0, type_graphql_1.ObjectType)({ description: 'What a re-drive of refused facts did.' })
|
|
188
|
+
], WebhookRequeueReport);
|
|
189
|
+
/** Put refused facts of one stream back in the queue, keeping why on each. */
|
|
190
|
+
async function requeueFailedWebhookFacts(manager, domain, stream, request) {
|
|
191
|
+
const reason = request.reason?.trim();
|
|
192
|
+
if (!reason) {
|
|
193
|
+
throw new shell_1.Refusal('outbox-resend-reason-missing', {}, 'enter a reason for resending.');
|
|
194
|
+
}
|
|
195
|
+
const matching = request.matching?.trim();
|
|
196
|
+
const where = { domain: { id: domain.id }, peer: stream.peer, scope: stream.scope };
|
|
197
|
+
const refused = await manager.find(webhook_outbox_event_1.WebhookOutboxEvent, {
|
|
198
|
+
where: { ...where, status: webhook_outbox_event_1.WebhookOutboxStatus.Failed, ...(matching ? { lastError: (0, typeorm_1.Like)(`%${matching}%`) } : {}) }
|
|
199
|
+
});
|
|
200
|
+
for (const event of refused) {
|
|
201
|
+
await manager.update(webhook_outbox_event_1.WebhookOutboxEvent, { id: (0, typeorm_1.In)([event.id]) }, { status: webhook_outbox_event_1.WebhookOutboxStatus.Pending, sentAt: null, lastError: (0, requeue_note_1.requeueNote)(reason, event.lastError) });
|
|
202
|
+
}
|
|
203
|
+
const stillFailed = await manager.count(webhook_outbox_event_1.WebhookOutboxEvent, { where: { ...where, status: webhook_outbox_event_1.WebhookOutboxStatus.Failed } });
|
|
204
|
+
return { requeued: refused.length, stillFailed };
|
|
205
|
+
}
|
|
206
|
+
let WebhookOutboxResolver = class WebhookOutboxResolver {
|
|
207
|
+
async webhookOutboxEvents(peer, scope, since, context, limit) {
|
|
208
|
+
const { domain } = context.state;
|
|
209
|
+
const take = Math.min(limit && limit > 0 ? limit : exports.WEBHOOK_RESUME_MAX, exports.WEBHOOK_RESUME_MAX);
|
|
210
|
+
/* One more than the limit — that one is the evidence for `hasMore`. */
|
|
211
|
+
const rows = await (0, shell_1.getRepository)(webhook_outbox_event_1.WebhookOutboxEvent).find({
|
|
212
|
+
where: { domain: { id: domain.id }, peer, scope, seq: (0, typeorm_1.MoreThan)(since) },
|
|
213
|
+
order: { seq: 'ASC' },
|
|
214
|
+
take: take + 1
|
|
215
|
+
});
|
|
216
|
+
const { items, hasMore } = (0, page_1.pageOf)(rows, take);
|
|
217
|
+
const last = items[items.length - 1];
|
|
218
|
+
return { items, hasMore, nextSince: last ? Number(last.seq) : since };
|
|
219
|
+
}
|
|
220
|
+
async webhookSeqReport(peer, scope, from, to, context) {
|
|
221
|
+
const { domain } = context.state;
|
|
222
|
+
/* Narrow to the cap before reading — asking 1 to a million must not load a million rows. */
|
|
223
|
+
const start = Math.max(1, Math.floor(from));
|
|
224
|
+
const end = Math.min(Math.floor(to), start + seq_report_1.SEQ_REPORT_MAX - 1);
|
|
225
|
+
const rows = end < start
|
|
226
|
+
? []
|
|
227
|
+
: await (0, shell_1.getRepository)(webhook_outbox_event_1.WebhookOutboxEvent).find({
|
|
228
|
+
where: { domain: { id: domain.id }, peer, scope, seq: (0, typeorm_1.Between)(start, end) },
|
|
229
|
+
order: { seq: 'ASC' }
|
|
230
|
+
});
|
|
231
|
+
/* No counter is `undefined`, not 0 — 0 would call every number not yet issued "lost". */
|
|
232
|
+
const counter = await (0, shell_1.getRepository)(webhook_sequence_1.WebhookSequence).findOne({ where: { domain: { id: domain.id }, peer, scope } });
|
|
233
|
+
const lastSeq = counter ? Number(counter.lastSeq) : undefined;
|
|
234
|
+
const report = (0, seq_report_1.seqReport)({
|
|
235
|
+
scope,
|
|
236
|
+
from,
|
|
237
|
+
to,
|
|
238
|
+
lastSeq: Number.isFinite(lastSeq) ? lastSeq : undefined,
|
|
239
|
+
known: rows.map(row => ({ seq: Number(row.seq), at: (row.eventTime ?? row.recordTime)?.toISOString(), record: row.payload }))
|
|
240
|
+
});
|
|
241
|
+
return { ...report, entries: report.entries.map(one => ({ ...one, state: one.state })) };
|
|
242
|
+
}
|
|
243
|
+
async webhookStanding(peer, scope, context) {
|
|
244
|
+
const { domain } = context.state;
|
|
245
|
+
const repository = (0, shell_1.getRepository)(webhook_outbox_event_1.WebhookOutboxEvent);
|
|
246
|
+
const where = { domain: { id: domain.id }, peer, scope };
|
|
247
|
+
const [pending, sent, failed, triedOften] = await Promise.all([
|
|
248
|
+
repository.count({ where: { ...where, status: webhook_outbox_event_1.WebhookOutboxStatus.Pending } }),
|
|
249
|
+
repository.count({ where: { ...where, status: webhook_outbox_event_1.WebhookOutboxStatus.Sent } }),
|
|
250
|
+
repository.count({ where: { ...where, status: webhook_outbox_event_1.WebhookOutboxStatus.Failed } }),
|
|
251
|
+
repository.count({ where: { ...where, status: webhook_outbox_event_1.WebhookOutboxStatus.Pending, attempts: (0, typeorm_1.MoreThan)(standing_1.ATTEMPTS_WORTH_SAYING) } })
|
|
252
|
+
]);
|
|
253
|
+
const oldest = await repository.findOne({
|
|
254
|
+
where: { ...where, status: webhook_outbox_event_1.WebhookOutboxStatus.Pending },
|
|
255
|
+
order: { createdAt: 'ASC' },
|
|
256
|
+
select: { id: true, createdAt: true }
|
|
257
|
+
});
|
|
258
|
+
const standing = (0, standing_1.outboxStandingOf)({
|
|
259
|
+
channel: scope,
|
|
260
|
+
counts: { pending, sent, failed },
|
|
261
|
+
oldestPendingAt: oldest?.createdAt ?? null,
|
|
262
|
+
triedOften,
|
|
263
|
+
now: Date.now()
|
|
264
|
+
});
|
|
265
|
+
const destination = await (0, shell_1.getRepository)(webhook_destination_1.WebhookDestination).findOne({ where: { domain: { id: domain.id }, peer } });
|
|
266
|
+
return {
|
|
267
|
+
peer,
|
|
268
|
+
scope,
|
|
269
|
+
counts: standing.counts,
|
|
270
|
+
oldestPendingAt: standing.oldestPendingAt ?? undefined,
|
|
271
|
+
waitingMs: standing.waitingMs ?? undefined,
|
|
272
|
+
triedOften: standing.triedOften,
|
|
273
|
+
triedOftenOver: standing.triedOftenOver,
|
|
274
|
+
hasDestination: !!destination,
|
|
275
|
+
active: destination?.active,
|
|
276
|
+
heldSince: destination?.heldSince ? new Date(destination.heldSince).toISOString() : undefined,
|
|
277
|
+
heldCount: destination?.heldCount ?? undefined,
|
|
278
|
+
heldRetryAfterMs: destination?.heldRetryAfterMs ?? undefined
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
async requeueFailedWebhookFacts(peer, scope, reason, context, matching) {
|
|
282
|
+
const { domain, tx } = context.state;
|
|
283
|
+
return await requeueFailedWebhookFacts(tx, domain, { peer, scope }, { reason, matching });
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
exports.WebhookOutboxResolver = WebhookOutboxResolver;
|
|
287
|
+
tslib_1.__decorate([
|
|
288
|
+
(0, type_graphql_1.Directive)('@privilege(category: "webhook", privilege: "query", domainOwnerGranted: true, superUserGranted: true)'),
|
|
289
|
+
(0, type_graphql_1.Query)(returns => WebhookOutboxEventList, { description: 'Queued facts of one stream after the given sequence, in order — for a receiver backfilling what it missed.' }),
|
|
290
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('peer', { description: 'The peer the stream is queued for.' })),
|
|
291
|
+
tslib_1.__param(1, (0, type_graphql_1.Arg)('scope', { description: 'The scope the sequence is counted in.' })),
|
|
292
|
+
tslib_1.__param(2, (0, type_graphql_1.Arg)('since', type => type_graphql_1.Int, { description: 'Return facts after this sequence.' })),
|
|
293
|
+
tslib_1.__param(3, (0, type_graphql_1.Ctx)()),
|
|
294
|
+
tslib_1.__param(4, (0, type_graphql_1.Arg)('limit', type => type_graphql_1.Int, { nullable: true, description: `At most this many; capped at ${exports.WEBHOOK_RESUME_MAX}.` })),
|
|
295
|
+
tslib_1.__metadata("design:type", Function),
|
|
296
|
+
tslib_1.__metadata("design:paramtypes", [String, String, Number, Object, Number]),
|
|
297
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
298
|
+
], WebhookOutboxResolver.prototype, "webhookOutboxEvents", null);
|
|
299
|
+
tslib_1.__decorate([
|
|
300
|
+
(0, type_graphql_1.Directive)('@privilege(category: "webhook", privilege: "query", domainOwnerGranted: true, superUserGranted: true)'),
|
|
301
|
+
(0, type_graphql_1.Query)(returns => WebhookSeqReport, { description: 'What the producer knows about a run of sequence numbers in one stream — present, lost or not issued yet.' }),
|
|
302
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('peer', { description: 'The peer the stream is queued for.' })),
|
|
303
|
+
tslib_1.__param(1, (0, type_graphql_1.Arg)('scope', { description: 'The scope the sequence is counted in.' })),
|
|
304
|
+
tslib_1.__param(2, (0, type_graphql_1.Arg)('from', type => type_graphql_1.Int, { description: 'First number of the run.' })),
|
|
305
|
+
tslib_1.__param(3, (0, type_graphql_1.Arg)('to', type => type_graphql_1.Int, { description: 'Last number of the run.' })),
|
|
306
|
+
tslib_1.__param(4, (0, type_graphql_1.Ctx)()),
|
|
307
|
+
tslib_1.__metadata("design:type", Function),
|
|
308
|
+
tslib_1.__metadata("design:paramtypes", [String, String, Number, Number, Object]),
|
|
309
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
310
|
+
], WebhookOutboxResolver.prototype, "webhookSeqReport", null);
|
|
311
|
+
tslib_1.__decorate([
|
|
312
|
+
(0, type_graphql_1.Directive)('@privilege(category: "webhook", privilege: "query", domainOwnerGranted: true, superUserGranted: true)'),
|
|
313
|
+
(0, type_graphql_1.Query)(returns => WebhookStanding, { description: 'How one stream stands: the oldest waiting fact, the counts, and whether the destination is off or held by the receiver.' }),
|
|
314
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('peer', { description: 'The peer the stream is queued for.' })),
|
|
315
|
+
tslib_1.__param(1, (0, type_graphql_1.Arg)('scope', { description: 'The scope the sequence is counted in.' })),
|
|
316
|
+
tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
|
|
317
|
+
tslib_1.__metadata("design:type", Function),
|
|
318
|
+
tslib_1.__metadata("design:paramtypes", [String, String, Object]),
|
|
319
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
320
|
+
], WebhookOutboxResolver.prototype, "webhookStanding", null);
|
|
321
|
+
tslib_1.__decorate([
|
|
322
|
+
(0, type_graphql_1.Directive)('@privilege(category: "webhook", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
323
|
+
(0, type_graphql_1.Directive)('@transaction'),
|
|
324
|
+
(0, type_graphql_1.Mutation)(returns => WebhookRequeueReport, { description: 'Put the refused facts of one stream back in the queue, saying why.' }),
|
|
325
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('peer', { description: 'The peer the stream is queued for.' })),
|
|
326
|
+
tslib_1.__param(1, (0, type_graphql_1.Arg)('scope', { description: 'The scope the sequence is counted in.' })),
|
|
327
|
+
tslib_1.__param(2, (0, type_graphql_1.Arg)('reason', { description: 'What changed since the refusal. Kept on each fact.' })),
|
|
328
|
+
tslib_1.__param(3, (0, type_graphql_1.Ctx)()),
|
|
329
|
+
tslib_1.__param(4, (0, type_graphql_1.Arg)('matching', { nullable: true, description: 'Only facts whose recorded refusal contains this.' })),
|
|
330
|
+
tslib_1.__metadata("design:type", Function),
|
|
331
|
+
tslib_1.__metadata("design:paramtypes", [String, String, String, Object, String]),
|
|
332
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
333
|
+
], WebhookOutboxResolver.prototype, "requeueFailedWebhookFacts", null);
|
|
334
|
+
exports.WebhookOutboxResolver = WebhookOutboxResolver = tslib_1.__decorate([
|
|
335
|
+
(0, type_graphql_1.Resolver)(webhook_outbox_event_1.WebhookOutboxEvent),
|
|
336
|
+
(0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
|
|
337
|
+
], WebhookOutboxResolver);
|
|
338
|
+
//# sourceMappingURL=webhook-outbox-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-outbox-resolver.js","sourceRoot":"","sources":["../../../server/service/webhook-outbox-event/webhook-outbox-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Field, Int, Mutation, ObjectType, Query, Resolver, registerEnumType, UseMiddleware } from 'type-graphql'\nimport { Between, EntityManager, In, Like, MoreThan } from 'typeorm'\n\nimport { Domain, getRepository, Refusal, ScalarObject, SpeakRefusals } from '@things-factory/shell'\n\nimport { pageOf } from '../outbox/page'\nimport { requeueNote } from '../outbox/requeue-note'\nimport { SEQ_REPORT_MAX, seqReport } from '../outbox/seq-report'\nimport { ATTEMPTS_WORTH_SAYING, outboxStandingOf } from '../outbox/standing'\nimport { WebhookDestination } from '../webhook-destination/webhook-destination'\nimport { WebhookSequence } from '../webhook-sequence/webhook-sequence'\nimport { WebhookOutboxEvent, WebhookOutboxStatus } from './webhook-outbox-event'\n\n/*\n * The doors on the queue itself, for every product that appends (ADR-0078 decision 1 · ADR-0110 adoption 2,\n * step 4). They moved from operato-plant, where they answered for one product and one unnamed peer; here a\n * stream is `(peer, scope)`.\n *\n * webhookOutboxEvents the receiver backfills what it missed — records after a number, in order\n * webhookSeqReport what the producer knows about a run of numbers — present, lost, not issued yet\n * webhookStanding how the stream stands — counts, the oldest waiting fact, and the destination:\n * switched off by a person, or held by the receiver (decision 2 addendum)\n * requeueFailedWebhookFacts put refused facts back, saying why\n *\n * The records leave as they were written — reasons for decisions and who decided travel in them — so all of\n * these sit behind the same privilege as the destination itself.\n */\n\n/** The most one call hands back. A constant, so nothing is cut short without saying so. */\nexport const WEBHOOK_RESUME_MAX = 500\n\n@ObjectType({ description: 'A page of queued facts for one stream, with whether more remain.' })\nexport class WebhookOutboxEventList {\n @Field(type => [WebhookOutboxEvent], { description: 'Facts in sequence order.' })\n items: WebhookOutboxEvent[]\n\n @Field({ description: 'True when the limit truncated the result.' })\n hasMore: boolean\n\n @Field(type => Int, {\n description: 'The sequence to pass as `since` next time. An empty page returns the `since` asked, so a cursor never moves back.'\n })\n nextSince: number\n}\n\nexport enum WebhookSeqState {\n Present = 'present',\n Lost = 'lost',\n Unissued = 'unissued',\n Unknown = 'unknown'\n}\n\nregisterEnumType(WebhookSeqState, {\n name: 'WebhookSeqState',\n description: 'What the producer knows about one sequence number.'\n})\n\n@ObjectType({ description: 'What the producer knows about one sequence number.' })\nexport class WebhookSeqEntry {\n @Field(type => Int, { description: 'The sequence number.' })\n seq: number\n\n @Field(type => WebhookSeqState, { description: 'present, lost, unissued or unknown.' })\n state: WebhookSeqState\n\n @Field({ nullable: true, description: 'When it happened. Only when present.' })\n at?: string\n\n @Field(type => ScalarObject, { nullable: true, description: 'The fact itself. Only when present.' })\n record?: unknown\n}\n\n@ObjectType({ description: 'A run of sequence numbers in one stream and what the producer knows about each.' })\nexport class WebhookSeqReport {\n @Field({ description: 'The scope this run is counted in.' })\n scope: string\n\n @Field(type => [WebhookSeqEntry], { description: 'One entry per number, in order.' })\n entries: WebhookSeqEntry[]\n\n @Field({ nullable: true, description: 'True when the limit cut the run short.' })\n truncated?: boolean\n}\n\n@ObjectType({ description: 'How many facts stand in each state.' })\nexport class WebhookOutboxCounts {\n @Field(type => Int, { description: 'Waiting to be delivered.' })\n pending: number\n\n @Field(type => Int, { description: 'Taken by the peer.' })\n sent: number\n\n @Field(type => Int, { description: 'Refused for good.' })\n failed: number\n}\n\n@ObjectType({ description: 'How one stream stands right now.' })\nexport class WebhookStanding {\n @Field({ description: 'The peer this reading is about.' })\n peer: string\n\n @Field({ description: 'The scope this reading is about.' })\n scope: string\n\n @Field(type => WebhookOutboxCounts, { description: 'Facts in each state.' })\n counts: WebhookOutboxCounts\n\n @Field({ nullable: true, description: 'When the oldest waiting fact was written. Absent when nothing waits.' })\n oldestPendingAt?: string\n\n @Field(type => Int, { nullable: true, description: 'How long that fact has waited, in ms. Absent when nothing waits — never 0.' })\n waitingMs?: number\n\n @Field(type => Int, { description: 'Waiting facts tried more than `triedOftenOver` times.' })\n triedOften: number\n\n @Field(type => Int, { description: 'The threshold the count above uses.' })\n triedOftenOver: number\n\n @Field({ description: 'False when there is no destination for this peer at all.' })\n hasDestination: boolean\n\n @Field({ nullable: true, description: 'False when a person switched the destination off. Facts pile up until it is on.' })\n active?: boolean\n\n @Field({ nullable: true, description: 'Since when the receiver has been holding us back (asking to wait). Absent when it is not.' })\n heldSince?: string\n\n @Field(type => Int, { nullable: true, description: 'How many times in a row the receiver asked to wait.' })\n heldCount?: number\n\n @Field(type => Int, { nullable: true, description: 'The last wait the receiver asked for, in ms.' })\n heldRetryAfterMs?: number\n}\n\n@ObjectType({ description: 'What a re-drive of refused facts did.' })\nexport class WebhookRequeueReport {\n @Field(type => Int, { description: 'How many refused facts went back to the queue.' })\n requeued: number\n\n @Field(type => Int, { description: 'How many refused facts this stream still holds.' })\n stillFailed: number\n}\n\n/** Put refused facts of one stream back in the queue, keeping why on each. */\nexport async function requeueFailedWebhookFacts(\n manager: EntityManager,\n domain: Domain,\n stream: { peer: string; scope: string },\n request: { reason: string; matching?: string }\n): Promise<{ requeued: number; stillFailed: number }> {\n const reason = request.reason?.trim()\n if (!reason) {\n throw new Refusal('outbox-resend-reason-missing', {}, 'enter a reason for resending.')\n }\n\n const matching = request.matching?.trim()\n const where = { domain: { id: domain.id }, peer: stream.peer, scope: stream.scope }\n\n const refused = await manager.find(WebhookOutboxEvent, {\n where: { ...where, status: WebhookOutboxStatus.Failed, ...(matching ? { lastError: Like(`%${matching}%`) } : {}) }\n })\n\n for (const event of refused) {\n await manager.update(\n WebhookOutboxEvent,\n { id: In([event.id]) },\n { status: WebhookOutboxStatus.Pending, sentAt: null, lastError: requeueNote(reason, event.lastError) }\n )\n }\n\n const stillFailed = await manager.count(WebhookOutboxEvent, { where: { ...where, status: WebhookOutboxStatus.Failed } })\n return { requeued: refused.length, stillFailed }\n}\n\n@Resolver(WebhookOutboxEvent)\n@UseMiddleware(SpeakRefusals)\nexport class WebhookOutboxResolver {\n @Directive('@privilege(category: \"webhook\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => WebhookOutboxEventList, { description: 'Queued facts of one stream after the given sequence, in order — for a receiver backfilling what it missed.' })\n async webhookOutboxEvents(\n @Arg('peer', { description: 'The peer the stream is queued for.' }) peer: string,\n @Arg('scope', { description: 'The scope the sequence is counted in.' }) scope: string,\n @Arg('since', type => Int, { description: 'Return facts after this sequence.' }) since: number,\n @Ctx() context: ResolverContext,\n @Arg('limit', type => Int, { nullable: true, description: `At most this many; capped at ${WEBHOOK_RESUME_MAX}.` }) limit?: number\n ): Promise<WebhookOutboxEventList> {\n const { domain } = context.state\n const take = Math.min(limit && limit > 0 ? limit : WEBHOOK_RESUME_MAX, WEBHOOK_RESUME_MAX)\n\n /* One more than the limit — that one is the evidence for `hasMore`. */\n const rows = await getRepository(WebhookOutboxEvent).find({\n where: { domain: { id: domain.id }, peer, scope, seq: MoreThan(since) },\n order: { seq: 'ASC' },\n take: take + 1\n })\n\n const { items, hasMore } = pageOf(rows, take)\n const last = items[items.length - 1]\n return { items, hasMore, nextSince: last ? Number(last.seq) : since }\n }\n\n @Directive('@privilege(category: \"webhook\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => WebhookSeqReport, { description: 'What the producer knows about a run of sequence numbers in one stream — present, lost or not issued yet.' })\n async webhookSeqReport(\n @Arg('peer', { description: 'The peer the stream is queued for.' }) peer: string,\n @Arg('scope', { description: 'The scope the sequence is counted in.' }) scope: string,\n @Arg('from', type => Int, { description: 'First number of the run.' }) from: number,\n @Arg('to', type => Int, { description: 'Last number of the run.' }) to: number,\n @Ctx() context: ResolverContext\n ): Promise<WebhookSeqReport> {\n const { domain } = context.state\n\n /* Narrow to the cap before reading — asking 1 to a million must not load a million rows. */\n const start = Math.max(1, Math.floor(from))\n const end = Math.min(Math.floor(to), start + SEQ_REPORT_MAX - 1)\n\n const rows =\n end < start\n ? []\n : await getRepository(WebhookOutboxEvent).find({\n where: { domain: { id: domain.id }, peer, scope, seq: Between(start, end) },\n order: { seq: 'ASC' }\n })\n\n /* No counter is `undefined`, not 0 — 0 would call every number not yet issued \"lost\". */\n const counter = await getRepository(WebhookSequence).findOne({ where: { domain: { id: domain.id }, peer, scope } })\n const lastSeq = counter ? Number(counter.lastSeq) : undefined\n\n const report = seqReport({\n scope,\n from,\n to,\n lastSeq: Number.isFinite(lastSeq) ? lastSeq : undefined,\n known: rows.map(row => ({ seq: Number(row.seq), at: (row.eventTime ?? row.recordTime)?.toISOString(), record: row.payload }))\n })\n\n return { ...report, entries: report.entries.map(one => ({ ...one, state: one.state as WebhookSeqState })) }\n }\n\n @Directive('@privilege(category: \"webhook\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => WebhookStanding, { description: 'How one stream stands: the oldest waiting fact, the counts, and whether the destination is off or held by the receiver.' })\n async webhookStanding(\n @Arg('peer', { description: 'The peer the stream is queued for.' }) peer: string,\n @Arg('scope', { description: 'The scope the sequence is counted in.' }) scope: string,\n @Ctx() context: ResolverContext\n ): Promise<WebhookStanding> {\n const { domain } = context.state\n const repository = getRepository(WebhookOutboxEvent)\n const where = { domain: { id: domain.id }, peer, scope }\n\n const [pending, sent, failed, triedOften] = await Promise.all([\n repository.count({ where: { ...where, status: WebhookOutboxStatus.Pending } }),\n repository.count({ where: { ...where, status: WebhookOutboxStatus.Sent } }),\n repository.count({ where: { ...where, status: WebhookOutboxStatus.Failed } }),\n repository.count({ where: { ...where, status: WebhookOutboxStatus.Pending, attempts: MoreThan(ATTEMPTS_WORTH_SAYING) } })\n ])\n\n const oldest = await repository.findOne({\n where: { ...where, status: WebhookOutboxStatus.Pending },\n order: { createdAt: 'ASC' },\n select: { id: true, createdAt: true }\n })\n\n const standing = outboxStandingOf({\n channel: scope,\n counts: { pending, sent, failed },\n oldestPendingAt: oldest?.createdAt ?? null,\n triedOften,\n now: Date.now()\n })\n\n const destination = await getRepository(WebhookDestination).findOne({ where: { domain: { id: domain.id }, peer } })\n\n return {\n peer,\n scope,\n counts: standing.counts,\n oldestPendingAt: standing.oldestPendingAt ?? undefined,\n waitingMs: standing.waitingMs ?? undefined,\n triedOften: standing.triedOften,\n triedOftenOver: standing.triedOftenOver,\n hasDestination: !!destination,\n active: destination?.active,\n heldSince: destination?.heldSince ? new Date(destination.heldSince).toISOString() : undefined,\n heldCount: destination?.heldCount ?? undefined,\n heldRetryAfterMs: destination?.heldRetryAfterMs ?? undefined\n }\n }\n\n @Directive('@privilege(category: \"webhook\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => WebhookRequeueReport, { description: 'Put the refused facts of one stream back in the queue, saying why.' })\n async requeueFailedWebhookFacts(\n @Arg('peer', { description: 'The peer the stream is queued for.' }) peer: string,\n @Arg('scope', { description: 'The scope the sequence is counted in.' }) scope: string,\n @Arg('reason', { description: 'What changed since the refusal. Kept on each fact.' }) reason: string,\n @Ctx() context: ResolverContext,\n @Arg('matching', { nullable: true, description: 'Only facts whose recorded refusal contains this.' }) matching?: string\n ): Promise<WebhookRequeueReport> {\n const { domain, tx } = context.state\n return await requeueFailedWebhookFacts(tx, domain, { peer, scope }, { reason, matching })\n }\n}\n"]}
|