@uengage.io/platform-sdk 0.15.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.API_BASE = void 0;
4
+ exports.endpointsFor = endpointsFor;
5
+ /**
6
+ * TODO(event-bus): populate `httpDomain` / `realtimeDomain` per
7
+ * environment from the `RealtimeEventApiHttpDns-{env}` and
8
+ * `RealtimeEventApiRealtimeDns-{env}` outputs of `UengageRealtimeService-{env}`
9
+ * once the stack has been deployed there.
10
+ */
11
+ const ENDPOINTS = {
12
+ dev: null,
13
+ uat: null,
14
+ prod: null,
15
+ };
16
+ /** Platform API base per environment — known ahead of any deploy. */
17
+ exports.API_BASE = {
18
+ dev: 'https://api.dev.platform.uengage.io',
19
+ uat: 'https://api.uat.platform.uengage.io',
20
+ prod: 'https://api.platform.uengage.io',
21
+ };
22
+ function endpointsFor(env) {
23
+ const found = ENDPOINTS[env];
24
+ if (!found) {
25
+ throw new Error(`realtime.connect: no endpoints registered for env "${env}". ` +
26
+ 'Pass {endpoints: {httpDomain, realtimeDomain, apiBase}} explicitly, or fill in ' +
27
+ "src/realtime/endpoints.ts from the realtime stack's RealtimeEventApi*Dns outputs.");
28
+ }
29
+ return found;
30
+ }
31
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW5kcG9pbnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3JlYWx0aW1lL2VuZHBvaW50cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUEyQ0Esb0NBVUM7QUE3QkQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFNBQVMsR0FBa0Q7SUFDL0QsR0FBRyxFQUFFLElBQUk7SUFDVCxHQUFHLEVBQUUsSUFBSTtJQUNULElBQUksRUFBRSxJQUFJO0NBQ1gsQ0FBQztBQUVGLHFFQUFxRTtBQUN4RCxRQUFBLFFBQVEsR0FBZ0M7SUFDbkQsR0FBRyxFQUFFLHFDQUFxQztJQUMxQyxHQUFHLEVBQUUscUNBQXFDO0lBQzFDLElBQUksRUFBRSxpQ0FBaUM7Q0FDeEMsQ0FBQztBQUVGLFNBQWdCLFlBQVksQ0FBQyxHQUFnQjtJQUMzQyxNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDN0IsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ1gsTUFBTSxJQUFJLEtBQUssQ0FDYixzREFBc0QsR0FBRyxLQUFLO1lBQzVELGlGQUFpRjtZQUNqRixtRkFBbUYsQ0FDdEYsQ0FBQztJQUNKLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFBlci1lbnZpcm9ubWVudCByZWFsdGltZSBlbmRwb2ludHMuXG4gKlxuICogRnJvbnRlbmRzIHBhc3MgYGNvbm5lY3QoeyBlbnY6ICdwcm9kJyB9KWAgYW5kIGdldCBVUkxzIGZyb20gaGVyZSDigJQgbm9cbiAqIGhvc3RuYW1lcyBvciBBUk5zIGluIGFwcCBjb2RlLlxuICpcbiAqIEFwcFN5bmMgYXNzaWducyB0aGUgRXZlbnQgQVBJJ3MgRE5TIG5hbWVzIGF0IGNyZWF0ZSB0aW1lXG4gKiAoYDxhcGlJZD4uYXBwc3luYy1hcGkuPHJlZ2lvbj4uYW1hem9uYXdzLmNvbWAgYW5kIHRoZSBtYXRjaGluZ1xuICogYGFwcHN5bmMtcmVhbHRpbWUtYXBpYCBob3N0KSwgc28gdGhleSBjYW5ub3QgYmUga25vd24gYmVmb3JlIHRoZSBzdGFja1xuICogaXMgZGVwbG95ZWQuIFVudGlsIGBzZXJ2aWNlcy9yZWFsdGltZWAgaGFzIGJlZW4gZGVwbG95ZWQgdG8gYW5cbiAqIGVudmlyb25tZW50IGl0cyBlbnRyeSBzdGF5cyBudWxsIGFuZCBgY29ubmVjdCh7IGVudiB9KWAgdGhyb3dzIHdpdGhcbiAqIGluc3RydWN0aW9uczsgYGNvbm5lY3QoeyBlbmRwb2ludHMgfSlgIGFsd2F5cyB3b3Jrcy5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZWFsdGltZUVuZHBvaW50cyB7XG4gIC8qKiBBcHBTeW5jIEV2ZW50IEFQSSBIVFRQIGRvbWFpbi4gQWxzbyB0aGUgYGhvc3RgIGluIHRoZSBhdXRoIGhlYWRlci4gKi9cbiAgaHR0cERvbWFpbjogc3RyaW5nO1xuICAvKiogQXBwU3luYyBFdmVudCBBUEkgcmVhbHRpbWUgKFdlYlNvY2tldCkgZG9tYWluLiAqL1xuICByZWFsdGltZURvbWFpbjogc3RyaW5nO1xuICAvKiogUGxhdGZvcm0gQVBJIGJhc2UgVVJMLCB1c2VkIGZvciB0aGUgbGFzdC12YWx1ZSBzbmFwc2hvdCBmZXRjaC4gKi9cbiAgYXBpQmFzZTogc3RyaW5nO1xufVxuXG5leHBvcnQgdHlwZSBSZWFsdGltZUVudiA9ICdkZXYnIHwgJ3VhdCcgfCAncHJvZCc7XG5cbi8qKlxuICogVE9ETyhldmVudC1idXMpOiBwb3B1bGF0ZSBgaHR0cERvbWFpbmAgLyBgcmVhbHRpbWVEb21haW5gIHBlclxuICogZW52aXJvbm1lbnQgZnJvbSB0aGUgYFJlYWx0aW1lRXZlbnRBcGlIdHRwRG5zLXtlbnZ9YCBhbmRcbiAqIGBSZWFsdGltZUV2ZW50QXBpUmVhbHRpbWVEbnMte2Vudn1gIG91dHB1dHMgb2YgYFVlbmdhZ2VSZWFsdGltZVNlcnZpY2Ute2Vudn1gXG4gKiBvbmNlIHRoZSBzdGFjayBoYXMgYmVlbiBkZXBsb3llZCB0aGVyZS5cbiAqL1xuY29uc3QgRU5EUE9JTlRTOiBSZWNvcmQ8UmVhbHRpbWVFbnYsIFJlYWx0aW1lRW5kcG9pbnRzIHwgbnVsbD4gPSB7XG4gIGRldjogbnVsbCxcbiAgdWF0OiBudWxsLFxuICBwcm9kOiBudWxsLFxufTtcblxuLyoqIFBsYXRmb3JtIEFQSSBiYXNlIHBlciBlbnZpcm9ubWVudCDigJQga25vd24gYWhlYWQgb2YgYW55IGRlcGxveS4gKi9cbmV4cG9ydCBjb25zdCBBUElfQkFTRTogUmVjb3JkPFJlYWx0aW1lRW52LCBzdHJpbmc+ID0ge1xuICBkZXY6ICdodHRwczovL2FwaS5kZXYucGxhdGZvcm0udWVuZ2FnZS5pbycsXG4gIHVhdDogJ2h0dHBzOi8vYXBpLnVhdC5wbGF0Zm9ybS51ZW5nYWdlLmlvJyxcbiAgcHJvZDogJ2h0dHBzOi8vYXBpLnBsYXRmb3JtLnVlbmdhZ2UuaW8nLFxufTtcblxuZXhwb3J0IGZ1bmN0aW9uIGVuZHBvaW50c0ZvcihlbnY6IFJlYWx0aW1lRW52KTogUmVhbHRpbWVFbmRwb2ludHMge1xuICBjb25zdCBmb3VuZCA9IEVORFBPSU5UU1tlbnZdO1xuICBpZiAoIWZvdW5kKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYHJlYWx0aW1lLmNvbm5lY3Q6IG5vIGVuZHBvaW50cyByZWdpc3RlcmVkIGZvciBlbnYgXCIke2Vudn1cIi4gYCArXG4gICAgICAgICdQYXNzIHtlbmRwb2ludHM6IHtodHRwRG9tYWluLCByZWFsdGltZURvbWFpbiwgYXBpQmFzZX19IGV4cGxpY2l0bHksIG9yIGZpbGwgaW4gJyArXG4gICAgICAgIFwic3JjL3JlYWx0aW1lL2VuZHBvaW50cy50cyBmcm9tIHRoZSByZWFsdGltZSBzdGFjaydzIFJlYWx0aW1lRXZlbnRBcGkqRG5zIG91dHB1dHMuXCIsXG4gICAgKTtcbiAgfVxuICByZXR1cm4gZm91bmQ7XG59XG4iXX0=
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Dependency-free envelope guard for the browser realtime client.
3
+ *
4
+ * This mirrors `@uengage/common`'s `platformEventSchema` and the SDK's own
5
+ * `events/schema.ts`, but hand-rolled: the `/realtime` subpath ships with
6
+ * zero runtime dependencies (no zod, no Node built-ins) so it stays a few
7
+ * KB in a frontend bundle. Change one, change all three.
8
+ *
9
+ * NO DOMAIN CROSS-CHECK, deliberately — this is the one mirror that does
10
+ * not carry the resource→domain map, and it is not an oversight:
11
+ *
12
+ * - It would be checking the wrong thing. The producing SDK derives
13
+ * `domain` from `type` and the consuming service re-validates the pair
14
+ * before it ever publishes to a channel. By the time an envelope
15
+ * reaches a browser the pair has been checked twice server-side; a
16
+ * third check can only reject, never repair.
17
+ * - It would rot dangerously. A browser bundle sits in someone's cache
18
+ * for weeks. Baking today's map into it means that the day a new
19
+ * domain ships, every un-refreshed tab starts silently dropping live
20
+ * events for it — a stale client rejecting the future, which is the
21
+ * failure mode `validatePlatformEvent` is careful to avoid even
22
+ * server-side.
23
+ *
24
+ * So the guard is structural only: shapes and types, not membership.
25
+ * `packages/platform-sdk/test/events/domain-map.test.ts` asserts that
26
+ * every canonical domain is accepted, and that an unlisted one is too.
27
+ */
28
+ /** The platform event-bus envelope, as it arrives at a browser. */
29
+ export interface RealtimeEvent {
30
+ id: string;
31
+ type: string;
32
+ domain: string;
33
+ source: string;
34
+ version: number;
35
+ occurredAt: string;
36
+ tenantId: string;
37
+ data: Record<string, unknown>;
38
+ }
39
+ /**
40
+ * Structural check on an untrusted payload. Returns the event or null —
41
+ * never throws, because a single malformed frame must not tear down a
42
+ * live subscription.
43
+ */
44
+ export declare function asRealtimeEvent(value: unknown): RealtimeEvent | null;
45
+ /**
46
+ * Ordering/dedupe guard, keyed on the envelope's monotonic ULID.
47
+ *
48
+ * ULIDs are lexicographically sortable by mint time, so a plain string
49
+ * compare orders events without parsing timestamps or knowing anything
50
+ * about the domain. This is what makes at-least-once delivery and the
51
+ * snapshot/stream race both harmless: whichever arrives second with an
52
+ * id <= the one already rendered is dropped.
53
+ */
54
+ export declare function isNewer(candidateId: string, seenId: string | undefined): boolean;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * Dependency-free envelope guard for the browser realtime client.
4
+ *
5
+ * This mirrors `@uengage/common`'s `platformEventSchema` and the SDK's own
6
+ * `events/schema.ts`, but hand-rolled: the `/realtime` subpath ships with
7
+ * zero runtime dependencies (no zod, no Node built-ins) so it stays a few
8
+ * KB in a frontend bundle. Change one, change all three.
9
+ *
10
+ * NO DOMAIN CROSS-CHECK, deliberately — this is the one mirror that does
11
+ * not carry the resource→domain map, and it is not an oversight:
12
+ *
13
+ * - It would be checking the wrong thing. The producing SDK derives
14
+ * `domain` from `type` and the consuming service re-validates the pair
15
+ * before it ever publishes to a channel. By the time an envelope
16
+ * reaches a browser the pair has been checked twice server-side; a
17
+ * third check can only reject, never repair.
18
+ * - It would rot dangerously. A browser bundle sits in someone's cache
19
+ * for weeks. Baking today's map into it means that the day a new
20
+ * domain ships, every un-refreshed tab starts silently dropping live
21
+ * events for it — a stale client rejecting the future, which is the
22
+ * failure mode `validatePlatformEvent` is careful to avoid even
23
+ * server-side.
24
+ *
25
+ * So the guard is structural only: shapes and types, not membership.
26
+ * `packages/platform-sdk/test/events/domain-map.test.ts` asserts that
27
+ * every canonical domain is accepted, and that an unlisted one is too.
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.asRealtimeEvent = asRealtimeEvent;
31
+ exports.isNewer = isNewer;
32
+ /** ULID: Crockford base32, 26 chars, excludes I/L/O/U. */
33
+ const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
34
+ /** Dotted `resource.operation`, e.g. `order.status_changed`. */
35
+ const EVENT_TYPE_RE = /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
36
+ /**
37
+ * Structural check on an untrusted payload. Returns the event or null —
38
+ * never throws, because a single malformed frame must not tear down a
39
+ * live subscription.
40
+ */
41
+ function asRealtimeEvent(value) {
42
+ if (typeof value !== 'object' || value === null)
43
+ return null;
44
+ const v = value;
45
+ if (typeof v.id !== 'string' || !ULID_RE.test(v.id))
46
+ return null;
47
+ if (typeof v.type !== 'string' || !EVENT_TYPE_RE.test(v.type))
48
+ return null;
49
+ if (typeof v.domain !== 'string' || v.domain.length === 0)
50
+ return null;
51
+ if (typeof v.source !== 'string' || v.source.length === 0)
52
+ return null;
53
+ if (typeof v.version !== 'number' || !Number.isInteger(v.version) || v.version < 1)
54
+ return null;
55
+ if (typeof v.occurredAt !== 'string' || v.occurredAt.length === 0)
56
+ return null;
57
+ if (typeof v.tenantId !== 'string' || v.tenantId.length === 0)
58
+ return null;
59
+ if (typeof v.data !== 'object' || v.data === null || Array.isArray(v.data))
60
+ return null;
61
+ return v;
62
+ }
63
+ /**
64
+ * Ordering/dedupe guard, keyed on the envelope's monotonic ULID.
65
+ *
66
+ * ULIDs are lexicographically sortable by mint time, so a plain string
67
+ * compare orders events without parsing timestamps or knowing anything
68
+ * about the domain. This is what makes at-least-once delivery and the
69
+ * snapshot/stream race both harmless: whichever arrives second with an
70
+ * id <= the one already rendered is dropped.
71
+ */
72
+ function isNewer(candidateId, seenId) {
73
+ if (seenId === undefined)
74
+ return true;
75
+ return candidateId > seenId;
76
+ }
77
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52ZWxvcGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcmVhbHRpbWUvZW52ZWxvcGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBCRzs7QUF5QkgsMENBWUM7QUFXRCwwQkFHQztBQWpERCwwREFBMEQ7QUFDMUQsTUFBTSxPQUFPLEdBQUcsMEJBQTBCLENBQUM7QUFFM0MsZ0VBQWdFO0FBQ2hFLE1BQU0sYUFBYSxHQUFHLG9DQUFvQyxDQUFDO0FBYzNEOzs7O0dBSUc7QUFDSCxTQUFnQixlQUFlLENBQUMsS0FBYztJQUM1QyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLEtBQUssSUFBSTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQzdELE1BQU0sQ0FBQyxHQUFHLEtBQWdDLENBQUM7SUFDM0MsSUFBSSxPQUFPLENBQUMsQ0FBQyxFQUFFLEtBQUssUUFBUSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDakUsSUFBSSxPQUFPLENBQUMsQ0FBQyxJQUFJLEtBQUssUUFBUSxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDM0UsSUFBSSxPQUFPLENBQUMsQ0FBQyxNQUFNLEtBQUssUUFBUSxJQUFJLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN2RSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE1BQU0sS0FBSyxRQUFRLElBQUksQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEtBQUssQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQ3ZFLElBQUksT0FBTyxDQUFDLENBQUMsT0FBTyxLQUFLLFFBQVEsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxPQUFPLEdBQUcsQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQ2hHLElBQUksT0FBTyxDQUFDLENBQUMsVUFBVSxLQUFLLFFBQVEsSUFBSSxDQUFDLENBQUMsVUFBVSxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDL0UsSUFBSSxPQUFPLENBQUMsQ0FBQyxRQUFRLEtBQUssUUFBUSxJQUFJLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUMzRSxJQUFJLE9BQU8sQ0FBQyxDQUFDLElBQUksS0FBSyxRQUFRLElBQUksQ0FBQyxDQUFDLElBQUksS0FBSyxJQUFJLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDeEYsT0FBTyxDQUE2QixDQUFDO0FBQ3ZDLENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILFNBQWdCLE9BQU8sQ0FBQyxXQUFtQixFQUFFLE1BQTBCO0lBQ3JFLElBQUksTUFBTSxLQUFLLFNBQVM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN0QyxPQUFPLFdBQVcsR0FBRyxNQUFNLENBQUM7QUFDOUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogRGVwZW5kZW5jeS1mcmVlIGVudmVsb3BlIGd1YXJkIGZvciB0aGUgYnJvd3NlciByZWFsdGltZSBjbGllbnQuXG4gKlxuICogVGhpcyBtaXJyb3JzIGBAdWVuZ2FnZS9jb21tb25gJ3MgYHBsYXRmb3JtRXZlbnRTY2hlbWFgIGFuZCB0aGUgU0RLJ3Mgb3duXG4gKiBgZXZlbnRzL3NjaGVtYS50c2AsIGJ1dCBoYW5kLXJvbGxlZDogdGhlIGAvcmVhbHRpbWVgIHN1YnBhdGggc2hpcHMgd2l0aFxuICogemVybyBydW50aW1lIGRlcGVuZGVuY2llcyAobm8gem9kLCBubyBOb2RlIGJ1aWx0LWlucykgc28gaXQgc3RheXMgYSBmZXdcbiAqIEtCIGluIGEgZnJvbnRlbmQgYnVuZGxlLiBDaGFuZ2Ugb25lLCBjaGFuZ2UgYWxsIHRocmVlLlxuICpcbiAqIE5PIERPTUFJTiBDUk9TUy1DSEVDSywgZGVsaWJlcmF0ZWx5IOKAlCB0aGlzIGlzIHRoZSBvbmUgbWlycm9yIHRoYXQgZG9lc1xuICogbm90IGNhcnJ5IHRoZSByZXNvdXJjZeKGkmRvbWFpbiBtYXAsIGFuZCBpdCBpcyBub3QgYW4gb3ZlcnNpZ2h0OlxuICpcbiAqICAgLSBJdCB3b3VsZCBiZSBjaGVja2luZyB0aGUgd3JvbmcgdGhpbmcuIFRoZSBwcm9kdWNpbmcgU0RLIGRlcml2ZXNcbiAqICAgICBgZG9tYWluYCBmcm9tIGB0eXBlYCBhbmQgdGhlIGNvbnN1bWluZyBzZXJ2aWNlIHJlLXZhbGlkYXRlcyB0aGUgcGFpclxuICogICAgIGJlZm9yZSBpdCBldmVyIHB1Ymxpc2hlcyB0byBhIGNoYW5uZWwuIEJ5IHRoZSB0aW1lIGFuIGVudmVsb3BlXG4gKiAgICAgcmVhY2hlcyBhIGJyb3dzZXIgdGhlIHBhaXIgaGFzIGJlZW4gY2hlY2tlZCB0d2ljZSBzZXJ2ZXItc2lkZTsgYVxuICogICAgIHRoaXJkIGNoZWNrIGNhbiBvbmx5IHJlamVjdCwgbmV2ZXIgcmVwYWlyLlxuICogICAtIEl0IHdvdWxkIHJvdCBkYW5nZXJvdXNseS4gQSBicm93c2VyIGJ1bmRsZSBzaXRzIGluIHNvbWVvbmUncyBjYWNoZVxuICogICAgIGZvciB3ZWVrcy4gQmFraW5nIHRvZGF5J3MgbWFwIGludG8gaXQgbWVhbnMgdGhhdCB0aGUgZGF5IGEgbmV3XG4gKiAgICAgZG9tYWluIHNoaXBzLCBldmVyeSB1bi1yZWZyZXNoZWQgdGFiIHN0YXJ0cyBzaWxlbnRseSBkcm9wcGluZyBsaXZlXG4gKiAgICAgZXZlbnRzIGZvciBpdCDigJQgYSBzdGFsZSBjbGllbnQgcmVqZWN0aW5nIHRoZSBmdXR1cmUsIHdoaWNoIGlzIHRoZVxuICogICAgIGZhaWx1cmUgbW9kZSBgdmFsaWRhdGVQbGF0Zm9ybUV2ZW50YCBpcyBjYXJlZnVsIHRvIGF2b2lkIGV2ZW5cbiAqICAgICBzZXJ2ZXItc2lkZS5cbiAqXG4gKiBTbyB0aGUgZ3VhcmQgaXMgc3RydWN0dXJhbCBvbmx5OiBzaGFwZXMgYW5kIHR5cGVzLCBub3QgbWVtYmVyc2hpcC5cbiAqIGBwYWNrYWdlcy9wbGF0Zm9ybS1zZGsvdGVzdC9ldmVudHMvZG9tYWluLW1hcC50ZXN0LnRzYCBhc3NlcnRzIHRoYXRcbiAqIGV2ZXJ5IGNhbm9uaWNhbCBkb21haW4gaXMgYWNjZXB0ZWQsIGFuZCB0aGF0IGFuIHVubGlzdGVkIG9uZSBpcyB0b28uXG4gKi9cblxuLyoqIFVMSUQ6IENyb2NrZm9yZCBiYXNlMzIsIDI2IGNoYXJzLCBleGNsdWRlcyBJL0wvTy9VLiAqL1xuY29uc3QgVUxJRF9SRSA9IC9eWzAtOUEtSEpLTU5QLVRWLVpdezI2fSQvO1xuXG4vKiogRG90dGVkIGByZXNvdXJjZS5vcGVyYXRpb25gLCBlLmcuIGBvcmRlci5zdGF0dXNfY2hhbmdlZGAuICovXG5jb25zdCBFVkVOVF9UWVBFX1JFID0gL15bYS16XVthLXowLTlfXSpcXC5bYS16XVthLXowLTlfXSokLztcblxuLyoqIFRoZSBwbGF0Zm9ybSBldmVudC1idXMgZW52ZWxvcGUsIGFzIGl0IGFycml2ZXMgYXQgYSBicm93c2VyLiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZWFsdGltZUV2ZW50IHtcbiAgaWQ6IHN0cmluZztcbiAgdHlwZTogc3RyaW5nO1xuICBkb21haW46IHN0cmluZztcbiAgc291cmNlOiBzdHJpbmc7XG4gIHZlcnNpb246IG51bWJlcjtcbiAgb2NjdXJyZWRBdDogc3RyaW5nO1xuICB0ZW5hbnRJZDogc3RyaW5nO1xuICBkYXRhOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbn1cblxuLyoqXG4gKiBTdHJ1Y3R1cmFsIGNoZWNrIG9uIGFuIHVudHJ1c3RlZCBwYXlsb2FkLiBSZXR1cm5zIHRoZSBldmVudCBvciBudWxsIOKAlFxuICogbmV2ZXIgdGhyb3dzLCBiZWNhdXNlIGEgc2luZ2xlIG1hbGZvcm1lZCBmcmFtZSBtdXN0IG5vdCB0ZWFyIGRvd24gYVxuICogbGl2ZSBzdWJzY3JpcHRpb24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhc1JlYWx0aW1lRXZlbnQodmFsdWU6IHVua25vd24pOiBSZWFsdGltZUV2ZW50IHwgbnVsbCB7XG4gIGlmICh0eXBlb2YgdmFsdWUgIT09ICdvYmplY3QnIHx8IHZhbHVlID09PSBudWxsKSByZXR1cm4gbnVsbDtcbiAgY29uc3QgdiA9IHZhbHVlIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICBpZiAodHlwZW9mIHYuaWQgIT09ICdzdHJpbmcnIHx8ICFVTElEX1JFLnRlc3Qodi5pZCkpIHJldHVybiBudWxsO1xuICBpZiAodHlwZW9mIHYudHlwZSAhPT0gJ3N0cmluZycgfHwgIUVWRU5UX1RZUEVfUkUudGVzdCh2LnR5cGUpKSByZXR1cm4gbnVsbDtcbiAgaWYgKHR5cGVvZiB2LmRvbWFpbiAhPT0gJ3N0cmluZycgfHwgdi5kb21haW4ubGVuZ3RoID09PSAwKSByZXR1cm4gbnVsbDtcbiAgaWYgKHR5cGVvZiB2LnNvdXJjZSAhPT0gJ3N0cmluZycgfHwgdi5zb3VyY2UubGVuZ3RoID09PSAwKSByZXR1cm4gbnVsbDtcbiAgaWYgKHR5cGVvZiB2LnZlcnNpb24gIT09ICdudW1iZXInIHx8ICFOdW1iZXIuaXNJbnRlZ2VyKHYudmVyc2lvbikgfHwgdi52ZXJzaW9uIDwgMSkgcmV0dXJuIG51bGw7XG4gIGlmICh0eXBlb2Ygdi5vY2N1cnJlZEF0ICE9PSAnc3RyaW5nJyB8fCB2Lm9jY3VycmVkQXQubGVuZ3RoID09PSAwKSByZXR1cm4gbnVsbDtcbiAgaWYgKHR5cGVvZiB2LnRlbmFudElkICE9PSAnc3RyaW5nJyB8fCB2LnRlbmFudElkLmxlbmd0aCA9PT0gMCkgcmV0dXJuIG51bGw7XG4gIGlmICh0eXBlb2Ygdi5kYXRhICE9PSAnb2JqZWN0JyB8fCB2LmRhdGEgPT09IG51bGwgfHwgQXJyYXkuaXNBcnJheSh2LmRhdGEpKSByZXR1cm4gbnVsbDtcbiAgcmV0dXJuIHYgYXMgdW5rbm93biBhcyBSZWFsdGltZUV2ZW50O1xufVxuXG4vKipcbiAqIE9yZGVyaW5nL2RlZHVwZSBndWFyZCwga2V5ZWQgb24gdGhlIGVudmVsb3BlJ3MgbW9ub3RvbmljIFVMSUQuXG4gKlxuICogVUxJRHMgYXJlIGxleGljb2dyYXBoaWNhbGx5IHNvcnRhYmxlIGJ5IG1pbnQgdGltZSwgc28gYSBwbGFpbiBzdHJpbmdcbiAqIGNvbXBhcmUgb3JkZXJzIGV2ZW50cyB3aXRob3V0IHBhcnNpbmcgdGltZXN0YW1wcyBvciBrbm93aW5nIGFueXRoaW5nXG4gKiBhYm91dCB0aGUgZG9tYWluLiBUaGlzIGlzIHdoYXQgbWFrZXMgYXQtbGVhc3Qtb25jZSBkZWxpdmVyeSBhbmQgdGhlXG4gKiBzbmFwc2hvdC9zdHJlYW0gcmFjZSBib3RoIGhhcm1sZXNzOiB3aGljaGV2ZXIgYXJyaXZlcyBzZWNvbmQgd2l0aCBhblxuICogaWQgPD0gdGhlIG9uZSBhbHJlYWR5IHJlbmRlcmVkIGlzIGRyb3BwZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc05ld2VyKGNhbmRpZGF0ZUlkOiBzdHJpbmcsIHNlZW5JZDogc3RyaW5nIHwgdW5kZWZpbmVkKTogYm9vbGVhbiB7XG4gIGlmIChzZWVuSWQgPT09IHVuZGVmaW5lZCkgcmV0dXJuIHRydWU7XG4gIHJldHVybiBjYW5kaWRhdGVJZCA+IHNlZW5JZDtcbn1cbiJdfQ==
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `@uengage.io/platform-sdk/realtime` — browser-safe realtime client.
3
+ *
4
+ * A thin client for the platform's AppSync Events API: it owns the wire
5
+ * protocol, the keepalive watchdog, reconnect with jittered backoff,
6
+ * resubscribe, token refresh, snapshot catch-up on every (re)connect,
7
+ * and ULID-based ordering/dedupe. Apps own rendering and channel names.
8
+ *
9
+ * ```ts
10
+ * import { realtime } from '@uengage.io/platform-sdk/realtime';
11
+ *
12
+ * const client = realtime.connect({
13
+ * env: 'prod',
14
+ * getToken: () => session.jwt(),
15
+ * });
16
+ *
17
+ * const sub = client.subscribe(`/orders/${orderId}`, {
18
+ * onUpdate(event, meta) { render(event.data); },
19
+ * onError(err) { showOffline(err); },
20
+ * });
21
+ *
22
+ * sub.unsubscribe();
23
+ * ```
24
+ *
25
+ * This subpath is deliberately isolated from the SDK's main entry: zero
26
+ * runtime dependencies, no Node built-ins, no server auth code, so it
27
+ * stays a few KB in a frontend bundle and works unchanged in React
28
+ * Native. Do not import from `../` here.
29
+ */
30
+ import { connect } from './connect';
31
+ export declare const realtime: {
32
+ connect: typeof connect;
33
+ };
34
+ export { connect, jitteredBackoff, type RealtimeClient, type RealtimeConnectOptions, type Subscription, type SubscribeOptions, type UpdateMeta, type MinimalWebSocket, type WebSocketCtor, } from './connect';
35
+ export { asRealtimeEvent, isNewer, type RealtimeEvent } from './envelope';
36
+ export { API_BASE, endpointsFor, type RealtimeEndpoints, type RealtimeEnv } from './endpoints';
37
+ export { EVENT_WS_SUBPROTOCOL, authHeader, authSubprotocol, base64UrlEncode } from './protocol';
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.base64UrlEncode = exports.authSubprotocol = exports.authHeader = exports.EVENT_WS_SUBPROTOCOL = exports.endpointsFor = exports.API_BASE = exports.isNewer = exports.asRealtimeEvent = exports.jitteredBackoff = exports.connect = exports.realtime = void 0;
4
+ /**
5
+ * `@uengage.io/platform-sdk/realtime` — browser-safe realtime client.
6
+ *
7
+ * A thin client for the platform's AppSync Events API: it owns the wire
8
+ * protocol, the keepalive watchdog, reconnect with jittered backoff,
9
+ * resubscribe, token refresh, snapshot catch-up on every (re)connect,
10
+ * and ULID-based ordering/dedupe. Apps own rendering and channel names.
11
+ *
12
+ * ```ts
13
+ * import { realtime } from '@uengage.io/platform-sdk/realtime';
14
+ *
15
+ * const client = realtime.connect({
16
+ * env: 'prod',
17
+ * getToken: () => session.jwt(),
18
+ * });
19
+ *
20
+ * const sub = client.subscribe(`/orders/${orderId}`, {
21
+ * onUpdate(event, meta) { render(event.data); },
22
+ * onError(err) { showOffline(err); },
23
+ * });
24
+ *
25
+ * sub.unsubscribe();
26
+ * ```
27
+ *
28
+ * This subpath is deliberately isolated from the SDK's main entry: zero
29
+ * runtime dependencies, no Node built-ins, no server auth code, so it
30
+ * stays a few KB in a frontend bundle and works unchanged in React
31
+ * Native. Do not import from `../` here.
32
+ */
33
+ const connect_1 = require("./connect");
34
+ exports.realtime = { connect: connect_1.connect };
35
+ var connect_2 = require("./connect");
36
+ Object.defineProperty(exports, "connect", { enumerable: true, get: function () { return connect_2.connect; } });
37
+ Object.defineProperty(exports, "jitteredBackoff", { enumerable: true, get: function () { return connect_2.jitteredBackoff; } });
38
+ var envelope_1 = require("./envelope");
39
+ Object.defineProperty(exports, "asRealtimeEvent", { enumerable: true, get: function () { return envelope_1.asRealtimeEvent; } });
40
+ Object.defineProperty(exports, "isNewer", { enumerable: true, get: function () { return envelope_1.isNewer; } });
41
+ var endpoints_1 = require("./endpoints");
42
+ Object.defineProperty(exports, "API_BASE", { enumerable: true, get: function () { return endpoints_1.API_BASE; } });
43
+ Object.defineProperty(exports, "endpointsFor", { enumerable: true, get: function () { return endpoints_1.endpointsFor; } });
44
+ var protocol_1 = require("./protocol");
45
+ Object.defineProperty(exports, "EVENT_WS_SUBPROTOCOL", { enumerable: true, get: function () { return protocol_1.EVENT_WS_SUBPROTOCOL; } });
46
+ Object.defineProperty(exports, "authHeader", { enumerable: true, get: function () { return protocol_1.authHeader; } });
47
+ Object.defineProperty(exports, "authSubprotocol", { enumerable: true, get: function () { return protocol_1.authSubprotocol; } });
48
+ Object.defineProperty(exports, "base64UrlEncode", { enumerable: true, get: function () { return protocol_1.base64UrlEncode; } });
49
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcmVhbHRpbWUvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E0Qkc7QUFDSCx1Q0FBb0M7QUFFdkIsUUFBQSxRQUFRLEdBQUcsRUFBRSxPQUFPLEVBQVAsaUJBQU8sRUFBRSxDQUFDO0FBRXBDLHFDQVVtQjtBQVRqQixrR0FBQSxPQUFPLE9BQUE7QUFDUCwwR0FBQSxlQUFlLE9BQUE7QUFTakIsdUNBQTBFO0FBQWpFLDJHQUFBLGVBQWUsT0FBQTtBQUFFLG1HQUFBLE9BQU8sT0FBQTtBQUNqQyx5Q0FBK0Y7QUFBdEYscUdBQUEsUUFBUSxPQUFBO0FBQUUseUdBQUEsWUFBWSxPQUFBO0FBQy9CLHVDQUFnRztBQUF2RixnSEFBQSxvQkFBb0IsT0FBQTtBQUFFLHNHQUFBLFVBQVUsT0FBQTtBQUFFLDJHQUFBLGVBQWUsT0FBQTtBQUFFLDJHQUFBLGVBQWUsT0FBQSIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogYEB1ZW5nYWdlLmlvL3BsYXRmb3JtLXNkay9yZWFsdGltZWAg4oCUIGJyb3dzZXItc2FmZSByZWFsdGltZSBjbGllbnQuXG4gKlxuICogQSB0aGluIGNsaWVudCBmb3IgdGhlIHBsYXRmb3JtJ3MgQXBwU3luYyBFdmVudHMgQVBJOiBpdCBvd25zIHRoZSB3aXJlXG4gKiBwcm90b2NvbCwgdGhlIGtlZXBhbGl2ZSB3YXRjaGRvZywgcmVjb25uZWN0IHdpdGggaml0dGVyZWQgYmFja29mZixcbiAqIHJlc3Vic2NyaWJlLCB0b2tlbiByZWZyZXNoLCBzbmFwc2hvdCBjYXRjaC11cCBvbiBldmVyeSAocmUpY29ubmVjdCxcbiAqIGFuZCBVTElELWJhc2VkIG9yZGVyaW5nL2RlZHVwZS4gQXBwcyBvd24gcmVuZGVyaW5nIGFuZCBjaGFubmVsIG5hbWVzLlxuICpcbiAqIGBgYHRzXG4gKiBpbXBvcnQgeyByZWFsdGltZSB9IGZyb20gJ0B1ZW5nYWdlLmlvL3BsYXRmb3JtLXNkay9yZWFsdGltZSc7XG4gKlxuICogY29uc3QgY2xpZW50ID0gcmVhbHRpbWUuY29ubmVjdCh7XG4gKiAgIGVudjogJ3Byb2QnLFxuICogICBnZXRUb2tlbjogKCkgPT4gc2Vzc2lvbi5qd3QoKSxcbiAqIH0pO1xuICpcbiAqIGNvbnN0IHN1YiA9IGNsaWVudC5zdWJzY3JpYmUoYC9vcmRlcnMvJHtvcmRlcklkfWAsIHtcbiAqICAgb25VcGRhdGUoZXZlbnQsIG1ldGEpIHsgcmVuZGVyKGV2ZW50LmRhdGEpOyB9LFxuICogICBvbkVycm9yKGVycikgeyBzaG93T2ZmbGluZShlcnIpOyB9LFxuICogfSk7XG4gKlxuICogc3ViLnVuc3Vic2NyaWJlKCk7XG4gKiBgYGBcbiAqXG4gKiBUaGlzIHN1YnBhdGggaXMgZGVsaWJlcmF0ZWx5IGlzb2xhdGVkIGZyb20gdGhlIFNESydzIG1haW4gZW50cnk6IHplcm9cbiAqIHJ1bnRpbWUgZGVwZW5kZW5jaWVzLCBubyBOb2RlIGJ1aWx0LWlucywgbm8gc2VydmVyIGF1dGggY29kZSwgc28gaXRcbiAqIHN0YXlzIGEgZmV3IEtCIGluIGEgZnJvbnRlbmQgYnVuZGxlIGFuZCB3b3JrcyB1bmNoYW5nZWQgaW4gUmVhY3RcbiAqIE5hdGl2ZS4gRG8gbm90IGltcG9ydCBmcm9tIGAuLi9gIGhlcmUuXG4gKi9cbmltcG9ydCB7IGNvbm5lY3QgfSBmcm9tICcuL2Nvbm5lY3QnO1xuXG5leHBvcnQgY29uc3QgcmVhbHRpbWUgPSB7IGNvbm5lY3QgfTtcblxuZXhwb3J0IHtcbiAgY29ubmVjdCxcbiAgaml0dGVyZWRCYWNrb2ZmLFxuICB0eXBlIFJlYWx0aW1lQ2xpZW50LFxuICB0eXBlIFJlYWx0aW1lQ29ubmVjdE9wdGlvbnMsXG4gIHR5cGUgU3Vic2NyaXB0aW9uLFxuICB0eXBlIFN1YnNjcmliZU9wdGlvbnMsXG4gIHR5cGUgVXBkYXRlTWV0YSxcbiAgdHlwZSBNaW5pbWFsV2ViU29ja2V0LFxuICB0eXBlIFdlYlNvY2tldEN0b3IsXG59IGZyb20gJy4vY29ubmVjdCc7XG5leHBvcnQgeyBhc1JlYWx0aW1lRXZlbnQsIGlzTmV3ZXIsIHR5cGUgUmVhbHRpbWVFdmVudCB9IGZyb20gJy4vZW52ZWxvcGUnO1xuZXhwb3J0IHsgQVBJX0JBU0UsIGVuZHBvaW50c0ZvciwgdHlwZSBSZWFsdGltZUVuZHBvaW50cywgdHlwZSBSZWFsdGltZUVudiB9IGZyb20gJy4vZW5kcG9pbnRzJztcbmV4cG9ydCB7IEVWRU5UX1dTX1NVQlBST1RPQ09MLCBhdXRoSGVhZGVyLCBhdXRoU3VicHJvdG9jb2wsIGJhc2U2NFVybEVuY29kZSB9IGZyb20gJy4vcHJvdG9jb2wnO1xuIl19
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The AppSync Events WebSocket wire protocol, as documented at
3
+ * https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-websocket-protocol.html
4
+ *
5
+ * Everything here is pure data + string manipulation: no I/O, no
6
+ * dependencies, no Node built-ins. The socket lifecycle lives in
7
+ * `connect.ts`.
8
+ */
9
+ /** WebSocket subprotocol that selects the Events (not GraphQL) protocol. */
10
+ export declare const EVENT_WS_SUBPROTOCOL = "aws-appsync-event-ws";
11
+ /**
12
+ * AppSync takes connection auth as a *subprotocol*, because a browser
13
+ * WebSocket cannot set request headers. The auth header object is
14
+ * JSON-encoded, base64url-encoded, and prefixed with `header-`.
15
+ */
16
+ export declare function authSubprotocol(header: Record<string, string>): string;
17
+ /**
18
+ * The auth object AppSync expects, for both the connection handshake and
19
+ * each `subscribe` frame. `host` must be the API's HTTP domain (not the
20
+ * realtime domain) — AppSync validates the signature/token against it.
21
+ */
22
+ export declare function authHeader(httpHost: string, token: string): Record<string, string>;
23
+ /**
24
+ * base64url with padding stripped. Uses `btoa` over UTF-8 bytes rather
25
+ * than `Buffer`, which does not exist in a browser.
26
+ */
27
+ export declare function base64UrlEncode(input: string): string;
28
+ export interface ConnectionAckFrame {
29
+ type: 'connection_ack';
30
+ /**
31
+ * How long the server will tolerate silence before it closes the
32
+ * connection. The client watchdog uses this rather than a hard-coded
33
+ * 60s so a server-side change doesn't strand us.
34
+ */
35
+ connectionTimeoutMs?: number;
36
+ }
37
+ export interface DataFrame {
38
+ type: 'data';
39
+ id: string;
40
+ /**
41
+ * The published payload, JSON-encoded *inside* the already-JSON frame.
42
+ * Double-encoded on the wire; `parseDataFrameEvent` unwraps both.
43
+ */
44
+ event: string;
45
+ }
46
+ export interface ErrorFrame {
47
+ type: 'connection_error' | 'subscribe_error' | 'publish_error';
48
+ id?: string;
49
+ errors?: Array<{
50
+ errorType?: string;
51
+ message?: string;
52
+ }>;
53
+ }
54
+ export type ServerFrame = ConnectionAckFrame | DataFrame | ErrorFrame | {
55
+ type: 'ka';
56
+ } | {
57
+ type: 'subscribe_success';
58
+ id: string;
59
+ } | {
60
+ type: 'unsubscribe_success';
61
+ id: string;
62
+ } | {
63
+ type: string;
64
+ [k: string]: unknown;
65
+ };
66
+ export declare function parseServerFrame(raw: string): ServerFrame | null;
67
+ /** Unwrap the inner JSON of a `data` frame. Null if it isn't parseable. */
68
+ export declare function parseDataFrameEvent(frame: DataFrame): unknown;
69
+ /** Human-readable text from an error frame, for `onError`. */
70
+ export declare function errorFrameMessage(frame: ErrorFrame): string;
71
+ export declare function connectionInitFrame(): string;
72
+ export declare function subscribeFrame(id: string, channel: string, auth: Record<string, string>): string;
73
+ export declare function unsubscribeFrame(id: string): string;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ /**
3
+ * The AppSync Events WebSocket wire protocol, as documented at
4
+ * https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-websocket-protocol.html
5
+ *
6
+ * Everything here is pure data + string manipulation: no I/O, no
7
+ * dependencies, no Node built-ins. The socket lifecycle lives in
8
+ * `connect.ts`.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.EVENT_WS_SUBPROTOCOL = void 0;
12
+ exports.authSubprotocol = authSubprotocol;
13
+ exports.authHeader = authHeader;
14
+ exports.base64UrlEncode = base64UrlEncode;
15
+ exports.parseServerFrame = parseServerFrame;
16
+ exports.parseDataFrameEvent = parseDataFrameEvent;
17
+ exports.errorFrameMessage = errorFrameMessage;
18
+ exports.connectionInitFrame = connectionInitFrame;
19
+ exports.subscribeFrame = subscribeFrame;
20
+ exports.unsubscribeFrame = unsubscribeFrame;
21
+ /** WebSocket subprotocol that selects the Events (not GraphQL) protocol. */
22
+ exports.EVENT_WS_SUBPROTOCOL = 'aws-appsync-event-ws';
23
+ /**
24
+ * AppSync takes connection auth as a *subprotocol*, because a browser
25
+ * WebSocket cannot set request headers. The auth header object is
26
+ * JSON-encoded, base64url-encoded, and prefixed with `header-`.
27
+ */
28
+ function authSubprotocol(header) {
29
+ return `header-${base64UrlEncode(JSON.stringify(header))}`;
30
+ }
31
+ /**
32
+ * The auth object AppSync expects, for both the connection handshake and
33
+ * each `subscribe` frame. `host` must be the API's HTTP domain (not the
34
+ * realtime domain) — AppSync validates the signature/token against it.
35
+ */
36
+ function authHeader(httpHost, token) {
37
+ return { host: httpHost, Authorization: token };
38
+ }
39
+ /**
40
+ * base64url with padding stripped. Uses `btoa` over UTF-8 bytes rather
41
+ * than `Buffer`, which does not exist in a browser.
42
+ */
43
+ function base64UrlEncode(input) {
44
+ const bytes = new TextEncoder().encode(input);
45
+ let binary = '';
46
+ for (let i = 0; i < bytes.length; i++)
47
+ binary += String.fromCharCode(bytes[i]);
48
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
49
+ }
50
+ function parseServerFrame(raw) {
51
+ try {
52
+ const parsed = JSON.parse(raw);
53
+ if (typeof parsed !== 'object' || parsed === null)
54
+ return null;
55
+ const frame = parsed;
56
+ if (typeof frame.type !== 'string')
57
+ return null;
58
+ return parsed;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /** Unwrap the inner JSON of a `data` frame. Null if it isn't parseable. */
65
+ function parseDataFrameEvent(frame) {
66
+ try {
67
+ return JSON.parse(frame.event);
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ /** Human-readable text from an error frame, for `onError`. */
74
+ function errorFrameMessage(frame) {
75
+ const parts = (frame.errors ?? [])
76
+ .map((e) => e.message ?? e.errorType)
77
+ .filter((m) => typeof m === 'string' && m.length > 0);
78
+ return parts.length > 0 ? parts.join('; ') : frame.type;
79
+ }
80
+ function connectionInitFrame() {
81
+ return JSON.stringify({ type: 'connection_init' });
82
+ }
83
+ function subscribeFrame(id, channel, auth) {
84
+ return JSON.stringify({ type: 'subscribe', id, channel, authorization: auth });
85
+ }
86
+ function unsubscribeFrame(id) {
87
+ return JSON.stringify({ type: 'unsubscribe', id });
88
+ }
89
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdG9jb2wuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcmVhbHRpbWUvcHJvdG9jb2wudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7O0dBT0c7OztBQVVILDBDQUVDO0FBT0QsZ0NBRUM7QUFNRCwwQ0FLQztBQXVDRCw0Q0FVQztBQUdELGtEQU1DO0FBR0QsOENBS0M7QUFFRCxrREFFQztBQUVELHdDQUVDO0FBRUQsNENBRUM7QUE1R0QsNEVBQTRFO0FBQy9ELFFBQUEsb0JBQW9CLEdBQUcsc0JBQXNCLENBQUM7QUFFM0Q7Ozs7R0FJRztBQUNILFNBQWdCLGVBQWUsQ0FBQyxNQUE4QjtJQUM1RCxPQUFPLFVBQVUsZUFBZSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxDQUFDO0FBQzdELENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsU0FBZ0IsVUFBVSxDQUFDLFFBQWdCLEVBQUUsS0FBYTtJQUN4RCxPQUFPLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUUsS0FBSyxFQUFFLENBQUM7QUFDbEQsQ0FBQztBQUVEOzs7R0FHRztBQUNILFNBQWdCLGVBQWUsQ0FBQyxLQUFhO0lBQzNDLE1BQU0sS0FBSyxHQUFHLElBQUksV0FBVyxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzlDLElBQUksTUFBTSxHQUFHLEVBQUUsQ0FBQztJQUNoQixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUU7UUFBRSxNQUFNLElBQUksTUFBTSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUMvRSxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztBQUNqRixDQUFDO0FBdUNELFNBQWdCLGdCQUFnQixDQUFDLEdBQVc7SUFDMUMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQVksQ0FBQztRQUMxQyxJQUFJLE9BQU8sTUFBTSxLQUFLLFFBQVEsSUFBSSxNQUFNLEtBQUssSUFBSTtZQUFFLE9BQU8sSUFBSSxDQUFDO1FBQy9ELE1BQU0sS0FBSyxHQUFHLE1BQTRCLENBQUM7UUFDM0MsSUFBSSxPQUFPLEtBQUssQ0FBQyxJQUFJLEtBQUssUUFBUTtZQUFFLE9BQU8sSUFBSSxDQUFDO1FBQ2hELE9BQU8sTUFBcUIsQ0FBQztJQUMvQixDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVELDJFQUEyRTtBQUMzRSxTQUFnQixtQkFBbUIsQ0FBQyxLQUFnQjtJQUNsRCxJQUFJLENBQUM7UUFDSCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBWSxDQUFDO0lBQzVDLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7QUFDSCxDQUFDO0FBRUQsOERBQThEO0FBQzlELFNBQWdCLGlCQUFpQixDQUFDLEtBQWlCO0lBQ2pELE1BQU0sS0FBSyxHQUFHLENBQUMsS0FBSyxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUM7U0FDL0IsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxJQUFJLENBQUMsQ0FBQyxTQUFTLENBQUM7U0FDcEMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFlLEVBQUUsQ0FBQyxPQUFPLENBQUMsS0FBSyxRQUFRLElBQUksQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNyRSxPQUFPLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDO0FBQzFELENBQUM7QUFFRCxTQUFnQixtQkFBbUI7SUFDakMsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsSUFBSSxFQUFFLGlCQUFpQixFQUFFLENBQUMsQ0FBQztBQUNyRCxDQUFDO0FBRUQsU0FBZ0IsY0FBYyxDQUFDLEVBQVUsRUFBRSxPQUFlLEVBQUUsSUFBNEI7SUFDdEYsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxFQUFFLEVBQUUsT0FBTyxFQUFFLGFBQWEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0FBQ2pGLENBQUM7QUFFRCxTQUFnQixnQkFBZ0IsQ0FBQyxFQUFVO0lBQ3pDLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLElBQUksRUFBRSxhQUFhLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUNyRCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGUgQXBwU3luYyBFdmVudHMgV2ViU29ja2V0IHdpcmUgcHJvdG9jb2wsIGFzIGRvY3VtZW50ZWQgYXRcbiAqIGh0dHBzOi8vZG9jcy5hd3MuYW1hem9uLmNvbS9hcHBzeW5jL2xhdGVzdC9ldmVudGFwaS9ldmVudC1hcGktd2Vic29ja2V0LXByb3RvY29sLmh0bWxcbiAqXG4gKiBFdmVyeXRoaW5nIGhlcmUgaXMgcHVyZSBkYXRhICsgc3RyaW5nIG1hbmlwdWxhdGlvbjogbm8gSS9PLCBub1xuICogZGVwZW5kZW5jaWVzLCBubyBOb2RlIGJ1aWx0LWlucy4gVGhlIHNvY2tldCBsaWZlY3ljbGUgbGl2ZXMgaW5cbiAqIGBjb25uZWN0LnRzYC5cbiAqL1xuXG4vKiogV2ViU29ja2V0IHN1YnByb3RvY29sIHRoYXQgc2VsZWN0cyB0aGUgRXZlbnRzIChub3QgR3JhcGhRTCkgcHJvdG9jb2wuICovXG5leHBvcnQgY29uc3QgRVZFTlRfV1NfU1VCUFJPVE9DT0wgPSAnYXdzLWFwcHN5bmMtZXZlbnQtd3MnO1xuXG4vKipcbiAqIEFwcFN5bmMgdGFrZXMgY29ubmVjdGlvbiBhdXRoIGFzIGEgKnN1YnByb3RvY29sKiwgYmVjYXVzZSBhIGJyb3dzZXJcbiAqIFdlYlNvY2tldCBjYW5ub3Qgc2V0IHJlcXVlc3QgaGVhZGVycy4gVGhlIGF1dGggaGVhZGVyIG9iamVjdCBpc1xuICogSlNPTi1lbmNvZGVkLCBiYXNlNjR1cmwtZW5jb2RlZCwgYW5kIHByZWZpeGVkIHdpdGggYGhlYWRlci1gLlxuICovXG5leHBvcnQgZnVuY3Rpb24gYXV0aFN1YnByb3RvY29sKGhlYWRlcjogUmVjb3JkPHN0cmluZywgc3RyaW5nPik6IHN0cmluZyB7XG4gIHJldHVybiBgaGVhZGVyLSR7YmFzZTY0VXJsRW5jb2RlKEpTT04uc3RyaW5naWZ5KGhlYWRlcikpfWA7XG59XG5cbi8qKlxuICogVGhlIGF1dGggb2JqZWN0IEFwcFN5bmMgZXhwZWN0cywgZm9yIGJvdGggdGhlIGNvbm5lY3Rpb24gaGFuZHNoYWtlIGFuZFxuICogZWFjaCBgc3Vic2NyaWJlYCBmcmFtZS4gYGhvc3RgIG11c3QgYmUgdGhlIEFQSSdzIEhUVFAgZG9tYWluIChub3QgdGhlXG4gKiByZWFsdGltZSBkb21haW4pIOKAlCBBcHBTeW5jIHZhbGlkYXRlcyB0aGUgc2lnbmF0dXJlL3Rva2VuIGFnYWluc3QgaXQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhdXRoSGVhZGVyKGh0dHBIb3N0OiBzdHJpbmcsIHRva2VuOiBzdHJpbmcpOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+IHtcbiAgcmV0dXJuIHsgaG9zdDogaHR0cEhvc3QsIEF1dGhvcml6YXRpb246IHRva2VuIH07XG59XG5cbi8qKlxuICogYmFzZTY0dXJsIHdpdGggcGFkZGluZyBzdHJpcHBlZC4gVXNlcyBgYnRvYWAgb3ZlciBVVEYtOCBieXRlcyByYXRoZXJcbiAqIHRoYW4gYEJ1ZmZlcmAsIHdoaWNoIGRvZXMgbm90IGV4aXN0IGluIGEgYnJvd3Nlci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJhc2U2NFVybEVuY29kZShpbnB1dDogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3QgYnl0ZXMgPSBuZXcgVGV4dEVuY29kZXIoKS5lbmNvZGUoaW5wdXQpO1xuICBsZXQgYmluYXJ5ID0gJyc7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgYnl0ZXMubGVuZ3RoOyBpKyspIGJpbmFyeSArPSBTdHJpbmcuZnJvbUNoYXJDb2RlKGJ5dGVzW2ldKTtcbiAgcmV0dXJuIGJ0b2EoYmluYXJ5KS5yZXBsYWNlKC9cXCsvZywgJy0nKS5yZXBsYWNlKC9cXC8vZywgJ18nKS5yZXBsYWNlKC89KyQvLCAnJyk7XG59XG5cbi8vIOKUgOKUgOKUgCBGcmFtZXMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbmV4cG9ydCBpbnRlcmZhY2UgQ29ubmVjdGlvbkFja0ZyYW1lIHtcbiAgdHlwZTogJ2Nvbm5lY3Rpb25fYWNrJztcbiAgLyoqXG4gICAqIEhvdyBsb25nIHRoZSBzZXJ2ZXIgd2lsbCB0b2xlcmF0ZSBzaWxlbmNlIGJlZm9yZSBpdCBjbG9zZXMgdGhlXG4gICAqIGNvbm5lY3Rpb24uIFRoZSBjbGllbnQgd2F0Y2hkb2cgdXNlcyB0aGlzIHJhdGhlciB0aGFuIGEgaGFyZC1jb2RlZFxuICAgKiA2MHMgc28gYSBzZXJ2ZXItc2lkZSBjaGFuZ2UgZG9lc24ndCBzdHJhbmQgdXMuXG4gICAqL1xuICBjb25uZWN0aW9uVGltZW91dE1zPzogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIERhdGFGcmFtZSB7XG4gIHR5cGU6ICdkYXRhJztcbiAgaWQ6IHN0cmluZztcbiAgLyoqXG4gICAqIFRoZSBwdWJsaXNoZWQgcGF5bG9hZCwgSlNPTi1lbmNvZGVkICppbnNpZGUqIHRoZSBhbHJlYWR5LUpTT04gZnJhbWUuXG4gICAqIERvdWJsZS1lbmNvZGVkIG9uIHRoZSB3aXJlOyBgcGFyc2VEYXRhRnJhbWVFdmVudGAgdW53cmFwcyBib3RoLlxuICAgKi9cbiAgZXZlbnQ6IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBFcnJvckZyYW1lIHtcbiAgdHlwZTogJ2Nvbm5lY3Rpb25fZXJyb3InIHwgJ3N1YnNjcmliZV9lcnJvcicgfCAncHVibGlzaF9lcnJvcic7XG4gIGlkPzogc3RyaW5nO1xuICBlcnJvcnM/OiBBcnJheTx7IGVycm9yVHlwZT86IHN0cmluZzsgbWVzc2FnZT86IHN0cmluZyB9Pjtcbn1cblxuZXhwb3J0IHR5cGUgU2VydmVyRnJhbWUgPVxuICB8IENvbm5lY3Rpb25BY2tGcmFtZVxuICB8IERhdGFGcmFtZVxuICB8IEVycm9yRnJhbWVcbiAgfCB7IHR5cGU6ICdrYScgfVxuICB8IHsgdHlwZTogJ3N1YnNjcmliZV9zdWNjZXNzJzsgaWQ6IHN0cmluZyB9XG4gIHwgeyB0eXBlOiAndW5zdWJzY3JpYmVfc3VjY2Vzcyc7IGlkOiBzdHJpbmcgfVxuICB8IHsgdHlwZTogc3RyaW5nOyBbazogc3RyaW5nXTogdW5rbm93biB9O1xuXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VTZXJ2ZXJGcmFtZShyYXc6IHN0cmluZyk6IFNlcnZlckZyYW1lIHwgbnVsbCB7XG4gIHRyeSB7XG4gICAgY29uc3QgcGFyc2VkID0gSlNPTi5wYXJzZShyYXcpIGFzIHVua25vd247XG4gICAgaWYgKHR5cGVvZiBwYXJzZWQgIT09ICdvYmplY3QnIHx8IHBhcnNlZCA9PT0gbnVsbCkgcmV0dXJuIG51bGw7XG4gICAgY29uc3QgZnJhbWUgPSBwYXJzZWQgYXMgeyB0eXBlPzogdW5rbm93biB9O1xuICAgIGlmICh0eXBlb2YgZnJhbWUudHlwZSAhPT0gJ3N0cmluZycpIHJldHVybiBudWxsO1xuICAgIHJldHVybiBwYXJzZWQgYXMgU2VydmVyRnJhbWU7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiBudWxsO1xuICB9XG59XG5cbi8qKiBVbndyYXAgdGhlIGlubmVyIEpTT04gb2YgYSBgZGF0YWAgZnJhbWUuIE51bGwgaWYgaXQgaXNuJ3QgcGFyc2VhYmxlLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlRGF0YUZyYW1lRXZlbnQoZnJhbWU6IERhdGFGcmFtZSk6IHVua25vd24ge1xuICB0cnkge1xuICAgIHJldHVybiBKU09OLnBhcnNlKGZyYW1lLmV2ZW50KSBhcyB1bmtub3duO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxufVxuXG4vKiogSHVtYW4tcmVhZGFibGUgdGV4dCBmcm9tIGFuIGVycm9yIGZyYW1lLCBmb3IgYG9uRXJyb3JgLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGVycm9yRnJhbWVNZXNzYWdlKGZyYW1lOiBFcnJvckZyYW1lKTogc3RyaW5nIHtcbiAgY29uc3QgcGFydHMgPSAoZnJhbWUuZXJyb3JzID8/IFtdKVxuICAgIC5tYXAoKGUpID0+IGUubWVzc2FnZSA/PyBlLmVycm9yVHlwZSlcbiAgICAuZmlsdGVyKChtKTogbSBpcyBzdHJpbmcgPT4gdHlwZW9mIG0gPT09ICdzdHJpbmcnICYmIG0ubGVuZ3RoID4gMCk7XG4gIHJldHVybiBwYXJ0cy5sZW5ndGggPiAwID8gcGFydHMuam9pbignOyAnKSA6IGZyYW1lLnR5cGU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjb25uZWN0aW9uSW5pdEZyYW1lKCk6IHN0cmluZyB7XG4gIHJldHVybiBKU09OLnN0cmluZ2lmeSh7IHR5cGU6ICdjb25uZWN0aW9uX2luaXQnIH0pO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gc3Vic2NyaWJlRnJhbWUoaWQ6IHN0cmluZywgY2hhbm5lbDogc3RyaW5nLCBhdXRoOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+KTogc3RyaW5nIHtcbiAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KHsgdHlwZTogJ3N1YnNjcmliZScsIGlkLCBjaGFubmVsLCBhdXRob3JpemF0aW9uOiBhdXRoIH0pO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gdW5zdWJzY3JpYmVGcmFtZShpZDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KHsgdHlwZTogJ3Vuc3Vic2NyaWJlJywgaWQgfSk7XG59XG4iXX0=
@@ -0,0 +1,44 @@
1
+ import { type PlatformAuth, type PlatformAuthInput, type PlatformConfigInput } from '../config';
2
+ import type { Placement, PlacementDeleteInput, PlacementRecord, PlacementUpsertInput, PlacementsQuery, SuggestionsInput, SuggestionsResult, UpsellEventsInput } from './schema';
3
+ export interface UpsellClient {
4
+ config(input: PlatformConfigInput): UpsellClient;
5
+ auth(input: PlatformAuthInput | PlatformAuth): UpsellClient;
6
+ /** GET /v1/upsell/placements - resolved placements for a channel + tenant. */
7
+ placements(query: PlacementsQuery): Promise<Placement[]>;
8
+ /** POST /v1/upsell/suggestions - items for one placement given the live cart. */
9
+ suggest(input: SuggestionsInput): Promise<SuggestionsResult>;
10
+ /**
11
+ * POST /v1/upsell/events - report shown / added / ignored for a
12
+ * rendered placement. Fire-and-forget; feeds attach-rate measurement
13
+ * and the weight tuning that follows it.
14
+ */
15
+ reportEvents(input: UpsellEventsInput): Promise<{
16
+ accepted: number;
17
+ }>;
18
+ /** GET /v1/upsell/admin/placements - raw config rows (service-actor only). */
19
+ adminListPlacements(opts?: {
20
+ parentId?: number;
21
+ }): Promise<PlacementRecord[]>;
22
+ /** PUT /v1/upsell/admin/placements - create/replace a config row (service-actor only). */
23
+ adminUpsertPlacement(input: PlacementUpsertInput): Promise<PlacementRecord>;
24
+ /** DELETE /v1/upsell/admin/placements - throws UpsellApiError(404) if absent. */
25
+ adminDeletePlacement(input: PlacementDeleteInput): Promise<void>;
26
+ }
27
+ export declare class UpsellApiError extends Error {
28
+ readonly status: number;
29
+ readonly body: string;
30
+ constructor(status: number, body: string);
31
+ }
32
+ /**
33
+ * Build the `upsell` namespace.
34
+ *
35
+ * Shape and contract mirror {@link createZonesClient} - lazy config
36
+ * resolution via {@link ConfigState}, shared 401-retry via
37
+ * {@link sendWithAuthRetry}, immutable per-call config; no globals.
38
+ *
39
+ * Reads (`placements`, `suggest`) accept user or service tokens —
40
+ * channel apps call them with customer session JWTs. Admin methods
41
+ * require a service-actor token; the server answers 403 otherwise and
42
+ * we surface it as {@link UpsellApiError}.
43
+ */
44
+ export declare function createUpsellClient(input?: PlatformConfigInput): UpsellClient;