@shware/workflow 7.0.0 → 8.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.
- package/README.md +121 -34
- package/dist/aws/engine-step.cjs +4 -1
- package/dist/aws/engine-step.cjs.map +1 -1
- package/dist/aws/engine-step.d.cts.map +1 -1
- package/dist/aws/engine-step.d.mts.map +1 -1
- package/dist/aws/engine-step.mjs +4 -1
- package/dist/aws/engine-step.mjs.map +1 -1
- package/dist/client.cjs +66 -41
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +72 -22
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.mts +72 -22
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +66 -42
- package/dist/client.mjs.map +1 -1
- package/dist/cloudflare/index.d.cts +4 -3
- package/dist/cloudflare/index.d.mts +4 -3
- package/dist/cloudflare/runner.cjs +3 -1
- package/dist/cloudflare/runner.cjs.map +1 -1
- package/dist/cloudflare/runner.d.cts.map +1 -1
- package/dist/cloudflare/runner.d.mts.map +1 -1
- package/dist/cloudflare/runner.mjs +3 -1
- package/dist/cloudflare/runner.mjs.map +1 -1
- package/dist/cloudflare/worker.cjs +12 -1
- package/dist/cloudflare/worker.cjs.map +1 -1
- package/dist/cloudflare/worker.d.cts +9 -1
- package/dist/cloudflare/worker.d.cts.map +1 -1
- package/dist/cloudflare/worker.d.mts +9 -1
- package/dist/cloudflare/worker.d.mts.map +1 -1
- package/dist/cloudflare/worker.mjs +12 -1
- package/dist/cloudflare/worker.mjs.map +1 -1
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +4 -4
- package/dist/contract.d.mts +4 -4
- package/dist/contract.mjs.map +1 -1
- package/dist/dsl/trigger.cjs +2 -1
- package/dist/dsl/trigger.cjs.map +1 -1
- package/dist/dsl/trigger.d.cts +6 -0
- package/dist/dsl/trigger.d.cts.map +1 -1
- package/dist/dsl/trigger.d.mts +6 -0
- package/dist/dsl/trigger.d.mts.map +1 -1
- package/dist/dsl/trigger.mjs +2 -1
- package/dist/dsl/trigger.mjs.map +1 -1
- package/dist/engine/condition.cjs +3 -3
- package/dist/engine/condition.cjs.map +1 -1
- package/dist/engine/condition.d.cts +3 -3
- package/dist/engine/condition.d.mts +3 -3
- package/dist/engine/condition.mjs +3 -3
- package/dist/engine/condition.mjs.map +1 -1
- package/dist/engine/interpreter.cjs +9 -5
- package/dist/engine/interpreter.cjs.map +1 -1
- package/dist/engine/interpreter.d.cts.map +1 -1
- package/dist/engine/interpreter.d.mts.map +1 -1
- package/dist/engine/interpreter.mjs +9 -5
- package/dist/engine/interpreter.mjs.map +1 -1
- package/dist/engine/ports.d.cts +2 -0
- package/dist/engine/ports.d.cts.map +1 -1
- package/dist/engine/ports.d.mts +2 -0
- package/dist/engine/ports.d.mts.map +1 -1
- package/dist/host/batch.cjs +172 -0
- package/dist/host/batch.cjs.map +1 -0
- package/dist/host/batch.d.cts +88 -0
- package/dist/host/batch.d.cts.map +1 -0
- package/dist/host/batch.d.mts +88 -0
- package/dist/host/batch.d.mts.map +1 -0
- package/dist/host/batch.mjs +163 -0
- package/dist/host/batch.mjs.map +1 -0
- package/dist/host/execute.cjs +10 -6
- package/dist/host/execute.cjs.map +1 -1
- package/dist/host/execute.mjs +10 -6
- package/dist/host/execute.mjs.map +1 -1
- package/dist/host/http.cjs +30 -89
- package/dist/host/http.cjs.map +1 -1
- package/dist/host/http.d.cts +1 -1
- package/dist/host/http.d.cts.map +1 -1
- package/dist/host/http.d.mts +1 -1
- package/dist/host/http.d.mts.map +1 -1
- package/dist/host/http.mjs +24 -83
- package/dist/host/http.mjs.map +1 -1
- package/dist/host/ingest.cjs +56 -13
- package/dist/host/ingest.cjs.map +1 -1
- package/dist/host/ingest.d.cts +17 -6
- package/dist/host/ingest.d.cts.map +1 -1
- package/dist/host/ingest.d.mts +17 -6
- package/dist/host/ingest.d.mts.map +1 -1
- package/dist/host/ingest.mjs +55 -14
- package/dist/host/ingest.mjs.map +1 -1
- package/dist/host/journey-host.cjs +6 -0
- package/dist/host/journey-host.cjs.map +1 -1
- package/dist/host/journey-host.d.cts +5 -2
- package/dist/host/journey-host.d.cts.map +1 -1
- package/dist/host/journey-host.d.mts +5 -2
- package/dist/host/journey-host.d.mts.map +1 -1
- package/dist/host/journey-host.mjs +6 -0
- package/dist/host/journey-host.mjs.map +1 -1
- package/dist/host/platform.cjs.map +1 -1
- package/dist/host/platform.d.cts +2 -0
- package/dist/host/platform.d.cts.map +1 -1
- package/dist/host/platform.d.mts +2 -0
- package/dist/host/platform.d.mts.map +1 -1
- package/dist/host/platform.mjs.map +1 -1
- package/dist/ir.cjs +2 -1
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +3 -0
- package/dist/ir.d.cts.map +1 -1
- package/dist/ir.d.mts +3 -0
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +2 -1
- package/dist/ir.mjs.map +1 -1
- package/dist/store/facts.cjs +13 -9
- package/dist/store/facts.cjs.map +1 -1
- package/dist/store/facts.d.cts +6 -2
- package/dist/store/facts.d.cts.map +1 -1
- package/dist/store/facts.d.mts +6 -2
- package/dist/store/facts.d.mts.map +1 -1
- package/dist/store/facts.mjs +13 -9
- package/dist/store/facts.mjs.map +1 -1
- package/dist/store/identity.cjs +34 -0
- package/dist/store/identity.cjs.map +1 -0
- package/dist/store/identity.d.cts +13 -0
- package/dist/store/identity.d.cts.map +1 -0
- package/dist/store/identity.d.mts +13 -0
- package/dist/store/identity.d.mts.map +1 -0
- package/dist/store/identity.mjs +30 -0
- package/dist/store/identity.mjs.map +1 -0
- package/dist/store/index.cjs +5 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +49 -15
- package/dist/store/index.d.cts.map +1 -1
- package/dist/store/index.d.mts +49 -15
- package/dist/store/index.d.mts.map +1 -1
- package/dist/store/index.mjs +2 -1
- package/dist/store/index.mjs.map +1 -1
- package/dist/store/postgres.cjs +206 -44
- package/dist/store/postgres.cjs.map +1 -1
- package/dist/store/postgres.d.cts +17 -4
- package/dist/store/postgres.d.cts.map +1 -1
- package/dist/store/postgres.d.mts +17 -4
- package/dist/store/postgres.d.mts.map +1 -1
- package/dist/store/postgres.mjs +207 -45
- package/dist/store/postgres.mjs.map +1 -1
- package/dist/store/tables.cjs +24 -6
- package/dist/store/tables.cjs.map +1 -1
- package/dist/store/tables.d.cts +1 -0
- package/dist/store/tables.d.cts.map +1 -1
- package/dist/store/tables.d.mts +1 -0
- package/dist/store/tables.d.mts.map +1 -1
- package/dist/store/tables.mjs +24 -6
- package/dist/store/tables.mjs.map +1 -1
- package/dist/webpush.cjs.map +1 -1
- package/dist/webpush.d.cts +1 -1
- package/dist/webpush.d.mts +1 -1
- package/dist/webpush.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Contract, ContractIssue, ProfilePatch, Topic } from "./contract.cjs";
|
|
2
2
|
import { ProfileProps } from "./store/index.cjs";
|
|
3
|
-
import { ChannelSubscriptions, ChannelSubscriptionsPatch, DeployResult,
|
|
3
|
+
import { ChannelSubscriptions, ChannelSubscriptionsPatch, DeployResult, MessageOrigin, MessageSubscriptions, SubscriptionChannel, Subscriptions, SubscriptionsPatch } from "./host/ingest.cjs";
|
|
4
|
+
import { BatchEvent, BatchResult, JourneyBatch } from "./host/batch.cjs";
|
|
4
5
|
import * as core from "zod/v4/core";
|
|
5
6
|
/**
|
|
6
7
|
* `@shware/workflow/client` — the producer side of the ingest API, for the
|
|
@@ -10,8 +11,8 @@ import * as core from "zod/v4/core";
|
|
|
10
11
|
*/
|
|
11
12
|
export interface JourneyClientOptions<C extends Contract | undefined = undefined> {
|
|
12
13
|
/**
|
|
13
|
-
* The shared `defineContract(...)`: types `
|
|
14
|
-
*
|
|
14
|
+
* The shared `defineContract(...)`: types `ingest` (the event name picks the payload type, the
|
|
15
|
+
* profile patch is the contract's), and checks both before anything is sent.
|
|
15
16
|
*/
|
|
16
17
|
contract?: C;
|
|
17
18
|
/** The journey Worker's origin, e.g. `https://workflow.example.com`. */
|
|
@@ -35,39 +36,82 @@ export declare class JourneyValidationError extends Error {
|
|
|
35
36
|
constructor(path: string, event: string | undefined, issues: ContractIssue[]);
|
|
36
37
|
}
|
|
37
38
|
type EventsOf<C> = C extends Contract<infer E> ? E : never;
|
|
38
|
-
/** Event names: the contract's, or any string without one. */
|
|
39
|
-
export type ClientEventName<C> = [C] extends [undefined] ? string : keyof EventsOf<C> & string;
|
|
40
|
-
type TrackArgs<C, K> = [C] extends [undefined] ? [payload?: Record<string, unknown>, options?: TrackOptions] : K extends keyof EventsOf<C> ? [payload: core.input<EventsOf<C>[K]>, options?: TrackOptions] : never;
|
|
41
39
|
type IdentifyProps<C> = [C] extends [undefined] ? ProfileProps : C extends Contract<Record<string, core.$ZodObject>, infer P> ? ProfilePatch<P> : never;
|
|
42
40
|
/** The topics the client's contract declares; any string without a contract. */
|
|
43
41
|
export type ClientTopic<C> = [C] extends [undefined] ? string : Topic<C>;
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
/** One event of a batch: with a contract, the name picks the payload type. */
|
|
43
|
+
export type BatchEventInput<C> = [C] extends [undefined] ? {
|
|
44
|
+
event: string;
|
|
45
|
+
payload?: Record<string, unknown>;
|
|
46
46
|
id?: string;
|
|
47
|
-
/** When it happened (epoch ms); the Worker's clock when omitted. */
|
|
48
47
|
ts?: number;
|
|
48
|
+
} : { [K in keyof EventsOf<C> & string]: {
|
|
49
|
+
event: K;
|
|
50
|
+
payload: core.input<EventsOf<C>[K]>;
|
|
51
|
+
id?: string;
|
|
52
|
+
ts?: number;
|
|
53
|
+
}; }[keyof EventsOf<C> & string];
|
|
54
|
+
/**
|
|
55
|
+
* Who a report or device is about: a signed-in user, or a visitor before sign-in (a stable id for
|
|
56
|
+
* one client, renewed when a user signs out). Naming both says this visitor is this user: what the
|
|
57
|
+
* engine knows of the visitor moves to the user, once.
|
|
58
|
+
*/
|
|
59
|
+
export type JourneyIdentity = {
|
|
60
|
+
userId: string;
|
|
61
|
+
visitorId?: string | undefined;
|
|
62
|
+
} | {
|
|
63
|
+
userId?: undefined;
|
|
64
|
+
visitorId: string;
|
|
65
|
+
};
|
|
66
|
+
/** What the profile now says and what they just did, as one report. */
|
|
67
|
+
export type BatchInput<C> = JourneyIdentity & {
|
|
68
|
+
/** Merged before the first event: a journey the events start finds this recipient. */
|
|
69
|
+
profile?: IdentifyProps<C>;
|
|
70
|
+
events?: readonly BatchEventInput<C>[];
|
|
71
|
+
};
|
|
72
|
+
/** The producer side of a queue (the subset of a Cloudflare Queues binding used). */
|
|
73
|
+
export interface JourneyQueueBinding {
|
|
74
|
+
send(body: JourneyBatch): Promise<unknown>;
|
|
75
|
+
}
|
|
76
|
+
export interface JourneyQueueOptions<C extends Contract | undefined = undefined> {
|
|
77
|
+
/** The shared `defineContract(...)`: types `ingest` and checks each batch before it is sent. */
|
|
78
|
+
contract?: C;
|
|
79
|
+
/** The queue `journeyWorker`'s `queue` handler consumes. */
|
|
80
|
+
queue: JourneyQueueBinding;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* `JourneyClient.ingest` through a queue, for a producer that must not lose a report and must not
|
|
84
|
+
* wait for the engine: once `ingest` resolves the batch is durable, and the consumer retries it
|
|
85
|
+
* until it is applied. Delivery is at least once and in no guaranteed order: the batch is the unit
|
|
86
|
+
* that stays in order and event ids and property times absorb the rest. A rejected `ingest` was
|
|
87
|
+
* not queued; the caller decides (a 5xx so its client retries, say).
|
|
88
|
+
*/
|
|
89
|
+
export declare class JourneyQueue<C extends Contract | undefined = undefined> {
|
|
90
|
+
private readonly options;
|
|
91
|
+
constructor(options: JourneyQueueOptions<C>);
|
|
92
|
+
ingest(input: BatchInput<C>): Promise<void>;
|
|
49
93
|
}
|
|
50
94
|
export declare class JourneyClient<C extends Contract | undefined = undefined> {
|
|
51
95
|
private readonly options;
|
|
52
96
|
constructor(options: JourneyClientOptions<C>);
|
|
53
|
-
/** Merge profile properties (`null` clears one). Every property a journey reads must be identified before the event that starts it. */
|
|
54
|
-
identify(userId: string, props: IdentifyProps<C>): Promise<IdentifyResult>;
|
|
55
97
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
98
|
+
* The one way in: a profile and the events it belongs to, as one batch. A visitor who signed in
|
|
99
|
+
* merges first, then the profile, then the events in order, so a journey cannot start without
|
|
100
|
+
* its recipient. A caller that retries should pass each event's `id`: the Worker stores one
|
|
101
|
+
* occurrence per id. `ts` defaults to now. `JourneyQueue` is the same report through a queue.
|
|
60
102
|
*/
|
|
61
|
-
|
|
103
|
+
ingest(input: BatchInput<C>): Promise<BatchResult>;
|
|
62
104
|
/**
|
|
63
|
-
* A push token
|
|
64
|
-
* to the latest
|
|
65
|
-
*
|
|
105
|
+
* A push token registered for whoever holds the device now (call it on every registration): a
|
|
106
|
+
* token moves to the latest owner, and a `deviceId` seen with a new token replaces the old one.
|
|
107
|
+
* The engine keeps devices itself — pushes go to them, a token APNs / FCM reports as gone is
|
|
108
|
+
* dropped — and journeys waiting on `reachable('push')` wake. Naming both ids merges the visitor,
|
|
109
|
+
* as `ingest` does.
|
|
66
110
|
*/
|
|
67
|
-
registerDevice(
|
|
111
|
+
registerDevice(device: JourneyIdentity & {
|
|
112
|
+
deviceId?: string;
|
|
68
113
|
token: string;
|
|
69
114
|
platform: 'ios' | 'android' | 'web';
|
|
70
|
-
ts?: number;
|
|
71
115
|
}): Promise<{
|
|
72
116
|
ok: true;
|
|
73
117
|
woke: number;
|
|
@@ -91,6 +135,12 @@ export declare class JourneyClient<C extends Contract | undefined = undefined> {
|
|
|
91
135
|
erased: boolean;
|
|
92
136
|
pending: string[];
|
|
93
137
|
}>;
|
|
138
|
+
/** `forgetUser` for a visitor who never signed in. */
|
|
139
|
+
forgetVisitor(visitorId: string): Promise<{
|
|
140
|
+
ok: true;
|
|
141
|
+
erased: boolean;
|
|
142
|
+
pending: string[];
|
|
143
|
+
}>;
|
|
94
144
|
/** The user signed out on that device: the token stays registered, bound to nobody. */
|
|
95
145
|
unbindDevice(token: string): Promise<{
|
|
96
146
|
ok: true;
|
|
@@ -139,5 +189,5 @@ export declare class JourneyClient<C extends Contract | undefined = undefined> {
|
|
|
139
189
|
private post;
|
|
140
190
|
private request;
|
|
141
191
|
}
|
|
142
|
-
export type { ChannelSubscriptions, ChannelSubscriptionsPatch, MessageOrigin, MessageSubscriptions, SubscriptionChannel, Subscriptions, SubscriptionsPatch };
|
|
192
|
+
export type { BatchEvent, BatchResult, ChannelSubscriptions, ChannelSubscriptionsPatch, JourneyBatch, MessageOrigin, MessageSubscriptions, SubscriptionChannel, Subscriptions, SubscriptionsPatch };
|
|
143
193
|
//# sourceMappingURL=client.d.cts.map
|
package/dist/client.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;;;;;;;iBAmBiB,qBAAqB,UAAU;;;;;EAK9C,WAAW;;EAEX;;EAEA;;EAEA,eAAe;;qBAGJ,2BAA2B;WAE3B;WACA;WACA;EAHX,YACW,cACA,gBACA;;;qBAQA,+BAA+B;WAE/B;WACA;WACA,QAAQ;EAHnB,YACW,cACA,2BACA,QAAQ;;KAWhB,SAAS,KAAK,UAAU,eAAe,KAAK;KAC5C,cAAc,MAAM,yBACrB,eACA,UAAU,SAAS,eAAe,KAAK,mBAAmB,KACxD,aAAa;;YAIP,YAAY,MAAM,kCAAkC,MAAM;;YAc1D,gBAAgB,MAAM;EAC5B;EAAe,UAAU;EAAyB;EAAa;OAE9D,WAAW,SAAS;EACnB,OAAO;EACP,SAAS,KAAK,MAAM,SAAS,GAAG;EAChC;EACA;WAEI,SAAS;;;;;;YAOT;EACN;EAAgB;;EAChB;EAAoB;;;YAGd,WAAW,KAAK;;EAE1B,UAAU,cAAc;EACxB,kBAAkB,gBAAgB;;;iBAmDnB;EACf,KAAK,MAAM,eAAe;;iBAGX,oBAAoB,UAAU;;EAE7C,WAAW;;EAEX,OAAO;;;;;;;;;qBAUI,aAAa,UAAU;mBACL;EAA7B,YAA6B,SAAS,oBAAoB;EAEpD,OAAO,OAAO,WAAW,KAAK;;qBAKzB,cAAc,UAAU;mBACN;EAA7B,YAA6B,SAAS,qBAAqB;;;;;;;EAQrD,OAAO,OAAO,WAAW,KAAK,QAAQ;;;;;;;;EAW5C,eACE,QAAQ;IACN;IACA;IACA;MAED;IAAU;IAAU;;;;;;EAQvB,cAAc,gBAAgB,oBAAoB;IAAU;IAAU;;;;;;;;EAUtE,WAAW,iBAAiB;IAAU;IAAU;IAAiB;;;EAKjE,cAAc,oBAAoB;IAAU;IAAU;IAAiB;;;EAKvE,aAAa,gBAAgB;IAAU;IAAU;;;EAKjD,aAAa,gBAAgB;IAAU;IAAU;;;;;;;;;;EAYjD,iBAAiB,iBAAiB,QAAQ,cAAc,YAAY;;;;;;;;;;EAapE,iBACE,gBACA,OAAO,mBAAmB,YAAY,MACrC,QAAQ,cAAc,YAAY;;;;;;;;;;EAerC,0BAA0B,oBAAoB,QAAQ,qBAAqB,YAAY;;EAKvF,0BACE,mBACA,OAAO,mBAAmB,YAAY,MACrC,QAAQ,qBAAqB,YAAY;;UAOpC;;EAmBR,OAAO,kBAAkB,QAAQ;UAIzB;UAIM"}
|
package/dist/client.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Contract, ContractIssue, ProfilePatch, Topic } from "./contract.mjs";
|
|
2
2
|
import { ProfileProps } from "./store/index.mjs";
|
|
3
|
-
import { ChannelSubscriptions, ChannelSubscriptionsPatch, DeployResult,
|
|
3
|
+
import { ChannelSubscriptions, ChannelSubscriptionsPatch, DeployResult, MessageOrigin, MessageSubscriptions, SubscriptionChannel, Subscriptions, SubscriptionsPatch } from "./host/ingest.mjs";
|
|
4
|
+
import { BatchEvent, BatchResult, JourneyBatch } from "./host/batch.mjs";
|
|
4
5
|
import * as core from "zod/v4/core";
|
|
5
6
|
/**
|
|
6
7
|
* `@shware/workflow/client` — the producer side of the ingest API, for the
|
|
@@ -10,8 +11,8 @@ import * as core from "zod/v4/core";
|
|
|
10
11
|
*/
|
|
11
12
|
export interface JourneyClientOptions<C extends Contract | undefined = undefined> {
|
|
12
13
|
/**
|
|
13
|
-
* The shared `defineContract(...)`: types `
|
|
14
|
-
*
|
|
14
|
+
* The shared `defineContract(...)`: types `ingest` (the event name picks the payload type, the
|
|
15
|
+
* profile patch is the contract's), and checks both before anything is sent.
|
|
15
16
|
*/
|
|
16
17
|
contract?: C;
|
|
17
18
|
/** The journey Worker's origin, e.g. `https://workflow.example.com`. */
|
|
@@ -35,39 +36,82 @@ export declare class JourneyValidationError extends Error {
|
|
|
35
36
|
constructor(path: string, event: string | undefined, issues: ContractIssue[]);
|
|
36
37
|
}
|
|
37
38
|
type EventsOf<C> = C extends Contract<infer E> ? E : never;
|
|
38
|
-
/** Event names: the contract's, or any string without one. */
|
|
39
|
-
export type ClientEventName<C> = [C] extends [undefined] ? string : keyof EventsOf<C> & string;
|
|
40
|
-
type TrackArgs<C, K> = [C] extends [undefined] ? [payload?: Record<string, unknown>, options?: TrackOptions] : K extends keyof EventsOf<C> ? [payload: core.input<EventsOf<C>[K]>, options?: TrackOptions] : never;
|
|
41
39
|
type IdentifyProps<C> = [C] extends [undefined] ? ProfileProps : C extends Contract<Record<string, core.$ZodObject>, infer P> ? ProfilePatch<P> : never;
|
|
42
40
|
/** The topics the client's contract declares; any string without a contract. */
|
|
43
41
|
export type ClientTopic<C> = [C] extends [undefined] ? string : Topic<C>;
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
/** One event of a batch: with a contract, the name picks the payload type. */
|
|
43
|
+
export type BatchEventInput<C> = [C] extends [undefined] ? {
|
|
44
|
+
event: string;
|
|
45
|
+
payload?: Record<string, unknown>;
|
|
46
46
|
id?: string;
|
|
47
|
-
/** When it happened (epoch ms); the Worker's clock when omitted. */
|
|
48
47
|
ts?: number;
|
|
48
|
+
} : { [K in keyof EventsOf<C> & string]: {
|
|
49
|
+
event: K;
|
|
50
|
+
payload: core.input<EventsOf<C>[K]>;
|
|
51
|
+
id?: string;
|
|
52
|
+
ts?: number;
|
|
53
|
+
}; }[keyof EventsOf<C> & string];
|
|
54
|
+
/**
|
|
55
|
+
* Who a report or device is about: a signed-in user, or a visitor before sign-in (a stable id for
|
|
56
|
+
* one client, renewed when a user signs out). Naming both says this visitor is this user: what the
|
|
57
|
+
* engine knows of the visitor moves to the user, once.
|
|
58
|
+
*/
|
|
59
|
+
export type JourneyIdentity = {
|
|
60
|
+
userId: string;
|
|
61
|
+
visitorId?: string | undefined;
|
|
62
|
+
} | {
|
|
63
|
+
userId?: undefined;
|
|
64
|
+
visitorId: string;
|
|
65
|
+
};
|
|
66
|
+
/** What the profile now says and what they just did, as one report. */
|
|
67
|
+
export type BatchInput<C> = JourneyIdentity & {
|
|
68
|
+
/** Merged before the first event: a journey the events start finds this recipient. */
|
|
69
|
+
profile?: IdentifyProps<C>;
|
|
70
|
+
events?: readonly BatchEventInput<C>[];
|
|
71
|
+
};
|
|
72
|
+
/** The producer side of a queue (the subset of a Cloudflare Queues binding used). */
|
|
73
|
+
export interface JourneyQueueBinding {
|
|
74
|
+
send(body: JourneyBatch): Promise<unknown>;
|
|
75
|
+
}
|
|
76
|
+
export interface JourneyQueueOptions<C extends Contract | undefined = undefined> {
|
|
77
|
+
/** The shared `defineContract(...)`: types `ingest` and checks each batch before it is sent. */
|
|
78
|
+
contract?: C;
|
|
79
|
+
/** The queue `journeyWorker`'s `queue` handler consumes. */
|
|
80
|
+
queue: JourneyQueueBinding;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* `JourneyClient.ingest` through a queue, for a producer that must not lose a report and must not
|
|
84
|
+
* wait for the engine: once `ingest` resolves the batch is durable, and the consumer retries it
|
|
85
|
+
* until it is applied. Delivery is at least once and in no guaranteed order: the batch is the unit
|
|
86
|
+
* that stays in order and event ids and property times absorb the rest. A rejected `ingest` was
|
|
87
|
+
* not queued; the caller decides (a 5xx so its client retries, say).
|
|
88
|
+
*/
|
|
89
|
+
export declare class JourneyQueue<C extends Contract | undefined = undefined> {
|
|
90
|
+
private readonly options;
|
|
91
|
+
constructor(options: JourneyQueueOptions<C>);
|
|
92
|
+
ingest(input: BatchInput<C>): Promise<void>;
|
|
49
93
|
}
|
|
50
94
|
export declare class JourneyClient<C extends Contract | undefined = undefined> {
|
|
51
95
|
private readonly options;
|
|
52
96
|
constructor(options: JourneyClientOptions<C>);
|
|
53
|
-
/** Merge profile properties (`null` clears one). Every property a journey reads must be identified before the event that starts it. */
|
|
54
|
-
identify(userId: string, props: IdentifyProps<C>): Promise<IdentifyResult>;
|
|
55
97
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
98
|
+
* The one way in: a profile and the events it belongs to, as one batch. A visitor who signed in
|
|
99
|
+
* merges first, then the profile, then the events in order, so a journey cannot start without
|
|
100
|
+
* its recipient. A caller that retries should pass each event's `id`: the Worker stores one
|
|
101
|
+
* occurrence per id. `ts` defaults to now. `JourneyQueue` is the same report through a queue.
|
|
60
102
|
*/
|
|
61
|
-
|
|
103
|
+
ingest(input: BatchInput<C>): Promise<BatchResult>;
|
|
62
104
|
/**
|
|
63
|
-
* A push token
|
|
64
|
-
* to the latest
|
|
65
|
-
*
|
|
105
|
+
* A push token registered for whoever holds the device now (call it on every registration): a
|
|
106
|
+
* token moves to the latest owner, and a `deviceId` seen with a new token replaces the old one.
|
|
107
|
+
* The engine keeps devices itself — pushes go to them, a token APNs / FCM reports as gone is
|
|
108
|
+
* dropped — and journeys waiting on `reachable('push')` wake. Naming both ids merges the visitor,
|
|
109
|
+
* as `ingest` does.
|
|
66
110
|
*/
|
|
67
|
-
registerDevice(
|
|
111
|
+
registerDevice(device: JourneyIdentity & {
|
|
112
|
+
deviceId?: string;
|
|
68
113
|
token: string;
|
|
69
114
|
platform: 'ios' | 'android' | 'web';
|
|
70
|
-
ts?: number;
|
|
71
115
|
}): Promise<{
|
|
72
116
|
ok: true;
|
|
73
117
|
woke: number;
|
|
@@ -91,6 +135,12 @@ export declare class JourneyClient<C extends Contract | undefined = undefined> {
|
|
|
91
135
|
erased: boolean;
|
|
92
136
|
pending: string[];
|
|
93
137
|
}>;
|
|
138
|
+
/** `forgetUser` for a visitor who never signed in. */
|
|
139
|
+
forgetVisitor(visitorId: string): Promise<{
|
|
140
|
+
ok: true;
|
|
141
|
+
erased: boolean;
|
|
142
|
+
pending: string[];
|
|
143
|
+
}>;
|
|
94
144
|
/** The user signed out on that device: the token stays registered, bound to nobody. */
|
|
95
145
|
unbindDevice(token: string): Promise<{
|
|
96
146
|
ok: true;
|
|
@@ -139,5 +189,5 @@ export declare class JourneyClient<C extends Contract | undefined = undefined> {
|
|
|
139
189
|
private post;
|
|
140
190
|
private request;
|
|
141
191
|
}
|
|
142
|
-
export type { ChannelSubscriptions, ChannelSubscriptionsPatch, MessageOrigin, MessageSubscriptions, SubscriptionChannel, Subscriptions, SubscriptionsPatch };
|
|
192
|
+
export type { BatchEvent, BatchResult, ChannelSubscriptions, ChannelSubscriptionsPatch, JourneyBatch, MessageOrigin, MessageSubscriptions, SubscriptionChannel, Subscriptions, SubscriptionsPatch };
|
|
143
193
|
//# sourceMappingURL=client.d.mts.map
|
package/dist/client.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;;;;;;;iBAmBiB,qBAAqB,UAAU;;;;;EAK9C,WAAW;;EAEX;;EAEA;;EAEA,eAAe;;qBAGJ,2BAA2B;WAE3B;WACA;WACA;EAHX,YACW,cACA,gBACA;;;qBAQA,+BAA+B;WAE/B;WACA;WACA,QAAQ;EAHnB,YACW,cACA,2BACA,QAAQ;;KAWhB,SAAS,KAAK,UAAU,eAAe,KAAK;KAC5C,cAAc,MAAM,yBACrB,eACA,UAAU,SAAS,eAAe,KAAK,mBAAmB,KACxD,aAAa;;YAIP,YAAY,MAAM,kCAAkC,MAAM;;YAc1D,gBAAgB,MAAM;EAC5B;EAAe,UAAU;EAAyB;EAAa;OAE9D,WAAW,SAAS;EACnB,OAAO;EACP,SAAS,KAAK,MAAM,SAAS,GAAG;EAChC;EACA;WAEI,SAAS;;;;;;YAOT;EACN;EAAgB;;EAChB;EAAoB;;;YAGd,WAAW,KAAK;;EAE1B,UAAU,cAAc;EACxB,kBAAkB,gBAAgB;;;iBAmDnB;EACf,KAAK,MAAM,eAAe;;iBAGX,oBAAoB,UAAU;;EAE7C,WAAW;;EAEX,OAAO;;;;;;;;;qBAUI,aAAa,UAAU;mBACL;EAA7B,YAA6B,SAAS,oBAAoB;EAEpD,OAAO,OAAO,WAAW,KAAK;;qBAKzB,cAAc,UAAU;mBACN;EAA7B,YAA6B,SAAS,qBAAqB;;;;;;;EAQrD,OAAO,OAAO,WAAW,KAAK,QAAQ;;;;;;;;EAW5C,eACE,QAAQ;IACN;IACA;IACA;MAED;IAAU;IAAU;;;;;;EAQvB,cAAc,gBAAgB,oBAAoB;IAAU;IAAU;;;;;;;;EAUtE,WAAW,iBAAiB;IAAU;IAAU;IAAiB;;;EAKjE,cAAc,oBAAoB;IAAU;IAAU;IAAiB;;;EAKvE,aAAa,gBAAgB;IAAU;IAAU;;;EAKjD,aAAa,gBAAgB;IAAU;IAAU;;;;;;;;;;EAYjD,iBAAiB,iBAAiB,QAAQ,cAAc,YAAY;;;;;;;;;;EAapE,iBACE,gBACA,OAAO,mBAAmB,YAAY,MACrC,QAAQ,cAAc,YAAY;;;;;;;;;;EAerC,0BAA0B,oBAAoB,QAAQ,qBAAqB,YAAY;;EAKvF,0BACE,mBACA,OAAO,mBAAmB,YAAY,MACrC,QAAQ,qBAAqB,YAAY;;UAOpC;;EAmBR,OAAO,kBAAkB,QAAQ;UAIzB;UAIM"}
|
package/dist/client.mjs
CHANGED
|
@@ -25,57 +25,77 @@ var JourneyValidationError = class extends Error {
|
|
|
25
25
|
this.name = "JourneyValidationError";
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The batch as it travels: checked against the contract, with the time and the event ids and
|
|
30
|
+
* timestamps fixed here, so every redelivery is the same report.
|
|
31
|
+
*/
|
|
32
|
+
function prepareBatch(contract, input) {
|
|
33
|
+
const { userId, visitorId, profile, events } = input;
|
|
34
|
+
if (contract !== void 0) {
|
|
35
|
+
if (profile !== void 0) {
|
|
36
|
+
const result = contract.parseProfilePatch(profile);
|
|
37
|
+
if (!result.ok) throw new JourneyValidationError("/ingest", void 0, result.issues);
|
|
38
|
+
}
|
|
39
|
+
for (const { event, payload } of events ?? []) {
|
|
40
|
+
const result = contract.parseEvent(event, payload ?? {});
|
|
41
|
+
if (!result.ok) throw new JourneyValidationError("/ingest", event, result.issues);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
return {
|
|
46
|
+
...userId === void 0 ? {} : { userId },
|
|
47
|
+
...visitorId === void 0 ? {} : { visitorId },
|
|
48
|
+
...profile === void 0 ? {} : {
|
|
49
|
+
profile,
|
|
50
|
+
at: now
|
|
51
|
+
},
|
|
52
|
+
...events === void 0 ? {} : { events: events.map((e) => ({
|
|
53
|
+
event: e.event,
|
|
54
|
+
payload: e.payload ?? {},
|
|
55
|
+
id: e.id ?? crypto.randomUUID(),
|
|
56
|
+
ts: e.ts ?? now
|
|
57
|
+
})) }
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `JourneyClient.ingest` through a queue, for a producer that must not lose a report and must not
|
|
62
|
+
* wait for the engine: once `ingest` resolves the batch is durable, and the consumer retries it
|
|
63
|
+
* until it is applied. Delivery is at least once and in no guaranteed order: the batch is the unit
|
|
64
|
+
* that stays in order and event ids and property times absorb the rest. A rejected `ingest` was
|
|
65
|
+
* not queued; the caller decides (a 5xx so its client retries, say).
|
|
66
|
+
*/
|
|
67
|
+
var JourneyQueue = class {
|
|
29
68
|
options;
|
|
30
69
|
constructor(options) {
|
|
31
70
|
this.options = options;
|
|
32
71
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
userId,
|
|
42
|
-
props
|
|
43
|
-
});
|
|
72
|
+
async ingest(input) {
|
|
73
|
+
await this.options.queue.send(prepareBatch(this.options.contract, input));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var JourneyClient = class {
|
|
77
|
+
options;
|
|
78
|
+
constructor(options) {
|
|
79
|
+
this.options = options;
|
|
44
80
|
}
|
|
45
81
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
82
|
+
* The one way in: a profile and the events it belongs to, as one batch. A visitor who signed in
|
|
83
|
+
* merges first, then the profile, then the events in order, so a journey cannot start without
|
|
84
|
+
* its recipient. A caller that retries should pass each event's `id`: the Worker stores one
|
|
85
|
+
* occurrence per id. `ts` defaults to now. `JourneyQueue` is the same report through a queue.
|
|
50
86
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const contract = this.options.contract;
|
|
54
|
-
if (contract !== void 0) {
|
|
55
|
-
const result = contract.parseEvent(event, payload);
|
|
56
|
-
if (!result.ok) return Promise.reject(new JourneyValidationError("/events", event, result.issues));
|
|
57
|
-
}
|
|
58
|
-
return this.post("/events", {
|
|
59
|
-
userId,
|
|
60
|
-
event,
|
|
61
|
-
payload,
|
|
62
|
-
...options.id === void 0 ? {} : { id: options.id },
|
|
63
|
-
...options.ts === void 0 ? {} : { ts: options.ts }
|
|
64
|
-
});
|
|
87
|
+
async ingest(input) {
|
|
88
|
+
return this.post("/ingest", prepareBatch(this.options.contract, input));
|
|
65
89
|
}
|
|
66
90
|
/**
|
|
67
|
-
* A push token
|
|
68
|
-
* to the latest
|
|
69
|
-
*
|
|
91
|
+
* A push token registered for whoever holds the device now (call it on every registration): a
|
|
92
|
+
* token moves to the latest owner, and a `deviceId` seen with a new token replaces the old one.
|
|
93
|
+
* The engine keeps devices itself — pushes go to them, a token APNs / FCM reports as gone is
|
|
94
|
+
* dropped — and journeys waiting on `reachable('push')` wake. Naming both ids merges the visitor,
|
|
95
|
+
* as `ingest` does.
|
|
70
96
|
*/
|
|
71
|
-
registerDevice(
|
|
72
|
-
|
|
73
|
-
return this.post("/devices", {
|
|
74
|
-
userId,
|
|
75
|
-
token,
|
|
76
|
-
platform,
|
|
77
|
-
...ts === void 0 ? {} : { ts }
|
|
78
|
-
});
|
|
97
|
+
registerDevice(device) {
|
|
98
|
+
return this.post("/devices", device);
|
|
79
99
|
}
|
|
80
100
|
/**
|
|
81
101
|
* A journey push tapped open: the `message_id` the push carried, reported for the user who
|
|
@@ -96,6 +116,10 @@ var JourneyClient = class {
|
|
|
96
116
|
forgetUser(userId) {
|
|
97
117
|
return this.post("/users/forget", { userId });
|
|
98
118
|
}
|
|
119
|
+
/** `forgetUser` for a visitor who never signed in. */
|
|
120
|
+
forgetVisitor(visitorId) {
|
|
121
|
+
return this.post("/users/forget", { visitorId });
|
|
122
|
+
}
|
|
99
123
|
/** The user signed out on that device: the token stays registered, bound to nobody. */
|
|
100
124
|
unbindDevice(token) {
|
|
101
125
|
return this.post("/devices/unbind", { token });
|
|
@@ -183,6 +207,6 @@ var JourneyClient = class {
|
|
|
183
207
|
};
|
|
184
208
|
const subscriptionsPath = (userId) => `/subscriptions/${encodeURIComponent(userId)}`;
|
|
185
209
|
const subscriptionsByMessagePath = (messageId) => `/subscriptions/by-message/${encodeURIComponent(messageId)}`;
|
|
186
|
-
export { JourneyClient, JourneyClientError, JourneyValidationError };
|
|
210
|
+
export { JourneyClient, JourneyClientError, JourneyQueue, JourneyValidationError };
|
|
187
211
|
|
|
188
212
|
//# sourceMappingURL=client.mjs.map
|
package/dist/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type * as core from 'zod/v4/core';\nimport type { Contract, ContractIssue, ProfilePatch, Topic } from './contract';\nimport {\n type DeployResult,\n type IdentifyResult,\n type IngestResult,\n type MessageSubscriptions,\n SUBSCRIPTION_CHANNELS,\n type Subscriptions,\n type SubscriptionsPatch,\n} from './host/ingest';\nimport type { ProfileProps } from './store';\n\n/**\n * `@shware/workflow/client` — the producer side of the ingest API, for the\n * service that knows what users do (an api server, a webhook handler). Plain\n * fetch, no dependencies; errors carry the status and body so a caller can\n * log them without failing the request that triggered the report.\n */\n\nexport interface JourneyClientOptions<C extends Contract | undefined = undefined> {\n /**\n * The shared `defineContract(...)`: types `track` (the event name picks the\n * payload type) and `identify`, and checks both before anything is sent.\n */\n contract?: C;\n /** The journey Worker's origin, e.g. `https://workflow.example.com`. */\n origin: string;\n /** Its API_TOKEN, sent as a bearer; omit against an open local instance. */\n token?: string;\n /** Fetch implementation (defaults to the global one). */\n fetch?: typeof fetch;\n}\n\nexport class JourneyClientError extends Error {\n constructor(\n readonly path: string,\n readonly status: number,\n readonly body: string\n ) {\n super(`journey ${path} failed: ${status} ${body}`);\n this.name = 'JourneyClientError';\n }\n}\n\n/** A payload or profile patch the contract refuses — raised before the request, with the field paths. */\nexport class JourneyValidationError extends Error {\n constructor(\n readonly path: string,\n readonly event: string | undefined,\n readonly issues: ContractIssue[]\n ) {\n super(\n `journey ${path}${event === undefined ? '' : ` '${event}'`} refused by the contract: ${issues\n .map((i) => `${i.path || '(root)'} ${i.message}`)\n .join('; ')}`\n );\n this.name = 'JourneyValidationError';\n }\n}\n\ntype EventsOf<C> = C extends Contract<infer E> ? E : never;\n/** Event names: the contract's, or any string without one. */\nexport type ClientEventName<C> = [C] extends [undefined] ? string : keyof EventsOf<C> & string;\ntype TrackArgs<C, K> = [C] extends [undefined]\n ? [payload?: Record<string, unknown>, options?: TrackOptions]\n : K extends keyof EventsOf<C>\n ? [payload: core.input<EventsOf<C>[K]>, options?: TrackOptions]\n : never;\ntype IdentifyProps<C> = [C] extends [undefined]\n ? ProfileProps\n : C extends Contract<Record<string, core.$ZodObject>, infer P>\n ? ProfilePatch<P>\n : never;\n\n/** The topics the client's contract declares; any string without a contract. */\nexport type ClientTopic<C> = [C] extends [undefined] ? string : Topic<C>;\n\nexport type {\n ChannelSubscriptions,\n ChannelSubscriptionsPatch,\n MessageOrigin,\n MessageSubscriptions,\n SubscriptionChannel,\n Subscriptions,\n SubscriptionsPatch,\n} from './host/ingest';\n\nexport interface TrackOptions {\n /** The occurrence's identity: a retry with the same id is recorded once. */\n id?: string;\n /** When it happened (epoch ms); the Worker's clock when omitted. */\n ts?: number;\n}\n\nexport class JourneyClient<C extends Contract | undefined = undefined> {\n constructor(private readonly options: JourneyClientOptions<C>) {}\n\n /** Merge profile properties (`null` clears one). Every property a journey reads must be identified before the event that starts it. */\n identify(userId: string, props: IdentifyProps<C>): Promise<IdentifyResult> {\n const contract = this.options.contract;\n if (contract !== undefined) {\n const result = contract.parseProfilePatch(props);\n if (!result.ok)\n return Promise.reject(new JourneyValidationError('/identify', undefined, result.issues));\n }\n return this.post<IdentifyResult>('/identify', { userId, props });\n }\n\n /**\n * Record an event; the Worker starts every journey whose trigger matches it\n * and wakes waits on it. A caller that retries should pass the same `id`\n * each time: the Worker stores one occurrence per id, so a retry after a\n * timeout cannot double-count the event. `ts` defaults to the Worker's clock.\n */\n track<K extends ClientEventName<C>>(\n userId: string,\n event: K,\n ...args: TrackArgs<C, K>\n ): Promise<IngestResult> {\n const [payload = {}, options = {}] = args as [Record<string, unknown>?, TrackOptions?];\n const contract = this.options.contract;\n if (contract !== undefined) {\n const result = contract.parseEvent(event, payload);\n if (!result.ok)\n return Promise.reject(new JourneyValidationError('/events', event, result.issues));\n }\n return this.post<IngestResult>('/events', {\n userId,\n event,\n payload,\n ...(options.id === undefined ? {} : { id: options.id }),\n ...(options.ts === undefined ? {} : { ts: options.ts }),\n });\n }\n\n /**\n * A push token the app registered for the user (call it on every registration: a token moves\n * to the latest user). The engine keeps devices itself — pushes go to them, a token APNs / FCM\n * reports as gone is dropped — and journeys waiting on `reachable('push')` wake.\n */\n registerDevice(\n userId: string,\n device: { token: string; platform: 'ios' | 'android' | 'web'; ts?: number }\n ): Promise<{ ok: true; woke: number }> {\n const { token, platform, ts } = device;\n return this.post('/devices', {\n userId,\n token,\n platform,\n ...(ts === undefined ? {} : { ts }),\n });\n }\n\n /**\n * A journey push tapped open: the `message_id` the push carried, reported for the user who\n * tapped it. Recorded as a human open on the copy (an email's opens are tracked by the Worker).\n */\n messageOpened(userId: string, messageId: string): Promise<{ ok: true; recorded: boolean }> {\n return this.post('/messages/opened', { userId, messageId });\n }\n\n /**\n * A deleted account: the engine records the erasure, stops the user's running journeys, then\n * erases everything it keeps about them (profile, devices, events, reporting). Once this\n * resolves the request is durable: `erased: false` means an instance would not stop yet, and\n * the Worker's scheduled sweep finishes the job.\n */\n forgetUser(userId: string): Promise<{ ok: true; erased: boolean; pending: string[] }> {\n return this.post('/users/forget', { userId });\n }\n\n /** The user signed out on that device: the token stays registered, bound to nobody. */\n unbindDevice(token: string): Promise<{ ok: true; woke: number }> {\n return this.post('/devices/unbind', { token });\n }\n\n /** Forget a push token for good (an uninstall the app learned of, a token it replaced). */\n removeDevice(token: string): Promise<{ ok: true; woke: number }> {\n return this.post('/devices/remove', { token });\n }\n\n /**\n * Where the user stands, topic × channel: for `email` and for `push`, `all` (false once they\n * opted out of the whole channel) and each declared topic (false once they opted out of it on\n * that channel) — the engine's record, which a preference page shows as it is. The two are\n * independent flags: a topic answers its own opt-out even while `all` is false, so `all: false`\n * masks every topic however they read (nothing is sent on the channel until it is true again).\n * Rejects with a 410 for an erased user.\n */\n getSubscriptions(userId: string): Promise<Subscriptions<ClientTopic<C>>> {\n return this.request('GET', subscriptionsPath(userId));\n }\n\n /**\n * Set subscriptions: only what the patch names changes, per channel (`{ email: { topics: {\n * newsletter: false } } }` opts out of one topic's emails, `{ push: { all: false } }` of every\n * push — the in-app opt-out App Store Review Guideline 4.5.4 asks for). `all: true` clears the\n * channel-wide opt-out only: a topic opted out on its own stays off until the patch names it,\n * so a preference page's \"subscribe to all\" sends `all: true` plus every topic `true`. Answers\n * the subscriptions as they now stand. With a contract, a topic it does not declare is refused\n * here, before the request; the engine refuses it too.\n */\n setSubscriptions(\n userId: string,\n patch: SubscriptionsPatch<ClientTopic<C>>\n ): Promise<Subscriptions<ClientTopic<C>>> {\n const refused = this.undeclaredTopics(patch);\n if (refused !== undefined) return Promise.reject(refused);\n return this.request('PUT', subscriptionsPath(userId), patch);\n }\n\n /**\n * `getSubscriptions` for the recipient of one copy, by the message id an email's preferences\n * link carries (`email.preferencesUrl?m=<message id>`): how a preference page works without a\n * sign-in. The answer adds `message: { channel, topic }`, the copy the page was opened from,\n * so it can highlight that topic. Rejects with a 404 for an id that names no copy — never\n * sent, or erased with its user — and for a transactional one; the page should show the link\n * as expired. The message id is the capability, so the app proxies this behind its own API\n * and rate-limits it.\n */\n getSubscriptionsByMessage(messageId: string): Promise<MessageSubscriptions<ClientTopic<C>>> {\n return this.request('GET', subscriptionsByMessagePath(messageId));\n }\n\n /** `setSubscriptions` for the recipient of one copy; the patch and the answer are those of `getSubscriptionsByMessage`. */\n setSubscriptionsByMessage(\n messageId: string,\n patch: SubscriptionsPatch<ClientTopic<C>>\n ): Promise<MessageSubscriptions<ClientTopic<C>>> {\n const refused = this.undeclaredTopics(patch);\n if (refused !== undefined) return Promise.reject(refused);\n return this.request('PUT', subscriptionsByMessagePath(messageId), patch);\n }\n\n /** With a contract, the refusal of a patch naming a topic it does not declare; undefined when the patch is fine. */\n private undeclaredTopics(\n patch: SubscriptionsPatch<ClientTopic<C>>\n ): JourneyValidationError | undefined {\n const contract = this.options.contract;\n if (contract === undefined) return undefined;\n const issues: ContractIssue[] = [];\n for (const channel of SUBSCRIPTION_CHANNELS) {\n for (const topic of Object.keys(patch[channel]?.topics ?? {})) {\n // `includes`, not the predicate: narrowing `string` by a `topic is string` leaves `never`\n if (contract.topics.includes(topic)) continue;\n issues.push({ path: `${channel}.topics.${topic}`, message: 'not a declared topic' });\n }\n }\n return issues.length > 0\n ? new JourneyValidationError('/subscriptions', undefined, issues)\n : undefined;\n }\n\n /** Deploy a compiled bundle (BundleIR). */\n deploy(bundle: unknown): Promise<DeployResult> {\n return this.post<DeployResult>('/deploy', bundle);\n }\n\n private post<T>(path: string, body: unknown): Promise<T> {\n return this.request<T>('POST', path, body);\n }\n\n private async request<T>(\n method: 'GET' | 'POST' | 'PUT',\n path: string,\n body?: unknown\n ): Promise<T> {\n const headers: Record<string, string> = {};\n if (body !== undefined) headers['content-type'] = 'application/json';\n if (this.options.token !== undefined) headers.authorization = `Bearer ${this.options.token}`;\n const doFetch = this.options.fetch ?? fetch;\n const response = await doFetch(`${this.options.origin.replace(/\\/$/, '')}${path}`, {\n method,\n headers,\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n });\n if (!response.ok) throw new JourneyClientError(path, response.status, await response.text());\n return (await response.json()) as T;\n }\n}\n\nconst subscriptionsPath = (userId: string) => `/subscriptions/${encodeURIComponent(userId)}`;\nconst subscriptionsByMessagePath = (messageId: string) =>\n `/subscriptions/by-message/${encodeURIComponent(messageId)}`;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type * as core from 'zod/v4/core';\nimport type { Contract, ContractIssue, ProfilePatch, Topic } from './contract';\nimport type { BatchResult, JourneyBatch } from './host/batch';\nimport {\n type DeployResult,\n type MessageSubscriptions,\n SUBSCRIPTION_CHANNELS,\n type Subscriptions,\n type SubscriptionsPatch,\n} from './host/ingest';\nimport type { ProfileProps } from './store';\n\n/**\n * `@shware/workflow/client` — the producer side of the ingest API, for the\n * service that knows what users do (an api server, a webhook handler). Plain\n * fetch, no dependencies; errors carry the status and body so a caller can\n * log them without failing the request that triggered the report.\n */\n\nexport interface JourneyClientOptions<C extends Contract | undefined = undefined> {\n /**\n * The shared `defineContract(...)`: types `ingest` (the event name picks the payload type, the\n * profile patch is the contract's), and checks both before anything is sent.\n */\n contract?: C;\n /** The journey Worker's origin, e.g. `https://workflow.example.com`. */\n origin: string;\n /** Its API_TOKEN, sent as a bearer; omit against an open local instance. */\n token?: string;\n /** Fetch implementation (defaults to the global one). */\n fetch?: typeof fetch;\n}\n\nexport class JourneyClientError extends Error {\n constructor(\n readonly path: string,\n readonly status: number,\n readonly body: string\n ) {\n super(`journey ${path} failed: ${status} ${body}`);\n this.name = 'JourneyClientError';\n }\n}\n\n/** A payload or profile patch the contract refuses — raised before the request, with the field paths. */\nexport class JourneyValidationError extends Error {\n constructor(\n readonly path: string,\n readonly event: string | undefined,\n readonly issues: ContractIssue[]\n ) {\n super(\n `journey ${path}${event === undefined ? '' : ` '${event}'`} refused by the contract: ${issues\n .map((i) => `${i.path || '(root)'} ${i.message}`)\n .join('; ')}`\n );\n this.name = 'JourneyValidationError';\n }\n}\n\ntype EventsOf<C> = C extends Contract<infer E> ? E : never;\ntype IdentifyProps<C> = [C] extends [undefined]\n ? ProfileProps\n : C extends Contract<Record<string, core.$ZodObject>, infer P>\n ? ProfilePatch<P>\n : never;\n\n/** The topics the client's contract declares; any string without a contract. */\nexport type ClientTopic<C> = [C] extends [undefined] ? string : Topic<C>;\n\nexport type { BatchEvent, BatchResult, JourneyBatch } from './host/batch';\nexport type {\n ChannelSubscriptions,\n ChannelSubscriptionsPatch,\n MessageOrigin,\n MessageSubscriptions,\n SubscriptionChannel,\n Subscriptions,\n SubscriptionsPatch,\n} from './host/ingest';\n\n/** One event of a batch: with a contract, the name picks the payload type. */\nexport type BatchEventInput<C> = [C] extends [undefined]\n ? { event: string; payload?: Record<string, unknown>; id?: string; ts?: number }\n : {\n [K in keyof EventsOf<C> & string]: {\n event: K;\n payload: core.input<EventsOf<C>[K]>;\n id?: string;\n ts?: number;\n };\n }[keyof EventsOf<C> & string];\n\n/**\n * Who a report or device is about: a signed-in user, or a visitor before sign-in (a stable id for\n * one client, renewed when a user signs out). Naming both says this visitor is this user: what the\n * engine knows of the visitor moves to the user, once.\n */\nexport type JourneyIdentity =\n | { userId: string; visitorId?: string | undefined }\n | { userId?: undefined; visitorId: string };\n\n/** What the profile now says and what they just did, as one report. */\nexport type BatchInput<C> = JourneyIdentity & {\n /** Merged before the first event: a journey the events start finds this recipient. */\n profile?: IdentifyProps<C>;\n events?: readonly BatchEventInput<C>[];\n};\n\n/**\n * The batch as it travels: checked against the contract, with the time and the event ids and\n * timestamps fixed here, so every redelivery is the same report.\n */\nfunction prepareBatch<C extends Contract | undefined>(\n contract: C,\n input: BatchInput<C>\n): JourneyBatch {\n const { userId, visitorId, profile, events } = input as {\n userId?: string;\n visitorId?: string;\n profile?: ProfileProps;\n events?: readonly {\n event: string;\n payload?: Record<string, unknown>;\n id?: string;\n ts?: number;\n }[];\n };\n if (contract !== undefined) {\n if (profile !== undefined) {\n const result = contract.parseProfilePatch(profile);\n if (!result.ok) throw new JourneyValidationError('/ingest', undefined, result.issues);\n }\n for (const { event, payload } of events ?? []) {\n const result = contract.parseEvent(event, payload ?? {});\n if (!result.ok) throw new JourneyValidationError('/ingest', event, result.issues);\n }\n }\n const now = Date.now();\n return {\n ...(userId === undefined ? {} : { userId }),\n ...(visitorId === undefined ? {} : { visitorId }),\n ...(profile === undefined ? {} : { profile, at: now }),\n ...(events === undefined\n ? {}\n : {\n events: events.map((e) => ({\n event: e.event,\n payload: e.payload ?? {},\n id: e.id ?? crypto.randomUUID(),\n ts: e.ts ?? now,\n })),\n }),\n };\n}\n\n/** The producer side of a queue (the subset of a Cloudflare Queues binding used). */\nexport interface JourneyQueueBinding {\n send(body: JourneyBatch): Promise<unknown>;\n}\n\nexport interface JourneyQueueOptions<C extends Contract | undefined = undefined> {\n /** The shared `defineContract(...)`: types `ingest` and checks each batch before it is sent. */\n contract?: C;\n /** The queue `journeyWorker`'s `queue` handler consumes. */\n queue: JourneyQueueBinding;\n}\n\n/**\n * `JourneyClient.ingest` through a queue, for a producer that must not lose a report and must not\n * wait for the engine: once `ingest` resolves the batch is durable, and the consumer retries it\n * until it is applied. Delivery is at least once and in no guaranteed order: the batch is the unit\n * that stays in order and event ids and property times absorb the rest. A rejected `ingest` was\n * not queued; the caller decides (a 5xx so its client retries, say).\n */\nexport class JourneyQueue<C extends Contract | undefined = undefined> {\n constructor(private readonly options: JourneyQueueOptions<C>) {}\n\n async ingest(input: BatchInput<C>): Promise<void> {\n await this.options.queue.send(prepareBatch(this.options.contract as C, input));\n }\n}\n\nexport class JourneyClient<C extends Contract | undefined = undefined> {\n constructor(private readonly options: JourneyClientOptions<C>) {}\n\n /**\n * The one way in: a profile and the events it belongs to, as one batch. A visitor who signed in\n * merges first, then the profile, then the events in order, so a journey cannot start without\n * its recipient. A caller that retries should pass each event's `id`: the Worker stores one\n * occurrence per id. `ts` defaults to now. `JourneyQueue` is the same report through a queue.\n */\n async ingest(input: BatchInput<C>): Promise<BatchResult> {\n return this.post<BatchResult>('/ingest', prepareBatch(this.options.contract as C, input));\n }\n\n /**\n * A push token registered for whoever holds the device now (call it on every registration): a\n * token moves to the latest owner, and a `deviceId` seen with a new token replaces the old one.\n * The engine keeps devices itself — pushes go to them, a token APNs / FCM reports as gone is\n * dropped — and journeys waiting on `reachable('push')` wake. Naming both ids merges the visitor,\n * as `ingest` does.\n */\n registerDevice(\n device: JourneyIdentity & {\n deviceId?: string;\n token: string;\n platform: 'ios' | 'android' | 'web';\n }\n ): Promise<{ ok: true; woke: number }> {\n return this.post('/devices', device);\n }\n\n /**\n * A journey push tapped open: the `message_id` the push carried, reported for the user who\n * tapped it. Recorded as a human open on the copy (an email's opens are tracked by the Worker).\n */\n messageOpened(userId: string, messageId: string): Promise<{ ok: true; recorded: boolean }> {\n return this.post('/messages/opened', { userId, messageId });\n }\n\n /**\n * A deleted account: the engine records the erasure, stops the user's running journeys, then\n * erases everything it keeps about them (profile, devices, events, reporting). Once this\n * resolves the request is durable: `erased: false` means an instance would not stop yet, and\n * the Worker's scheduled sweep finishes the job.\n */\n forgetUser(userId: string): Promise<{ ok: true; erased: boolean; pending: string[] }> {\n return this.post('/users/forget', { userId });\n }\n\n /** `forgetUser` for a visitor who never signed in. */\n forgetVisitor(visitorId: string): Promise<{ ok: true; erased: boolean; pending: string[] }> {\n return this.post('/users/forget', { visitorId });\n }\n\n /** The user signed out on that device: the token stays registered, bound to nobody. */\n unbindDevice(token: string): Promise<{ ok: true; woke: number }> {\n return this.post('/devices/unbind', { token });\n }\n\n /** Forget a push token for good (an uninstall the app learned of, a token it replaced). */\n removeDevice(token: string): Promise<{ ok: true; woke: number }> {\n return this.post('/devices/remove', { token });\n }\n\n /**\n * Where the user stands, topic × channel: for `email` and for `push`, `all` (false once they\n * opted out of the whole channel) and each declared topic (false once they opted out of it on\n * that channel) — the engine's record, which a preference page shows as it is. The two are\n * independent flags: a topic answers its own opt-out even while `all` is false, so `all: false`\n * masks every topic however they read (nothing is sent on the channel until it is true again).\n * Rejects with a 410 for an erased user.\n */\n getSubscriptions(userId: string): Promise<Subscriptions<ClientTopic<C>>> {\n return this.request('GET', subscriptionsPath(userId));\n }\n\n /**\n * Set subscriptions: only what the patch names changes, per channel (`{ email: { topics: {\n * newsletter: false } } }` opts out of one topic's emails, `{ push: { all: false } }` of every\n * push — the in-app opt-out App Store Review Guideline 4.5.4 asks for). `all: true` clears the\n * channel-wide opt-out only: a topic opted out on its own stays off until the patch names it,\n * so a preference page's \"subscribe to all\" sends `all: true` plus every topic `true`. Answers\n * the subscriptions as they now stand. With a contract, a topic it does not declare is refused\n * here, before the request; the engine refuses it too.\n */\n setSubscriptions(\n userId: string,\n patch: SubscriptionsPatch<ClientTopic<C>>\n ): Promise<Subscriptions<ClientTopic<C>>> {\n const refused = this.undeclaredTopics(patch);\n if (refused !== undefined) return Promise.reject(refused);\n return this.request('PUT', subscriptionsPath(userId), patch);\n }\n\n /**\n * `getSubscriptions` for the recipient of one copy, by the message id an email's preferences\n * link carries (`email.preferencesUrl?m=<message id>`): how a preference page works without a\n * sign-in. The answer adds `message: { channel, topic }`, the copy the page was opened from,\n * so it can highlight that topic. Rejects with a 404 for an id that names no copy — never\n * sent, or erased with its user — and for a transactional one; the page should show the link\n * as expired. The message id is the capability, so the app proxies this behind its own API\n * and rate-limits it.\n */\n getSubscriptionsByMessage(messageId: string): Promise<MessageSubscriptions<ClientTopic<C>>> {\n return this.request('GET', subscriptionsByMessagePath(messageId));\n }\n\n /** `setSubscriptions` for the recipient of one copy; the patch and the answer are those of `getSubscriptionsByMessage`. */\n setSubscriptionsByMessage(\n messageId: string,\n patch: SubscriptionsPatch<ClientTopic<C>>\n ): Promise<MessageSubscriptions<ClientTopic<C>>> {\n const refused = this.undeclaredTopics(patch);\n if (refused !== undefined) return Promise.reject(refused);\n return this.request('PUT', subscriptionsByMessagePath(messageId), patch);\n }\n\n /** With a contract, the refusal of a patch naming a topic it does not declare; undefined when the patch is fine. */\n private undeclaredTopics(\n patch: SubscriptionsPatch<ClientTopic<C>>\n ): JourneyValidationError | undefined {\n const contract = this.options.contract;\n if (contract === undefined) return undefined;\n const issues: ContractIssue[] = [];\n for (const channel of SUBSCRIPTION_CHANNELS) {\n for (const topic of Object.keys(patch[channel]?.topics ?? {})) {\n // `includes`, not the predicate: narrowing `string` by a `topic is string` leaves `never`\n if (contract.topics.includes(topic)) continue;\n issues.push({ path: `${channel}.topics.${topic}`, message: 'not a declared topic' });\n }\n }\n return issues.length > 0\n ? new JourneyValidationError('/subscriptions', undefined, issues)\n : undefined;\n }\n\n /** Deploy a compiled bundle (BundleIR). */\n deploy(bundle: unknown): Promise<DeployResult> {\n return this.post<DeployResult>('/deploy', bundle);\n }\n\n private post<T>(path: string, body: unknown): Promise<T> {\n return this.request<T>('POST', path, body);\n }\n\n private async request<T>(\n method: 'GET' | 'POST' | 'PUT',\n path: string,\n body?: unknown\n ): Promise<T> {\n const headers: Record<string, string> = {};\n if (body !== undefined) headers['content-type'] = 'application/json';\n if (this.options.token !== undefined) headers.authorization = `Bearer ${this.options.token}`;\n const doFetch = this.options.fetch ?? fetch;\n const response = await doFetch(`${this.options.origin.replace(/\\/$/, '')}${path}`, {\n method,\n headers,\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n });\n if (!response.ok) throw new JourneyClientError(path, response.status, await response.text());\n return (await response.json()) as T;\n }\n}\n\nconst subscriptionsPath = (userId: string) => `/subscriptions/${encodeURIComponent(userId)}`;\nconst subscriptionsByMessagePath = (messageId: string) =>\n `/subscriptions/by-message/${encodeURIComponent(messageId)}`;\n"],"mappings":"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"}
|
|
@@ -8,16 +8,17 @@ import { PostgresReporting } from "../store/postgres-reporting.cjs";
|
|
|
8
8
|
import { BundleVersion, Engagement, EntryInput, EntryOutcome, EventInput, JourneyReporting, JourneyStore, MessageRecord, ProfileProps, SchemaMismatchError, SegmentTriggerRoute, StoreLease, TriggerRoute } from "../store/index.cjs";
|
|
9
9
|
import { EmailMessage, EmailRenderer, EmailSender, EmailSenderOptions, EmailTransport, RenderedEmail, trackEmail } from "../email-sender.cjs";
|
|
10
10
|
import { Authorize, authorized, bearerMatches, timingSafeEqual } from "../host/auth.cjs";
|
|
11
|
+
import { TrackingOptions, handleTracking, recordPushOpen } from "../reporting/tracking.cjs";
|
|
11
12
|
import { EmailBindingLike, WAKE_TIMEOUT_TOLERANCE_MS, cloudflareJourneyBinding, wakeExpired } from "./bindings.cjs";
|
|
12
13
|
import { InstanceNotFoundError, JourneyEnv, JourneyParams, WAKE_EVENT_TYPE, WorkflowBindingLike, WorkflowInstanceLike } from "../host/platform.cjs";
|
|
13
14
|
import { BundleIntegrityError, DeployResult, DeviceRegistration, IdentifyResult, IngestInput, IngestResult, SWEEP_PAGE_SIZE, SweepPage, SweepResult, deployBundle, forgetUser, identifyUser, ingestEvent, registerDevice, removeDevice, sweepMaintenance, sweepSegments, sweepSegmentsPaged, unbindDevice } from "../host/ingest.cjs";
|
|
14
|
-
import { EnvValidationError } from "../host/journey-host.cjs";
|
|
15
|
-
import { TrackingOptions, handleTracking, recordPushOpen } from "../reporting/tracking.cjs";
|
|
16
15
|
import { IngestValidation, handleRequest } from "../host/http.cjs";
|
|
16
|
+
import { QueueMessageLike } from "../host/batch.cjs";
|
|
17
|
+
import { EnvValidationError } from "../host/journey-host.cjs";
|
|
17
18
|
import { JourneyRunner } from "./runner.cjs";
|
|
18
19
|
import { LogMessageSender, WebhookMessageSender, routeByChannel, routeByPlatform } from "../host/senders.cjs";
|
|
19
20
|
import { ChannelsConfig, channelsFromConfig, journeyMode, runtimeFromConfig } from "../host/channels.cjs";
|
|
20
21
|
import { reportingObserver } from "../reporting/observer.cjs";
|
|
21
22
|
import { BURST_WINDOW_MS, ClassifyOptions, ClickFacts, DEFAULT_QUICK_MS, RequestFacts, Verdict, classifyClick, classifyOpen } from "../reporting/classify.cjs";
|
|
22
23
|
import { ExecutionContextLike, JourneyWorker, JourneyWorkerOptions, journeyWorker } from "./worker.cjs";
|
|
23
|
-
export { type Authorize, BURST_WINDOW_MS, BundleIntegrityError, type BundleVersion, type ChannelsConfig, type ClassifyOptions, type ClickFacts, DEFAULT_QUICK_MS, type DeployResult, type DeviceRegistration, type EmailAddress, type EmailBindingLike, type EmailMessage, type EmailRenderer, EmailSender, type EmailSenderOptions, type EmailTransport, type Engagement, type EntryInput, type EntryOutcome, EnvValidationError, type EventInput, type ExecutionContextLike, type IdentifyResult, type IngestInput, type IngestResult, type IngestValidation, InstanceNotFoundError, type JourneyEnv, JourneyFactSource, type JourneyObserver, type JourneyParams, type JourneyReporting, JourneyRunner, type JourneyStore, type JourneyWorker, type JourneyWorkerOptions, type LinkPurpose, LogMessageSender, type MessageRecord, type MessageTracking, type OutboundMessage, PostgresJourneyStore, type PostgresJourneyStoreOptions, PostgresReporting, type ProfileProps, type PushDevice, type RenderedEmail, type RequestFacts, SWEEP_PAGE_SIZE, SchemaMismatchError, type SegmentTriggerRoute, type SendResult, type SqlClientLike, type StoreLease, type SweepPage, type SweepResult, type TrackingOptions, type TriggerRoute, UserErasedError, type Verdict, WAKE_EVENT_TYPE, WAKE_TIMEOUT_TOLERANCE_MS, WebhookMessageSender, type WorkflowBindingLike, type WorkflowInstanceLike, authorized, bearerMatches, channelsFromConfig, classifyClick, classifyOpen, cloudflareJourneyBinding, deployBundle, forgetUser, handleRequest, handleTracking, identifyUser, ingestEvent, journeyMode, journeyWorker, recordPushOpen, registerDevice, removeDevice, reportingObserver, routeByChannel, routeByPlatform, runtimeFromConfig, sweepMaintenance, sweepSegments, sweepSegmentsPaged, timingSafeEqual, trackEmail, unbindDevice, wakeExpired };
|
|
24
|
+
export { type Authorize, BURST_WINDOW_MS, BundleIntegrityError, type BundleVersion, type ChannelsConfig, type ClassifyOptions, type ClickFacts, DEFAULT_QUICK_MS, type DeployResult, type DeviceRegistration, type EmailAddress, type EmailBindingLike, type EmailMessage, type EmailRenderer, EmailSender, type EmailSenderOptions, type EmailTransport, type Engagement, type EntryInput, type EntryOutcome, EnvValidationError, type EventInput, type ExecutionContextLike, type IdentifyResult, type IngestInput, type IngestResult, type IngestValidation, InstanceNotFoundError, type JourneyEnv, JourneyFactSource, type JourneyObserver, type JourneyParams, type JourneyReporting, JourneyRunner, type JourneyStore, type JourneyWorker, type JourneyWorkerOptions, type LinkPurpose, LogMessageSender, type MessageRecord, type MessageTracking, type OutboundMessage, PostgresJourneyStore, type PostgresJourneyStoreOptions, PostgresReporting, type ProfileProps, type PushDevice, type QueueMessageLike, type RenderedEmail, type RequestFacts, SWEEP_PAGE_SIZE, SchemaMismatchError, type SegmentTriggerRoute, type SendResult, type SqlClientLike, type StoreLease, type SweepPage, type SweepResult, type TrackingOptions, type TriggerRoute, UserErasedError, type Verdict, WAKE_EVENT_TYPE, WAKE_TIMEOUT_TOLERANCE_MS, WebhookMessageSender, type WorkflowBindingLike, type WorkflowInstanceLike, authorized, bearerMatches, channelsFromConfig, classifyClick, classifyOpen, cloudflareJourneyBinding, deployBundle, forgetUser, handleRequest, handleTracking, identifyUser, ingestEvent, journeyMode, journeyWorker, recordPushOpen, registerDevice, removeDevice, reportingObserver, routeByChannel, routeByPlatform, runtimeFromConfig, sweepMaintenance, sweepSegments, sweepSegmentsPaged, timingSafeEqual, trackEmail, unbindDevice, wakeExpired };
|
|
@@ -8,16 +8,17 @@ import { PostgresReporting } from "../store/postgres-reporting.mjs";
|
|
|
8
8
|
import { BundleVersion, Engagement, EntryInput, EntryOutcome, EventInput, JourneyReporting, JourneyStore, MessageRecord, ProfileProps, SchemaMismatchError, SegmentTriggerRoute, StoreLease, TriggerRoute } from "../store/index.mjs";
|
|
9
9
|
import { EmailMessage, EmailRenderer, EmailSender, EmailSenderOptions, EmailTransport, RenderedEmail, trackEmail } from "../email-sender.mjs";
|
|
10
10
|
import { Authorize, authorized, bearerMatches, timingSafeEqual } from "../host/auth.mjs";
|
|
11
|
+
import { TrackingOptions, handleTracking, recordPushOpen } from "../reporting/tracking.mjs";
|
|
11
12
|
import { EmailBindingLike, WAKE_TIMEOUT_TOLERANCE_MS, cloudflareJourneyBinding, wakeExpired } from "./bindings.mjs";
|
|
12
13
|
import { InstanceNotFoundError, JourneyEnv, JourneyParams, WAKE_EVENT_TYPE, WorkflowBindingLike, WorkflowInstanceLike } from "../host/platform.mjs";
|
|
13
14
|
import { BundleIntegrityError, DeployResult, DeviceRegistration, IdentifyResult, IngestInput, IngestResult, SWEEP_PAGE_SIZE, SweepPage, SweepResult, deployBundle, forgetUser, identifyUser, ingestEvent, registerDevice, removeDevice, sweepMaintenance, sweepSegments, sweepSegmentsPaged, unbindDevice } from "../host/ingest.mjs";
|
|
14
|
-
import { EnvValidationError } from "../host/journey-host.mjs";
|
|
15
|
-
import { TrackingOptions, handleTracking, recordPushOpen } from "../reporting/tracking.mjs";
|
|
16
15
|
import { IngestValidation, handleRequest } from "../host/http.mjs";
|
|
16
|
+
import { QueueMessageLike } from "../host/batch.mjs";
|
|
17
|
+
import { EnvValidationError } from "../host/journey-host.mjs";
|
|
17
18
|
import { JourneyRunner } from "./runner.mjs";
|
|
18
19
|
import { LogMessageSender, WebhookMessageSender, routeByChannel, routeByPlatform } from "../host/senders.mjs";
|
|
19
20
|
import { ChannelsConfig, channelsFromConfig, journeyMode, runtimeFromConfig } from "../host/channels.mjs";
|
|
20
21
|
import { reportingObserver } from "../reporting/observer.mjs";
|
|
21
22
|
import { BURST_WINDOW_MS, ClassifyOptions, ClickFacts, DEFAULT_QUICK_MS, RequestFacts, Verdict, classifyClick, classifyOpen } from "../reporting/classify.mjs";
|
|
22
23
|
import { ExecutionContextLike, JourneyWorker, JourneyWorkerOptions, journeyWorker } from "./worker.mjs";
|
|
23
|
-
export { type Authorize, BURST_WINDOW_MS, BundleIntegrityError, type BundleVersion, type ChannelsConfig, type ClassifyOptions, type ClickFacts, DEFAULT_QUICK_MS, type DeployResult, type DeviceRegistration, type EmailAddress, type EmailBindingLike, type EmailMessage, type EmailRenderer, EmailSender, type EmailSenderOptions, type EmailTransport, type Engagement, type EntryInput, type EntryOutcome, EnvValidationError, type EventInput, type ExecutionContextLike, type IdentifyResult, type IngestInput, type IngestResult, type IngestValidation, InstanceNotFoundError, type JourneyEnv, JourneyFactSource, type JourneyObserver, type JourneyParams, type JourneyReporting, JourneyRunner, type JourneyStore, type JourneyWorker, type JourneyWorkerOptions, type LinkPurpose, LogMessageSender, type MessageRecord, type MessageTracking, type OutboundMessage, PostgresJourneyStore, type PostgresJourneyStoreOptions, PostgresReporting, type ProfileProps, type PushDevice, type RenderedEmail, type RequestFacts, SWEEP_PAGE_SIZE, SchemaMismatchError, type SegmentTriggerRoute, type SendResult, type SqlClientLike, type StoreLease, type SweepPage, type SweepResult, type TrackingOptions, type TriggerRoute, UserErasedError, type Verdict, WAKE_EVENT_TYPE, WAKE_TIMEOUT_TOLERANCE_MS, WebhookMessageSender, type WorkflowBindingLike, type WorkflowInstanceLike, authorized, bearerMatches, channelsFromConfig, classifyClick, classifyOpen, cloudflareJourneyBinding, deployBundle, forgetUser, handleRequest, handleTracking, identifyUser, ingestEvent, journeyMode, journeyWorker, recordPushOpen, registerDevice, removeDevice, reportingObserver, routeByChannel, routeByPlatform, runtimeFromConfig, sweepMaintenance, sweepSegments, sweepSegmentsPaged, timingSafeEqual, trackEmail, unbindDevice, wakeExpired };
|
|
24
|
+
export { type Authorize, BURST_WINDOW_MS, BundleIntegrityError, type BundleVersion, type ChannelsConfig, type ClassifyOptions, type ClickFacts, DEFAULT_QUICK_MS, type DeployResult, type DeviceRegistration, type EmailAddress, type EmailBindingLike, type EmailMessage, type EmailRenderer, EmailSender, type EmailSenderOptions, type EmailTransport, type Engagement, type EntryInput, type EntryOutcome, EnvValidationError, type EventInput, type ExecutionContextLike, type IdentifyResult, type IngestInput, type IngestResult, type IngestValidation, InstanceNotFoundError, type JourneyEnv, JourneyFactSource, type JourneyObserver, type JourneyParams, type JourneyReporting, JourneyRunner, type JourneyStore, type JourneyWorker, type JourneyWorkerOptions, type LinkPurpose, LogMessageSender, type MessageRecord, type MessageTracking, type OutboundMessage, PostgresJourneyStore, type PostgresJourneyStoreOptions, PostgresReporting, type ProfileProps, type PushDevice, type QueueMessageLike, type RenderedEmail, type RequestFacts, SWEEP_PAGE_SIZE, SchemaMismatchError, type SegmentTriggerRoute, type SendResult, type SqlClientLike, type StoreLease, type SweepPage, type SweepResult, type TrackingOptions, type TriggerRoute, UserErasedError, type Verdict, WAKE_EVENT_TYPE, WAKE_TIMEOUT_TOLERANCE_MS, WebhookMessageSender, type WorkflowBindingLike, type WorkflowInstanceLike, authorized, bearerMatches, channelsFromConfig, classifyClick, classifyOpen, cloudflareJourneyBinding, deployBundle, forgetUser, handleRequest, handleTracking, identifyUser, ingestEvent, journeyMode, journeyWorker, recordPushOpen, registerDevice, removeDevice, reportingObserver, routeByChannel, routeByPlatform, runtimeFromConfig, sweepMaintenance, sweepSegments, sweepSegmentsPaged, timingSafeEqual, trackEmail, unbindDevice, wakeExpired };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_engine_retry = require("../engine/retry.cjs");
|
|
3
|
+
const require_store_identity = require("../store/identity.cjs");
|
|
4
|
+
require("../store/index.cjs");
|
|
3
5
|
const require_host_platform = require("../host/platform.cjs");
|
|
4
6
|
const require_runtime = require("../runtime.cjs");
|
|
5
7
|
const require_host_execute = require("../host/execute.cjs");
|
|
@@ -52,7 +54,7 @@ var CfEngineStep = class {
|
|
|
52
54
|
return this.step.sleep(name, ms);
|
|
53
55
|
}
|
|
54
56
|
subscribe(name, events) {
|
|
55
|
-
return this.step.do(name, () => this.store
|
|
57
|
+
return this.step.do(name, () => require_store_identity.instanceOwner(this.store, this.instanceId, this.userId).then((owner) => this.store.subscribe(this.instanceId, owner, events, Date.now())));
|
|
56
58
|
}
|
|
57
59
|
unsubscribe(name) {
|
|
58
60
|
return this.step.do(name, () => this.store.unsubscribe(this.instanceId));
|