@voyantjs/distribution 0.20.0 → 0.21.1
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/channel-push/admin-routes.d.ts +31 -0
- package/dist/channel-push/admin-routes.d.ts.map +1 -0
- package/dist/channel-push/admin-routes.js +165 -0
- package/dist/channel-push/availability-push.d.ts +76 -0
- package/dist/channel-push/availability-push.d.ts.map +1 -0
- package/dist/channel-push/availability-push.js +238 -0
- package/dist/channel-push/booking-push.d.ts +114 -0
- package/dist/channel-push/booking-push.d.ts.map +1 -0
- package/dist/channel-push/booking-push.js +503 -0
- package/dist/channel-push/content-push.d.ts +60 -0
- package/dist/channel-push/content-push.d.ts.map +1 -0
- package/dist/channel-push/content-push.js +256 -0
- package/dist/channel-push/index.d.ts +15 -0
- package/dist/channel-push/index.d.ts.map +1 -0
- package/dist/channel-push/index.js +18 -0
- package/dist/channel-push/plugin.d.ts +18 -0
- package/dist/channel-push/plugin.d.ts.map +1 -0
- package/dist/channel-push/plugin.js +21 -0
- package/dist/channel-push/reconciler.d.ts +85 -0
- package/dist/channel-push/reconciler.d.ts.map +1 -0
- package/dist/channel-push/reconciler.js +175 -0
- package/dist/channel-push/subscriber.d.ts +40 -0
- package/dist/channel-push/subscriber.d.ts.map +1 -0
- package/dist/channel-push/subscriber.js +174 -0
- package/dist/channel-push/types.d.ts +43 -0
- package/dist/channel-push/types.d.ts.map +1 -0
- package/dist/channel-push/types.js +32 -0
- package/dist/channel-push/workflows.d.ts +56 -0
- package/dist/channel-push/workflows.d.ts.map +1 -0
- package/dist/channel-push/workflows.js +100 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/rate-limit.d.ts +69 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +135 -0
- package/dist/routes.d.ts +170 -10
- package/dist/routes.d.ts.map +1 -1
- package/dist/schema-core.d.ts +417 -1
- package/dist/schema-core.d.ts.map +1 -1
- package/dist/schema-core.js +98 -1
- package/dist/schema-push-intents.d.ts +387 -0
- package/dist/schema-push-intents.d.ts.map +1 -0
- package/dist/schema-push-intents.js +77 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -0
- package/dist/service.d.ts +103 -7
- package/dist/service.d.ts.map +1 -1
- package/dist/validation.d.ts +5 -5
- package/dist/webhook-deliveries.d.ts +86 -0
- package/dist/webhook-deliveries.d.ts.map +1 -0
- package/dist/webhook-deliveries.js +293 -0
- package/package.json +16 -8
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel-push EventBus subscribers.
|
|
3
|
+
*
|
|
4
|
+
* The `booking.confirmed` subscriber writes durable intent rows to
|
|
5
|
+
* `channel_booking_links` and returns immediately — per §4.5 the
|
|
6
|
+
* subscriber MUST NOT do HTTP work because the EventBus is in-process
|
|
7
|
+
* and sequential. The intent rows are drained by `processBookingPush`
|
|
8
|
+
* (called inline in v1, or by the durable workflow in production).
|
|
9
|
+
*
|
|
10
|
+
* Per docs/architecture/channel-push-architecture.md §4.1.
|
|
11
|
+
*/
|
|
12
|
+
import { processAvailabilityPushIntents, resolveAllotmentTargetsForSlot, upsertAvailabilityIntent, } from "./availability-push.js";
|
|
13
|
+
import { processBookingPush, resolveBookingPushTargets, upsertPendingBookingLinks, } from "./booking-push.js";
|
|
14
|
+
import { processContentPushIntents, resolveContentPushTargets, upsertContentIntent, } from "./content-push.js";
|
|
15
|
+
import { defaultLogger, getChannelPushDeps, getChannelPushDepsOrThrow, } from "./types.js";
|
|
16
|
+
function coerceBookingConfirmed(envelope) {
|
|
17
|
+
const data = envelope.data;
|
|
18
|
+
if (data == null || typeof data !== "object")
|
|
19
|
+
return null;
|
|
20
|
+
const maybe = data;
|
|
21
|
+
if (typeof maybe.bookingId !== "string")
|
|
22
|
+
return null;
|
|
23
|
+
return maybe;
|
|
24
|
+
}
|
|
25
|
+
function coerceSlotChanged(envelope) {
|
|
26
|
+
const data = envelope.data;
|
|
27
|
+
if (data == null || typeof data !== "object")
|
|
28
|
+
return null;
|
|
29
|
+
const maybe = data;
|
|
30
|
+
if (typeof maybe.slotId !== "string" || typeof maybe.productId !== "string")
|
|
31
|
+
return null;
|
|
32
|
+
return maybe;
|
|
33
|
+
}
|
|
34
|
+
function coerceContentChanged(envelope) {
|
|
35
|
+
const data = envelope.data;
|
|
36
|
+
if (data == null || typeof data !== "object")
|
|
37
|
+
return null;
|
|
38
|
+
const maybe = data;
|
|
39
|
+
if (typeof maybe.id !== "string")
|
|
40
|
+
return null;
|
|
41
|
+
return maybe;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Construct the channel-push subscriber bundle. Templates pass these to
|
|
45
|
+
* `registerPlugins({ subscribers })` or attach them to an EventBus
|
|
46
|
+
* directly via `eventBus.subscribe`.
|
|
47
|
+
*/
|
|
48
|
+
export function createChannelPushSubscribers(options = {}) {
|
|
49
|
+
const drainInline = options.drainInline ?? true;
|
|
50
|
+
const handler = async (envelope) => {
|
|
51
|
+
const payload = coerceBookingConfirmed(envelope);
|
|
52
|
+
if (!payload)
|
|
53
|
+
return;
|
|
54
|
+
const deps = options.deps ?? getChannelPushDeps();
|
|
55
|
+
if (!deps) {
|
|
56
|
+
// Templates haven't wired channel-push — silent no-op so the rest
|
|
57
|
+
// of the booking flow isn't impacted by missing wiring.
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const logger = deps.logger ?? defaultLogger;
|
|
61
|
+
try {
|
|
62
|
+
const targets = await resolveBookingPushTargets(deps.db, payload.bookingId);
|
|
63
|
+
if (targets.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
await upsertPendingBookingLinks(deps.db, payload.bookingId, targets);
|
|
66
|
+
if (drainInline) {
|
|
67
|
+
// Inline drain — simple deployments without the workflow runtime
|
|
68
|
+
// wired (dev templates, demo mode). Production uses the workflow
|
|
69
|
+
// and skips this branch.
|
|
70
|
+
await processBookingPush({ bookingId: payload.bookingId }, deps);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
logger.error("[channel-push] booking.confirmed subscriber failed", {
|
|
75
|
+
bookingId: payload.bookingId,
|
|
76
|
+
error: err instanceof Error ? err.message : String(err),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const slotHandler = async (envelope) => {
|
|
81
|
+
const payload = coerceSlotChanged(envelope);
|
|
82
|
+
if (!payload)
|
|
83
|
+
return;
|
|
84
|
+
const deps = options.deps ?? getChannelPushDeps();
|
|
85
|
+
if (!deps)
|
|
86
|
+
return;
|
|
87
|
+
const logger = deps.logger ?? defaultLogger;
|
|
88
|
+
try {
|
|
89
|
+
const targets = await resolveAllotmentTargetsForSlot(deps.db, {
|
|
90
|
+
slotId: payload.slotId,
|
|
91
|
+
productId: payload.productId,
|
|
92
|
+
optionId: payload.optionId,
|
|
93
|
+
});
|
|
94
|
+
if (targets.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
const startsAt = payload.startsAt instanceof Date ? payload.startsAt : new Date(payload.startsAt);
|
|
97
|
+
// One intent per (channel, slot) pair — supersession collapses
|
|
98
|
+
// concurrent events.
|
|
99
|
+
for (const target of targets) {
|
|
100
|
+
await upsertAvailabilityIntent(deps.db, {
|
|
101
|
+
channelId: target.channelId,
|
|
102
|
+
sourceConnectionId: target.sourceConnectionId,
|
|
103
|
+
slotId: payload.slotId,
|
|
104
|
+
productId: payload.productId,
|
|
105
|
+
optionId: payload.optionId,
|
|
106
|
+
startsAt,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (drainInline) {
|
|
110
|
+
// Drain only this channel's intents to keep latency bounded.
|
|
111
|
+
// (v1 dev behavior; production runs the scheduled workflow.)
|
|
112
|
+
for (const target of targets) {
|
|
113
|
+
await processAvailabilityPushIntents({ channelId: target.channelId, limit: 50 }, deps);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
logger.error("[channel-push] availability.slot.changed subscriber failed", {
|
|
119
|
+
slotId: payload.slotId,
|
|
120
|
+
error: err instanceof Error ? err.message : String(err),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const contentHandler = async (envelope) => {
|
|
125
|
+
const payload = coerceContentChanged(envelope);
|
|
126
|
+
if (!payload)
|
|
127
|
+
return;
|
|
128
|
+
const deps = options.deps ?? getChannelPushDeps();
|
|
129
|
+
if (!deps)
|
|
130
|
+
return;
|
|
131
|
+
const logger = deps.logger ?? defaultLogger;
|
|
132
|
+
try {
|
|
133
|
+
const targets = await resolveContentPushTargets(deps.db, payload.id);
|
|
134
|
+
if (targets.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
for (const target of targets) {
|
|
137
|
+
await upsertContentIntent(deps.db, {
|
|
138
|
+
channelId: target.channelId,
|
|
139
|
+
sourceConnectionId: target.sourceConnectionId,
|
|
140
|
+
productId: payload.id,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (drainInline) {
|
|
144
|
+
for (const target of targets) {
|
|
145
|
+
await processContentPushIntents({ channelId: target.channelId, limit: 50 }, deps);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
logger.error("[channel-push] product.content.changed subscriber failed", {
|
|
151
|
+
productId: payload.id,
|
|
152
|
+
error: err instanceof Error ? err.message : String(err),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return [
|
|
157
|
+
{ event: "booking.confirmed", handler },
|
|
158
|
+
{ event: "availability.slot.changed", handler: slotHandler },
|
|
159
|
+
{ event: "product.content.changed", handler: contentHandler },
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Trigger the booking-push pipeline for an arbitrary booking id —
|
|
164
|
+
* useful for the operator dashboard's "retry sync" button and for the
|
|
165
|
+
* reconciler (Phase G).
|
|
166
|
+
*/
|
|
167
|
+
export async function triggerBookingPushForBooking(bookingId) {
|
|
168
|
+
const deps = getChannelPushDepsOrThrow();
|
|
169
|
+
const targets = await resolveBookingPushTargets(deps.db, bookingId);
|
|
170
|
+
if (targets.length === 0)
|
|
171
|
+
return;
|
|
172
|
+
await upsertPendingBookingLinks(deps.db, bookingId, targets);
|
|
173
|
+
await processBookingPush({ bookingId }, deps);
|
|
174
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the channel-push pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Per docs/architecture/channel-push-architecture.md §4-§6.
|
|
5
|
+
*/
|
|
6
|
+
import type { SourceAdapterRegistry } from "@voyantjs/catalog/booking-engine";
|
|
7
|
+
import type { AnyDrizzleDb } from "@voyantjs/db";
|
|
8
|
+
/**
|
|
9
|
+
* Process-local dependencies the channel-push workers and subscribers
|
|
10
|
+
* need. Templates wire this once at startup via `setChannelPushDeps`;
|
|
11
|
+
* callers retrieve via `getChannelPushDeps`.
|
|
12
|
+
*
|
|
13
|
+
* The runtime indirection lets workflows (which can't take closures in
|
|
14
|
+
* their durable input) and EventBus subscribers (which run in the
|
|
15
|
+
* emitter's process) share the same wiring without each consumer
|
|
16
|
+
* re-resolving services. Per §4.5 — subscribers stay write-only and
|
|
17
|
+
* delegate HTTP work to the workflow, which reaches into these deps.
|
|
18
|
+
*/
|
|
19
|
+
export interface ChannelPushDeps {
|
|
20
|
+
db: AnyDrizzleDb;
|
|
21
|
+
registry: SourceAdapterRegistry;
|
|
22
|
+
/**
|
|
23
|
+
* Optional logger. Defaults to a console fallback. Subscribers and
|
|
24
|
+
* workers log to this when they swallow errors per the EventBus
|
|
25
|
+
* fire-and-forget contract.
|
|
26
|
+
*/
|
|
27
|
+
logger?: ChannelPushLogger;
|
|
28
|
+
}
|
|
29
|
+
export interface ChannelPushLogger {
|
|
30
|
+
info?: (message: string, meta?: Record<string, unknown>) => void;
|
|
31
|
+
warn?: (message: string, meta?: Record<string, unknown>) => void;
|
|
32
|
+
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
33
|
+
}
|
|
34
|
+
/** Wire the channel-push pipeline at process start. Idempotent. */
|
|
35
|
+
export declare function setChannelPushDeps(deps: ChannelPushDeps): void;
|
|
36
|
+
/** Retrieve the wired deps, throwing if templates haven't wired them. */
|
|
37
|
+
export declare function getChannelPushDepsOrThrow(): ChannelPushDeps;
|
|
38
|
+
/** Retrieve the wired deps, returning undefined when none are wired. */
|
|
39
|
+
export declare function getChannelPushDeps(): ChannelPushDeps | undefined;
|
|
40
|
+
/** Reset (test helper). */
|
|
41
|
+
export declare function clearChannelPushDeps(): void;
|
|
42
|
+
export declare const defaultLogger: ChannelPushLogger;
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/channel-push/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,YAAY,CAAA;IAChB,QAAQ,EAAE,qBAAqB,CAAA;IAC/B;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAChE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAChE,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;CACjE;AAUD,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAE9D;AAED,yEAAyE;AACzE,wBAAgB,yBAAyB,IAAI,eAAe,CAQ3D;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,SAAS,CAEhE;AAED,2BAA2B;AAC3B,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,eAAO,MAAM,aAAa,EAAE,iBAI3B,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the channel-push pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Per docs/architecture/channel-push-architecture.md §4-§6.
|
|
5
|
+
*/
|
|
6
|
+
const DEPS_KEY = Symbol.for("voyant.distribution.channel-push.deps");
|
|
7
|
+
const globalRef = globalThis;
|
|
8
|
+
/** Wire the channel-push pipeline at process start. Idempotent. */
|
|
9
|
+
export function setChannelPushDeps(deps) {
|
|
10
|
+
globalRef[DEPS_KEY] = deps;
|
|
11
|
+
}
|
|
12
|
+
/** Retrieve the wired deps, throwing if templates haven't wired them. */
|
|
13
|
+
export function getChannelPushDepsOrThrow() {
|
|
14
|
+
const deps = globalRef[DEPS_KEY];
|
|
15
|
+
if (!deps) {
|
|
16
|
+
throw new Error("channel-push deps not wired — call setChannelPushDeps({ db, registry }) at process start");
|
|
17
|
+
}
|
|
18
|
+
return deps;
|
|
19
|
+
}
|
|
20
|
+
/** Retrieve the wired deps, returning undefined when none are wired. */
|
|
21
|
+
export function getChannelPushDeps() {
|
|
22
|
+
return globalRef[DEPS_KEY];
|
|
23
|
+
}
|
|
24
|
+
/** Reset (test helper). */
|
|
25
|
+
export function clearChannelPushDeps() {
|
|
26
|
+
delete globalRef[DEPS_KEY];
|
|
27
|
+
}
|
|
28
|
+
export const defaultLogger = {
|
|
29
|
+
info: (message, meta) => console.log(`[channel-push] ${message}`, meta ?? ""),
|
|
30
|
+
warn: (message, meta) => console.warn(`[channel-push] ${message}`, meta ?? ""),
|
|
31
|
+
error: (message, meta) => console.error(`[channel-push] ${message}`, meta ?? ""),
|
|
32
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable channel-push workflows.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the inline-callable processors (`processBookingPush`,
|
|
5
|
+
* `processAvailabilityPushIntents`, `processContentPushIntents`) in
|
|
6
|
+
* `@voyantjs/workflows` definitions so retries, sleeps, and resumption
|
|
7
|
+
* survive worker restarts. Importing this module registers the
|
|
8
|
+
* workflows in the global registry — Voyant Cloud orchestrator picks
|
|
9
|
+
* them up automatically.
|
|
10
|
+
*
|
|
11
|
+
* The workflow bodies look up `ChannelPushDeps` from the process-local
|
|
12
|
+
* holder set via `setChannelPushDeps`. Hosts wire deps at bootstrap;
|
|
13
|
+
* the orchestrator's Node container reads from the same global since
|
|
14
|
+
* it runs in the same isolate that loaded the user bundle.
|
|
15
|
+
*
|
|
16
|
+
* Dev / single-process deployments (e.g. the operator template's
|
|
17
|
+
* inline drain) don't need to register these — the subscriber calls the
|
|
18
|
+
* processors directly. Production deployments with the Voyant Cloud
|
|
19
|
+
* orchestrator wired import this module to opt into durability.
|
|
20
|
+
*
|
|
21
|
+
* Per docs/architecture/channel-push-architecture.md §4.2 + §12.
|
|
22
|
+
*/
|
|
23
|
+
import { type ProcessAvailabilityPushInput, type ProcessAvailabilityPushResult } from "./availability-push.js";
|
|
24
|
+
import { type ProcessBookingPushInput, type ProcessBookingPushResult } from "./booking-push.js";
|
|
25
|
+
import { type ProcessContentPushInput, type ProcessContentPushResult } from "./content-push.js";
|
|
26
|
+
/**
|
|
27
|
+
* Per-booking saga workflow with compensation support.
|
|
28
|
+
*
|
|
29
|
+
* Concurrency is keyed by `bookingId` so two confirms of the same
|
|
30
|
+
* booking serialize (which can't actually happen given the booking
|
|
31
|
+
* state machine, but the perKey lock is cheap insurance). Retries on
|
|
32
|
+
* exponential backoff up to 5 attempts; the per-link compensation pass
|
|
33
|
+
* inside `processBookingPush` handles strict-atomic policy when
|
|
34
|
+
* `channel_contracts.policy.compensation = "strict-atomic"`.
|
|
35
|
+
*
|
|
36
|
+
* Per §4.2 + §12.1.
|
|
37
|
+
*/
|
|
38
|
+
export declare const channelBookingPushWorkflow: import("@voyantjs/workflows").WorkflowDefinition<ProcessBookingPushInput, ProcessBookingPushResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Scheduled batch worker for availability push. Runs every 30 seconds
|
|
41
|
+
* (tunable per channel via policy in a future iteration); each tick
|
|
42
|
+
* drains up to 100 pending intents, capped at one concurrent run per
|
|
43
|
+
* channel. Idempotency is upstream-side via `(slot_id, remaining_pax)`.
|
|
44
|
+
*
|
|
45
|
+
* Per §5.3 + §12.2.
|
|
46
|
+
*/
|
|
47
|
+
export declare const channelAvailabilityPushWorkflow: import("@voyantjs/workflows").WorkflowDefinition<ProcessAvailabilityPushInput, ProcessAvailabilityPushResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Scheduled batch worker for content push. Longer cadence (5m) since
|
|
50
|
+
* content drift is rarely time-critical; idempotency via the upstream's
|
|
51
|
+
* acknowledged-hash skip in `processContentPushIntents`.
|
|
52
|
+
*
|
|
53
|
+
* Per §6 + §12.3.
|
|
54
|
+
*/
|
|
55
|
+
export declare const channelContentPushWorkflow: import("@voyantjs/workflows").WorkflowDefinition<ProcessContentPushInput, ProcessContentPushResult>;
|
|
56
|
+
//# sourceMappingURL=workflows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../../src/channel-push/workflows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,6BAA6B,EAEnC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAE9B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAE9B,MAAM,mBAAmB,CAAA;AAE1B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,qGAiBrC,CAAA;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,+GAkB1C,CAAA;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,qGAkBrC,CAAA"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable channel-push workflows.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the inline-callable processors (`processBookingPush`,
|
|
5
|
+
* `processAvailabilityPushIntents`, `processContentPushIntents`) in
|
|
6
|
+
* `@voyantjs/workflows` definitions so retries, sleeps, and resumption
|
|
7
|
+
* survive worker restarts. Importing this module registers the
|
|
8
|
+
* workflows in the global registry — Voyant Cloud orchestrator picks
|
|
9
|
+
* them up automatically.
|
|
10
|
+
*
|
|
11
|
+
* The workflow bodies look up `ChannelPushDeps` from the process-local
|
|
12
|
+
* holder set via `setChannelPushDeps`. Hosts wire deps at bootstrap;
|
|
13
|
+
* the orchestrator's Node container reads from the same global since
|
|
14
|
+
* it runs in the same isolate that loaded the user bundle.
|
|
15
|
+
*
|
|
16
|
+
* Dev / single-process deployments (e.g. the operator template's
|
|
17
|
+
* inline drain) don't need to register these — the subscriber calls the
|
|
18
|
+
* processors directly. Production deployments with the Voyant Cloud
|
|
19
|
+
* orchestrator wired import this module to opt into durability.
|
|
20
|
+
*
|
|
21
|
+
* Per docs/architecture/channel-push-architecture.md §4.2 + §12.
|
|
22
|
+
*/
|
|
23
|
+
import { workflow } from "@voyantjs/workflows";
|
|
24
|
+
import { CHANNEL_AVAILABILITY_PUSH_WORKFLOW_ID, processAvailabilityPushIntents, } from "./availability-push.js";
|
|
25
|
+
import { CHANNEL_BOOKING_PUSH_WORKFLOW_ID, processBookingPush, } from "./booking-push.js";
|
|
26
|
+
import { CHANNEL_CONTENT_PUSH_WORKFLOW_ID, processContentPushIntents, } from "./content-push.js";
|
|
27
|
+
/**
|
|
28
|
+
* Per-booking saga workflow with compensation support.
|
|
29
|
+
*
|
|
30
|
+
* Concurrency is keyed by `bookingId` so two confirms of the same
|
|
31
|
+
* booking serialize (which can't actually happen given the booking
|
|
32
|
+
* state machine, but the perKey lock is cheap insurance). Retries on
|
|
33
|
+
* exponential backoff up to 5 attempts; the per-link compensation pass
|
|
34
|
+
* inside `processBookingPush` handles strict-atomic policy when
|
|
35
|
+
* `channel_contracts.policy.compensation = "strict-atomic"`.
|
|
36
|
+
*
|
|
37
|
+
* Per §4.2 + §12.1.
|
|
38
|
+
*/
|
|
39
|
+
export const channelBookingPushWorkflow = workflow({
|
|
40
|
+
id: CHANNEL_BOOKING_PUSH_WORKFLOW_ID,
|
|
41
|
+
description: "Drain pending channel_booking_links and push to upstream channels",
|
|
42
|
+
retry: { backoff: "exponential", max: 5, initial: "5s", maxDelay: "5m" },
|
|
43
|
+
timeout: "1h",
|
|
44
|
+
concurrency: {
|
|
45
|
+
key: (input) => input.bookingId,
|
|
46
|
+
limit: 1,
|
|
47
|
+
strategy: "queue",
|
|
48
|
+
},
|
|
49
|
+
tags: ["channel-push", "booking"],
|
|
50
|
+
async run(input, ctx) {
|
|
51
|
+
return await ctx.step("process-booking-push", () => processBookingPush(input));
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Scheduled batch worker for availability push. Runs every 30 seconds
|
|
56
|
+
* (tunable per channel via policy in a future iteration); each tick
|
|
57
|
+
* drains up to 100 pending intents, capped at one concurrent run per
|
|
58
|
+
* channel. Idempotency is upstream-side via `(slot_id, remaining_pax)`.
|
|
59
|
+
*
|
|
60
|
+
* Per §5.3 + §12.2.
|
|
61
|
+
*/
|
|
62
|
+
export const channelAvailabilityPushWorkflow = workflow({
|
|
63
|
+
id: CHANNEL_AVAILABILITY_PUSH_WORKFLOW_ID,
|
|
64
|
+
description: "Drain channel_availability_push_intents per channel",
|
|
65
|
+
schedule: { every: "30s" },
|
|
66
|
+
retry: { backoff: "exponential", max: 3, initial: "10s" },
|
|
67
|
+
timeout: "5m",
|
|
68
|
+
concurrency: {
|
|
69
|
+
key: (input) => input.channelId ?? "all",
|
|
70
|
+
limit: 1,
|
|
71
|
+
strategy: "queue",
|
|
72
|
+
},
|
|
73
|
+
tags: ["channel-push", "availability"],
|
|
74
|
+
async run(input, ctx) {
|
|
75
|
+
return await ctx.step("process-availability-push", () => processAvailabilityPushIntents(input));
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* Scheduled batch worker for content push. Longer cadence (5m) since
|
|
80
|
+
* content drift is rarely time-critical; idempotency via the upstream's
|
|
81
|
+
* acknowledged-hash skip in `processContentPushIntents`.
|
|
82
|
+
*
|
|
83
|
+
* Per §6 + §12.3.
|
|
84
|
+
*/
|
|
85
|
+
export const channelContentPushWorkflow = workflow({
|
|
86
|
+
id: CHANNEL_CONTENT_PUSH_WORKFLOW_ID,
|
|
87
|
+
description: "Drain channel_content_push_intents per channel",
|
|
88
|
+
schedule: { every: "5m" },
|
|
89
|
+
retry: { backoff: "exponential", max: 3, initial: "30s" },
|
|
90
|
+
timeout: "5m",
|
|
91
|
+
concurrency: {
|
|
92
|
+
key: (input) => input.channelId ?? "all",
|
|
93
|
+
limit: 1,
|
|
94
|
+
strategy: "queue",
|
|
95
|
+
},
|
|
96
|
+
tags: ["channel-push", "content"],
|
|
97
|
+
async run(input, ctx) {
|
|
98
|
+
return await ctx.step("process-content-push", () => processContentPushIntents(input));
|
|
99
|
+
},
|
|
100
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,12 @@ export type { DistributionRoutes } from "./routes.js";
|
|
|
5
5
|
export declare const distributionModule: Module;
|
|
6
6
|
export declare const distributionHonoModule: HonoModule;
|
|
7
7
|
export { distributionBookingExtension } from "./booking-extension.js";
|
|
8
|
+
export { type AcquireTokenAcquired, type AcquireTokenDenied, type AcquireTokenResult, acquireToken, type ChannelPushPriority, channelScopeKey, DEFAULT_PRIORITY_GATES, drainBucket, type RateLimitConfig, } from "./rate-limit.js";
|
|
8
9
|
export type { Channel, ChannelBookingLink, ChannelCommissionRule, ChannelContactProjection, ChannelContract, ChannelInventoryAllotment, ChannelInventoryAllotmentTarget, ChannelInventoryReleaseExecution, ChannelInventoryReleaseRule, ChannelProductMapping, ChannelReconciliationItem, ChannelReconciliationPolicy, ChannelReconciliationRun, ChannelReleaseSchedule, ChannelRemittanceException, ChannelSettlementApproval, ChannelSettlementItem, ChannelSettlementPolicy, ChannelSettlementRun, ChannelWebhookEvent, NewChannel, NewChannelBookingLink, NewChannelCommissionRule, NewChannelContactProjection, NewChannelContract, NewChannelInventoryAllotment, NewChannelInventoryAllotmentTarget, NewChannelInventoryReleaseExecution, NewChannelInventoryReleaseRule, NewChannelProductMapping, NewChannelReconciliationItem, NewChannelReconciliationPolicy, NewChannelReconciliationRun, NewChannelReleaseSchedule, NewChannelRemittanceException, NewChannelSettlementApproval, NewChannelSettlementItem, NewChannelSettlementPolicy, NewChannelSettlementRun, NewChannelWebhookEvent, } from "./schema.js";
|
|
9
10
|
export { channelBookingLinks, channelCommissionRules, channelContactProjections, channelContracts, channelInventoryAllotments, channelInventoryAllotmentTargets, channelInventoryReleaseExecutions, channelInventoryReleaseRules, channelProductMappings, channelReconciliationItems, channelReconciliationPolicies, channelReconciliationPolicyFrequencyEnum, channelReconciliationRuns, channelReleaseScheduleKindEnum, channelReleaseSchedules, channelRemittanceExceptionStatusEnum, channelRemittanceExceptions, channelSettlementApprovalStatusEnum, channelSettlementApprovals, channelSettlementItems, channelSettlementPolicies, channelSettlementPolicyFrequencyEnum, channelSettlementRuns, channels, channelWebhookEvents, } from "./schema.js";
|
|
11
|
+
export type { ChannelAvailabilityPushIntent, ChannelContentPushIntent, NewChannelAvailabilityPushIntent, NewChannelContentPushIntent, } from "./schema-push-intents.js";
|
|
12
|
+
export { channelAvailabilityPushIntents, channelContentPushIntents, } from "./schema-push-intents.js";
|
|
10
13
|
export { channelBookingLinkListQuerySchema, channelCommissionRuleListQuerySchema, channelContractListQuerySchema, channelInventoryAllotmentListQuerySchema, channelInventoryAllotmentTargetListQuerySchema, channelInventoryReleaseExecutionListQuerySchema, channelInventoryReleaseRuleListQuerySchema, channelListQuerySchema, channelProductMappingListQuerySchema, channelReconciliationItemListQuerySchema, channelReconciliationPolicyFrequencySchema, channelReconciliationPolicyListQuerySchema, channelReconciliationRunListQuerySchema, channelReleaseScheduleKindSchema, channelReleaseScheduleListQuerySchema, channelRemittanceExceptionListQuerySchema, channelRemittanceExceptionStatusSchema, channelSettlementApprovalListQuerySchema, channelSettlementApprovalStatusSchema, channelSettlementItemListQuerySchema, channelSettlementPolicyFrequencySchema, channelSettlementPolicyListQuerySchema, channelSettlementRunListQuerySchema, channelWebhookEventListQuerySchema, insertChannelBookingLinkSchema, insertChannelCommissionRuleSchema, insertChannelContractSchema, insertChannelInventoryAllotmentSchema, insertChannelInventoryAllotmentTargetSchema, insertChannelInventoryReleaseExecutionSchema, insertChannelInventoryReleaseRuleSchema, insertChannelProductMappingSchema, insertChannelReconciliationItemSchema, insertChannelReconciliationPolicySchema, insertChannelReconciliationRunSchema, insertChannelReleaseScheduleSchema, insertChannelRemittanceExceptionSchema, insertChannelSchema, insertChannelSettlementApprovalSchema, insertChannelSettlementItemSchema, insertChannelSettlementPolicySchema, insertChannelSettlementRunSchema, insertChannelWebhookEventSchema, updateChannelBookingLinkSchema, updateChannelCommissionRuleSchema, updateChannelContractSchema, updateChannelInventoryAllotmentSchema, updateChannelInventoryAllotmentTargetSchema, updateChannelInventoryReleaseExecutionSchema, updateChannelInventoryReleaseRuleSchema, updateChannelProductMappingSchema, updateChannelReconciliationItemSchema, updateChannelReconciliationPolicySchema, updateChannelReconciliationRunSchema, updateChannelReleaseScheduleSchema, updateChannelRemittanceExceptionSchema, updateChannelSchema, updateChannelSettlementApprovalSchema, updateChannelSettlementItemSchema, updateChannelSettlementPolicySchema, updateChannelSettlementRunSchema, updateChannelWebhookEventSchema, } from "./validation.js";
|
|
14
|
+
export { type OutboundEnvelopeInput, type OutboundEnvelopeResultInput, type PreparedEnvelope, prepareOutboundEnvelope, redactBodyPii, redactHeaders, redactStringPii, } from "./webhook-deliveries.js";
|
|
11
15
|
export { distributionService };
|
|
12
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,9 @@ export const distributionHonoModule = {
|
|
|
8
8
|
routes: distributionRoutes,
|
|
9
9
|
};
|
|
10
10
|
export { distributionBookingExtension } from "./booking-extension.js";
|
|
11
|
+
export { acquireToken, channelScopeKey, DEFAULT_PRIORITY_GATES, drainBucket, } from "./rate-limit.js";
|
|
11
12
|
export { channelBookingLinks, channelCommissionRules, channelContactProjections, channelContracts, channelInventoryAllotments, channelInventoryAllotmentTargets, channelInventoryReleaseExecutions, channelInventoryReleaseRules, channelProductMappings, channelReconciliationItems, channelReconciliationPolicies, channelReconciliationPolicyFrequencyEnum, channelReconciliationRuns, channelReleaseScheduleKindEnum, channelReleaseSchedules, channelRemittanceExceptionStatusEnum, channelRemittanceExceptions, channelSettlementApprovalStatusEnum, channelSettlementApprovals, channelSettlementItems, channelSettlementPolicies, channelSettlementPolicyFrequencyEnum, channelSettlementRuns, channels, channelWebhookEvents, } from "./schema.js";
|
|
13
|
+
export { channelAvailabilityPushIntents, channelContentPushIntents, } from "./schema-push-intents.js";
|
|
12
14
|
export { channelBookingLinkListQuerySchema, channelCommissionRuleListQuerySchema, channelContractListQuerySchema, channelInventoryAllotmentListQuerySchema, channelInventoryAllotmentTargetListQuerySchema, channelInventoryReleaseExecutionListQuerySchema, channelInventoryReleaseRuleListQuerySchema, channelListQuerySchema, channelProductMappingListQuerySchema, channelReconciliationItemListQuerySchema, channelReconciliationPolicyFrequencySchema, channelReconciliationPolicyListQuerySchema, channelReconciliationRunListQuerySchema, channelReleaseScheduleKindSchema, channelReleaseScheduleListQuerySchema, channelRemittanceExceptionListQuerySchema, channelRemittanceExceptionStatusSchema, channelSettlementApprovalListQuerySchema, channelSettlementApprovalStatusSchema, channelSettlementItemListQuerySchema, channelSettlementPolicyFrequencySchema, channelSettlementPolicyListQuerySchema, channelSettlementRunListQuerySchema, channelWebhookEventListQuerySchema, insertChannelBookingLinkSchema, insertChannelCommissionRuleSchema, insertChannelContractSchema, insertChannelInventoryAllotmentSchema, insertChannelInventoryAllotmentTargetSchema, insertChannelInventoryReleaseExecutionSchema, insertChannelInventoryReleaseRuleSchema, insertChannelProductMappingSchema, insertChannelReconciliationItemSchema, insertChannelReconciliationPolicySchema, insertChannelReconciliationRunSchema, insertChannelReleaseScheduleSchema, insertChannelRemittanceExceptionSchema, insertChannelSchema, insertChannelSettlementApprovalSchema, insertChannelSettlementItemSchema, insertChannelSettlementPolicySchema, insertChannelSettlementRunSchema, insertChannelWebhookEventSchema, updateChannelBookingLinkSchema, updateChannelCommissionRuleSchema, updateChannelContractSchema, updateChannelInventoryAllotmentSchema, updateChannelInventoryAllotmentTargetSchema, updateChannelInventoryReleaseExecutionSchema, updateChannelInventoryReleaseRuleSchema, updateChannelProductMappingSchema, updateChannelReconciliationItemSchema, updateChannelReconciliationPolicySchema, updateChannelReconciliationRunSchema, updateChannelReleaseScheduleSchema, updateChannelRemittanceExceptionSchema, updateChannelSchema, updateChannelSettlementApprovalSchema, updateChannelSettlementItemSchema, updateChannelSettlementPolicySchema, updateChannelSettlementRunSchema, updateChannelWebhookEventSchema, } from "./validation.js";
|
|
15
|
+
export { prepareOutboundEnvelope, redactBodyPii, redactHeaders, redactStringPii, } from "./webhook-deliveries.js";
|
|
13
16
|
export { distributionService };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-channel rate limiting (token bucket on Postgres).
|
|
3
|
+
*
|
|
4
|
+
* `acquireToken` is the canonical channel-push wrapper around the
|
|
5
|
+
* generic `infra.rate_limit_buckets` primitive. Each call:
|
|
6
|
+
*
|
|
7
|
+
* 1. Atomically refills the bucket based on `(now - last_refill_at) *
|
|
8
|
+
* refill_rate`, capped at capacity.
|
|
9
|
+
* 2. Checks the priority gate: tokens_available >= gate * capacity
|
|
10
|
+
* AND tokens_available >= 1.
|
|
11
|
+
* 3. On success, decrements by 1 and returns `{ acquired: true }`.
|
|
12
|
+
* 4. On denial, returns `{ acquired: false, retryAfterMs }` computed
|
|
13
|
+
* from how long until enough tokens refill to clear the gate.
|
|
14
|
+
*
|
|
15
|
+
* Whole thing is one round-trip (an UPSERT with conditional UPDATE).
|
|
16
|
+
*
|
|
17
|
+
* Per docs/architecture/channel-push-architecture.md §14.2 and §14.3.
|
|
18
|
+
*/
|
|
19
|
+
import type { AnyDrizzleDb } from "@voyantjs/db";
|
|
20
|
+
export type ChannelPushPriority = "booking" | "availability" | "content";
|
|
21
|
+
export interface RateLimitConfig {
|
|
22
|
+
/** Sustained refill rate (tokens per second). */
|
|
23
|
+
rps: number;
|
|
24
|
+
/** Burst capacity (max tokens in the bucket). */
|
|
25
|
+
burst: number;
|
|
26
|
+
/**
|
|
27
|
+
* Per-priority reserve thresholds. Defaults to:
|
|
28
|
+
* { booking: 0, availability: 0.3, content: 0.7 }
|
|
29
|
+
* Read as: bookings dispatch with any tokens; availability when
|
|
30
|
+
* bucket ≥ 30% full; content when ≥ 70% full.
|
|
31
|
+
*/
|
|
32
|
+
priorityGates?: Partial<Record<ChannelPushPriority, number>>;
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_PRIORITY_GATES: Record<ChannelPushPriority, number>;
|
|
35
|
+
export interface AcquireTokenAcquired {
|
|
36
|
+
acquired: true;
|
|
37
|
+
/** Tokens left in the bucket after this call. */
|
|
38
|
+
tokensRemaining: number;
|
|
39
|
+
}
|
|
40
|
+
export interface AcquireTokenDenied {
|
|
41
|
+
acquired: false;
|
|
42
|
+
/** Suggested wait time in milliseconds before retrying. */
|
|
43
|
+
retryAfterMs: number;
|
|
44
|
+
/** Tokens currently in the bucket (post-refill). */
|
|
45
|
+
tokensAvailable: number;
|
|
46
|
+
}
|
|
47
|
+
export type AcquireTokenResult = AcquireTokenAcquired | AcquireTokenDenied;
|
|
48
|
+
/**
|
|
49
|
+
* Build the channel-push scope key from a (channel, connection) pair.
|
|
50
|
+
* Same shape used by the workflow + reconciler so all paths address the
|
|
51
|
+
* same bucket.
|
|
52
|
+
*/
|
|
53
|
+
export declare function channelScopeKey(channelId: string, connectionId: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Acquire one token from the bucket at `scope`, applying the priority
|
|
56
|
+
* gate for `priority`. Creates the bucket on first call (UPSERT with
|
|
57
|
+
* full capacity).
|
|
58
|
+
*/
|
|
59
|
+
export declare function acquireToken(db: AnyDrizzleDb, scope: string, config: RateLimitConfig, priority: ChannelPushPriority): Promise<AcquireTokenResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Drain the bucket to zero and freeze it for `cooldownMs`.
|
|
62
|
+
*
|
|
63
|
+
* Called when an upstream returns 429 with a `Retry-After` hint —
|
|
64
|
+
* prevents subsequent dispatchers from immediately retrying through
|
|
65
|
+
* the same bucket and lets our outbound estimate converge with the
|
|
66
|
+
* channel's authoritative state. Per §14.4.
|
|
67
|
+
*/
|
|
68
|
+
export declare function drainBucket(db: AnyDrizzleDb, scope: string, cooldownMs: number): Promise<void>;
|
|
69
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAIhD,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAAA;AAExE,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAA;IACX,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,CAAA;CAC7D;AAED,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAItE,CAAA;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,IAAI,CAAA;IACd,iDAAiD;IACjD,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,CAAA;IACf,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAA;IACpB,oDAAoD;IACpD,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,MAAM,kBAAkB,GAAG,oBAAoB,GAAG,kBAAkB,CAAA;AAE1E;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAkF7B;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAUf"}
|