@getuserfeedback/protocol 0.6.1 → 0.8.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/dist/app-event.d.ts +779 -10
- package/dist/app-event.d.ts.map +1 -1
- package/dist/app-event.js +81 -8
- package/dist/flow-assignments.d.ts +7 -4
- package/dist/flow-assignments.d.ts.map +1 -1
- package/dist/flow-assignments.js +15 -5
- package/dist/host/constants.d.ts +2 -2
- package/dist/host/constants.d.ts.map +1 -1
- package/dist/host/constants.js +3 -1
- package/dist/host/external-id-argument-guards.d.ts +10 -0
- package/dist/host/external-id-argument-guards.d.ts.map +1 -0
- package/dist/host/external-id-argument-guards.js +27 -0
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +1 -0
- package/dist/host/sdk-types.d.ts +92 -27
- package/dist/host/sdk-types.d.ts.map +1 -1
- package/dist/host/sdk.d.ts.map +1 -1
- package/dist/identity-type.d.ts +2 -1
- package/dist/identity-type.d.ts.map +1 -1
- package/dist/identity-type.js +1 -1
- package/dist/index.d.ts +3 -87
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/widget-commands.d.ts +124 -3
- package/dist/widget-commands.d.ts.map +1 -1
- package/dist/widget-commands.js +10 -0
- package/dist/widget-config.d.ts +23 -5
- package/dist/widget-config.d.ts.map +1 -1
- package/dist/widget-config.js +2 -1
- package/package.json +22 -2
- package/src/app-event.ts +235 -0
- package/src/client-meta.ts +25 -0
- package/src/defaults.ts +4 -0
- package/src/errors.ts +58 -0
- package/src/flow-assignments.test.ts +82 -0
- package/src/flow-assignments.ts +151 -0
- package/src/host/command-dispatch.ts +59 -0
- package/src/host/command-envelope.ts +41 -0
- package/src/host/command-settlement.ts +121 -0
- package/src/host/constants.ts +103 -0
- package/src/host/external-id-argument-guards.ts +57 -0
- package/src/host/host-event-contract.ts +277 -0
- package/src/host/index.ts +14 -0
- package/src/host/lazy-handle-client.ts +207 -0
- package/src/host/request-id.ts +3 -0
- package/src/host/sdk-types.ts +528 -0
- package/src/host/sdk.ts +43 -0
- package/src/host/unique-id.ts +17 -0
- package/src/identity-type.ts +96 -0
- package/src/index.ts +139 -0
- package/src/protocol-root.test.ts +582 -0
- package/src/public-grant-scope.ts +25 -0
- package/src/runtime-endpoints.ts +69 -0
- package/src/scopes.ts +79 -0
- package/src/trpc-envelope.ts +9 -0
- package/src/version-resolution.ts +18 -0
- package/src/widget-commands.ts +162 -0
- package/src/widget-config.ts +164 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getuserfeedback/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "getuserfeedback widget protocol — host surface and (later) wire protocol",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"getuserfeedback",
|
|
@@ -8,82 +8,101 @@
|
|
|
8
8
|
"protocol"
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/getuserfeedback/getuserfeedback.git"
|
|
14
|
+
},
|
|
11
15
|
"publishConfig": {
|
|
12
16
|
"access": "public"
|
|
13
17
|
},
|
|
14
18
|
"type": "module",
|
|
15
19
|
"sideEffects": false,
|
|
16
20
|
"files": [
|
|
17
|
-
"dist"
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
18
23
|
],
|
|
19
24
|
"exports": {
|
|
20
25
|
".": {
|
|
21
26
|
"types": "./dist/index.d.ts",
|
|
27
|
+
"bun": "./src/index.ts",
|
|
22
28
|
"import": "./dist/index.js",
|
|
23
29
|
"default": "./dist/index.js"
|
|
24
30
|
},
|
|
25
31
|
"./host": {
|
|
26
32
|
"types": "./dist/host/index.d.ts",
|
|
33
|
+
"bun": "./src/host/index.ts",
|
|
27
34
|
"import": "./dist/host/index.js",
|
|
28
35
|
"default": "./dist/host/index.js"
|
|
29
36
|
},
|
|
30
37
|
"./host/sdk-types": {
|
|
31
38
|
"types": "./dist/host/sdk-types.d.ts",
|
|
39
|
+
"bun": "./src/host/sdk-types.ts",
|
|
32
40
|
"import": "./dist/host/sdk-types.js",
|
|
33
41
|
"default": "./dist/host/sdk-types.js"
|
|
34
42
|
},
|
|
35
43
|
"./host/host-event-contract": {
|
|
36
44
|
"types": "./dist/host/host-event-contract.d.ts",
|
|
45
|
+
"bun": "./src/host/host-event-contract.ts",
|
|
37
46
|
"import": "./dist/host/host-event-contract.js",
|
|
38
47
|
"default": "./dist/host/host-event-contract.js"
|
|
39
48
|
},
|
|
40
49
|
"./host/constants": {
|
|
41
50
|
"types": "./dist/host/constants.d.ts",
|
|
51
|
+
"bun": "./src/host/constants.ts",
|
|
42
52
|
"import": "./dist/host/constants.js",
|
|
43
53
|
"default": "./dist/host/constants.js"
|
|
44
54
|
},
|
|
45
55
|
"./host/command-dispatch": {
|
|
46
56
|
"types": "./dist/host/command-dispatch.d.ts",
|
|
57
|
+
"bun": "./src/host/command-dispatch.ts",
|
|
47
58
|
"import": "./dist/host/command-dispatch.js",
|
|
48
59
|
"default": "./dist/host/command-dispatch.js"
|
|
49
60
|
},
|
|
50
61
|
"./host/command-settlement": {
|
|
51
62
|
"types": "./dist/host/command-settlement.d.ts",
|
|
63
|
+
"bun": "./src/host/command-settlement.ts",
|
|
52
64
|
"import": "./dist/host/command-settlement.js",
|
|
53
65
|
"default": "./dist/host/command-settlement.js"
|
|
54
66
|
},
|
|
55
67
|
"./host/sdk": {
|
|
56
68
|
"types": "./dist/host/sdk.d.ts",
|
|
69
|
+
"bun": "./src/host/sdk.ts",
|
|
57
70
|
"import": "./dist/host/sdk.js",
|
|
58
71
|
"default": "./dist/host/sdk.js"
|
|
59
72
|
},
|
|
60
73
|
"./host/request-id": {
|
|
61
74
|
"types": "./dist/host/request-id.d.ts",
|
|
75
|
+
"bun": "./src/host/request-id.ts",
|
|
62
76
|
"import": "./dist/host/request-id.js",
|
|
63
77
|
"default": "./dist/host/request-id.js"
|
|
64
78
|
},
|
|
65
79
|
"./host/unique-id": {
|
|
66
80
|
"types": "./dist/host/unique-id.d.ts",
|
|
81
|
+
"bun": "./src/host/unique-id.ts",
|
|
67
82
|
"import": "./dist/host/unique-id.js",
|
|
68
83
|
"default": "./dist/host/unique-id.js"
|
|
69
84
|
},
|
|
70
85
|
"./host/lazy-handle-client": {
|
|
71
86
|
"types": "./dist/host/lazy-handle-client.d.ts",
|
|
87
|
+
"bun": "./src/host/lazy-handle-client.ts",
|
|
72
88
|
"import": "./dist/host/lazy-handle-client.js",
|
|
73
89
|
"default": "./dist/host/lazy-handle-client.js"
|
|
74
90
|
},
|
|
75
91
|
"./version-resolution": {
|
|
76
92
|
"types": "./dist/version-resolution.d.ts",
|
|
93
|
+
"bun": "./src/version-resolution.ts",
|
|
77
94
|
"import": "./dist/version-resolution.js",
|
|
78
95
|
"default": "./dist/version-resolution.js"
|
|
79
96
|
},
|
|
80
97
|
"./flow-assignments": {
|
|
81
98
|
"types": "./dist/flow-assignments.d.ts",
|
|
99
|
+
"bun": "./src/flow-assignments.ts",
|
|
82
100
|
"import": "./dist/flow-assignments.js",
|
|
83
101
|
"default": "./dist/flow-assignments.js"
|
|
84
102
|
},
|
|
85
103
|
"./trpc-envelope": {
|
|
86
104
|
"types": "./dist/trpc-envelope.d.ts",
|
|
105
|
+
"bun": "./src/trpc-envelope.ts",
|
|
87
106
|
"import": "./dist/trpc-envelope.js",
|
|
88
107
|
"default": "./dist/trpc-envelope.js"
|
|
89
108
|
}
|
|
@@ -91,6 +110,7 @@
|
|
|
91
110
|
"scripts": {
|
|
92
111
|
"lint": "ultracite check .",
|
|
93
112
|
"build": "bun ../../scripts/clean-build-output.ts dist tsconfig.tsbuildinfo && tsc -b tsconfig.json",
|
|
113
|
+
"types": "bun ../../scripts/clean-build-output.ts dist tsconfig.tsbuildinfo && tsc -b tsconfig.json --emitDeclarationOnly",
|
|
94
114
|
"typecheck": "tsc -b tsconfig.json",
|
|
95
115
|
"test": "bun test --dots",
|
|
96
116
|
"pack:verify": "node ../../scripts/pack-and-verify.cjs",
|
package/src/app-event.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
import { appEventIdentityTypeSchema } from "./identity-type.js";
|
|
3
|
+
|
|
4
|
+
export { appEventIdentityTypeSchema } from "./identity-type.js";
|
|
5
|
+
|
|
6
|
+
export const appEventIdentitySchema = z.object({
|
|
7
|
+
type: appEventIdentityTypeSchema,
|
|
8
|
+
value: z.string().check(z.trim(), z.minLength(1)),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
type AppEventFlagValue =
|
|
12
|
+
| string
|
|
13
|
+
| number
|
|
14
|
+
| boolean
|
|
15
|
+
| null
|
|
16
|
+
| AppEventFlagValue[]
|
|
17
|
+
| { [key: string]: AppEventFlagValue };
|
|
18
|
+
|
|
19
|
+
export type AppEventJsonValue =
|
|
20
|
+
| string
|
|
21
|
+
| number
|
|
22
|
+
| boolean
|
|
23
|
+
| null
|
|
24
|
+
| AppEventJsonValue[]
|
|
25
|
+
| { [key: string]: AppEventJsonValue };
|
|
26
|
+
|
|
27
|
+
const appEventFlagValueSchema: z.ZodMiniType<AppEventFlagValue> = z.lazy(() =>
|
|
28
|
+
z.union([
|
|
29
|
+
z.string(),
|
|
30
|
+
z.number(),
|
|
31
|
+
z.boolean(),
|
|
32
|
+
z.null(),
|
|
33
|
+
z.array(appEventFlagValueSchema),
|
|
34
|
+
z.record(z.string(), appEventFlagValueSchema),
|
|
35
|
+
]),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const appEventJsonNumberSchema = z.number().check(
|
|
39
|
+
z.refine(Number.isFinite, {
|
|
40
|
+
message: "Expected a finite JSON number.",
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const appEventJsonValueSchema: z.ZodMiniType<AppEventJsonValue> = z.lazy(() =>
|
|
45
|
+
z.union([
|
|
46
|
+
z.string(),
|
|
47
|
+
appEventJsonNumberSchema,
|
|
48
|
+
z.boolean(),
|
|
49
|
+
z.null(),
|
|
50
|
+
z.array(appEventJsonValueSchema),
|
|
51
|
+
z.record(z.string(), appEventJsonValueSchema),
|
|
52
|
+
]),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const appEventJsonObjectSchema = z.record(z.string(), appEventJsonValueSchema);
|
|
56
|
+
|
|
57
|
+
export const appEventFlagProviderSchema = z.object({
|
|
58
|
+
name: z.string().check(z.trim(), z.minLength(1)),
|
|
59
|
+
project: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
60
|
+
environment: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const appEventFlagSchema = z.object({
|
|
64
|
+
key: z.string().check(z.trim(), z.minLength(1)),
|
|
65
|
+
target: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
66
|
+
value: appEventFlagValueSchema,
|
|
67
|
+
variant: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
68
|
+
origin: z.optional(
|
|
69
|
+
z.enum(["runtime_config", "trigger_context", "provider_sync", "api"]),
|
|
70
|
+
),
|
|
71
|
+
provider: z.optional(appEventFlagProviderSchema),
|
|
72
|
+
status: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
73
|
+
reason: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
74
|
+
evaluatedAt: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const appEventCapabilitySchema = z.object({
|
|
78
|
+
key: z.string().check(z.trim(), z.minLength(1)),
|
|
79
|
+
source: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
80
|
+
provider: z.optional(appEventFlagProviderSchema),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const appEventPageSchema = z.object({
|
|
84
|
+
path: z.optional(z.string()),
|
|
85
|
+
referrer: z.optional(z.string()),
|
|
86
|
+
search: z.optional(z.string()),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const externalIdTypeSchema = z.string().check(
|
|
90
|
+
z.trim(),
|
|
91
|
+
z.minLength(1),
|
|
92
|
+
z.regex(/^[a-z0-9][a-z0-9_.-]*$/, {
|
|
93
|
+
message:
|
|
94
|
+
"External ID type must start with a lowercase letter or number and may contain lowercase letters, numbers, underscores, dots, or hyphens.",
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
export const appEventExternalIdSchema = z.object({
|
|
99
|
+
id: z.pipe(
|
|
100
|
+
z.string().check(z.trim(), z.minLength(1)),
|
|
101
|
+
z.transform((value) => value.trim()),
|
|
102
|
+
),
|
|
103
|
+
type: externalIdTypeSchema,
|
|
104
|
+
collection: z.literal("users"),
|
|
105
|
+
encoding: z.literal("none"),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export type AppEventExternalId = z.output<typeof appEventExternalIdSchema>;
|
|
109
|
+
|
|
110
|
+
export const appEventContextSchema = z.object({
|
|
111
|
+
page: z.optional(appEventPageSchema),
|
|
112
|
+
locale: z.optional(z.string()),
|
|
113
|
+
userAgent: z.optional(z.string()),
|
|
114
|
+
externalIds: z.optional(
|
|
115
|
+
z.array(appEventExternalIdSchema).check(z.maxLength(20)),
|
|
116
|
+
),
|
|
117
|
+
flags: z.optional(z.array(appEventFlagSchema)),
|
|
118
|
+
capabilities: z.optional(z.array(appEventCapabilitySchema)),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export const appEventBaseSchema = z.object({
|
|
122
|
+
traits: z.optional(z.record(z.string(), z.unknown())),
|
|
123
|
+
identities: z.optional(z.array(appEventIdentitySchema)),
|
|
124
|
+
context: z.optional(appEventContextSchema),
|
|
125
|
+
messageId: z.optional(z.string()),
|
|
126
|
+
timestamp: z.optional(z.number()),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const appEventSurveyReferenceSchema = z.object({
|
|
130
|
+
surveyId: z.string(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export const reservedSystemAppEventNames = [
|
|
134
|
+
"Response Submitted",
|
|
135
|
+
"Flow Viewed",
|
|
136
|
+
"Flow Dismissed",
|
|
137
|
+
"Identified",
|
|
138
|
+
] as const;
|
|
139
|
+
const reservedSystemAppEventNameLowercaseSet = new Set<string>(
|
|
140
|
+
reservedSystemAppEventNames.map((event) => event.toLowerCase()),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const appEventSystemTrackNameSchema = z.enum(["Flow Viewed", "Flow Dismissed"]);
|
|
144
|
+
|
|
145
|
+
const appEventCustomerNameSchema = z.string().check(
|
|
146
|
+
z.trim(),
|
|
147
|
+
z.minLength(1),
|
|
148
|
+
z.maxLength(255),
|
|
149
|
+
z.check<string>((ctx) => {
|
|
150
|
+
if (!reservedSystemAppEventNameLowercaseSet.has(ctx.value.toLowerCase())) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
ctx.issues.push({
|
|
154
|
+
code: "custom",
|
|
155
|
+
input: ctx.value,
|
|
156
|
+
message:
|
|
157
|
+
"Customer app event names cannot use reserved system event names.",
|
|
158
|
+
continue: false,
|
|
159
|
+
});
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
export const appEventSystemBaseSchema = z.extend(appEventBaseSchema, {
|
|
164
|
+
origin: z.literal("system"),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const normalizeSystemAppEventOrigin = <T extends { origin?: "system" }>(
|
|
168
|
+
input: T,
|
|
169
|
+
): Omit<T, "origin"> & { origin: "system" } => {
|
|
170
|
+
const { origin: _origin, ...rest } = input;
|
|
171
|
+
return {
|
|
172
|
+
...rest,
|
|
173
|
+
origin: "system" as const,
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const appEventSurveyViewedSchema = z.pipe(
|
|
178
|
+
z.extend(appEventBaseSchema, {
|
|
179
|
+
origin: z.optional(z.literal("system")),
|
|
180
|
+
event: z.literal("Flow Viewed"),
|
|
181
|
+
references: appEventSurveyReferenceSchema,
|
|
182
|
+
}),
|
|
183
|
+
z.transform(normalizeSystemAppEventOrigin),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
export const appEventFlowDismissedSchema = z.pipe(
|
|
187
|
+
z.extend(appEventBaseSchema, {
|
|
188
|
+
origin: z.optional(z.literal("system")),
|
|
189
|
+
event: z.literal("Flow Dismissed"),
|
|
190
|
+
references: appEventSurveyReferenceSchema,
|
|
191
|
+
}),
|
|
192
|
+
z.transform(normalizeSystemAppEventOrigin),
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
export const appEventSystemTrackSchema = z.extend(appEventSystemBaseSchema, {
|
|
196
|
+
event: appEventSystemTrackNameSchema,
|
|
197
|
+
references: appEventSurveyReferenceSchema,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const appEventLegacySystemTrackSchema = z.pipe(
|
|
201
|
+
z.extend(appEventBaseSchema, {
|
|
202
|
+
origin: z.optional(z.literal("system")),
|
|
203
|
+
event: appEventSystemTrackNameSchema,
|
|
204
|
+
references: appEventSurveyReferenceSchema,
|
|
205
|
+
}),
|
|
206
|
+
z.transform(normalizeSystemAppEventOrigin),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
export const appEventCustomerTrackSchema = z.extend(appEventBaseSchema, {
|
|
210
|
+
origin: z.literal("customer"),
|
|
211
|
+
event: appEventCustomerNameSchema,
|
|
212
|
+
// TODO(migration): Normalize Date values in event properties to ISO strings.
|
|
213
|
+
// [from=manual-date-property-serialization] [to=protocol-date-property-normalization] [scope=slice] [priority=medium] [impact=medium] [risk=low]
|
|
214
|
+
properties: z.optional(appEventJsonObjectSchema),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export const appEventTrackSchema = z.union([
|
|
218
|
+
appEventLegacySystemTrackSchema,
|
|
219
|
+
appEventCustomerTrackSchema,
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
const appEventDiscriminatedPayloadSchema = z.discriminatedUnion("origin", [
|
|
223
|
+
appEventSystemTrackSchema,
|
|
224
|
+
appEventCustomerTrackSchema,
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
export const appEventPayloadSchema = z.union([
|
|
228
|
+
appEventLegacySystemTrackSchema,
|
|
229
|
+
appEventDiscriminatedPayloadSchema,
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
export type AppEventTrackInput = z.output<typeof appEventTrackSchema>;
|
|
233
|
+
export type AppEventPayload = z.output<typeof appEventPayloadSchema>;
|
|
234
|
+
export type AppEventFlag = z.output<typeof appEventFlagSchema>;
|
|
235
|
+
export type AppEventCapability = z.output<typeof appEventCapabilitySchema>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
|
|
3
|
+
export const clientMetaSchema = z.object({
|
|
4
|
+
loader: z.enum(["sdk", "gtm", "segment", "rudderstack", "tealium", "custom"]),
|
|
5
|
+
clientName: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
6
|
+
clientVersion: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
7
|
+
integrator: z.optional(
|
|
8
|
+
z.object({
|
|
9
|
+
name: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
10
|
+
version: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
11
|
+
}),
|
|
12
|
+
),
|
|
13
|
+
transport: z.optional(
|
|
14
|
+
z.enum(["script-tag", "esm", "loader", "tag-manager", "snippet"]),
|
|
15
|
+
),
|
|
16
|
+
runtime: z.optional(
|
|
17
|
+
z.object({
|
|
18
|
+
framework: z.optional(z.enum(["next", "vite", "webpack", "plain"])),
|
|
19
|
+
runtime: z.optional(z.enum(["browser", "edge"])),
|
|
20
|
+
}),
|
|
21
|
+
),
|
|
22
|
+
notes: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type ClientMeta = z.output<typeof clientMetaSchema>;
|
package/src/defaults.ts
ADDED
package/src/errors.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
|
|
3
|
+
import { clientMetaSchema } from "./client-meta.js";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Error-telemetry definitions shared across widget / host.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
export const ErrorCategory = {
|
|
10
|
+
INTERNAL: "INTERNAL",
|
|
11
|
+
INVALID_USAGE: "INVALID_USAGE",
|
|
12
|
+
HOST: "HOST",
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export type ErrorCategory = (typeof ErrorCategory)[keyof typeof ErrorCategory];
|
|
16
|
+
|
|
17
|
+
export const ErrorCategorySchema = z.enum([
|
|
18
|
+
ErrorCategory.INTERNAL,
|
|
19
|
+
ErrorCategory.INVALID_USAGE,
|
|
20
|
+
ErrorCategory.HOST,
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export const ErrorEventSchema = z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
name: z.string(),
|
|
26
|
+
message: z.string(),
|
|
27
|
+
stack: z.optional(z.string()),
|
|
28
|
+
category: ErrorCategorySchema,
|
|
29
|
+
timestamp: z.number(),
|
|
30
|
+
probability: z.optional(z.number()),
|
|
31
|
+
context: z.object({
|
|
32
|
+
widgetVersion: z.string(),
|
|
33
|
+
clientMeta: z.optional(clientMetaSchema),
|
|
34
|
+
/** When true, telemetry was captured with debug mode on (affects how to interpret context). */
|
|
35
|
+
debug: z.optional(z.boolean()),
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export type ErrorEvent = z.output<typeof ErrorEventSchema>;
|
|
40
|
+
|
|
41
|
+
export type ProtocolErrorCode = "PARSE" | "VERSION" | "UNKNOWN_TYPE";
|
|
42
|
+
|
|
43
|
+
const MAX_DETAIL_LENGTH = 80;
|
|
44
|
+
|
|
45
|
+
export class ProtocolError extends Error {
|
|
46
|
+
code: ProtocolErrorCode;
|
|
47
|
+
details: string;
|
|
48
|
+
|
|
49
|
+
constructor(code: ProtocolErrorCode, details: string) {
|
|
50
|
+
const short =
|
|
51
|
+
details.length > MAX_DETAIL_LENGTH
|
|
52
|
+
? details.slice(0, MAX_DETAIL_LENGTH)
|
|
53
|
+
: details;
|
|
54
|
+
super(short);
|
|
55
|
+
this.code = code;
|
|
56
|
+
this.details = short;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
claimPendingFlowAssignmentsRequestSchema,
|
|
4
|
+
flowAssignmentRecipientRequestSchema,
|
|
5
|
+
updateFlowAssignmentLifecycleRequestSchema,
|
|
6
|
+
} from "./flow-assignments";
|
|
7
|
+
|
|
8
|
+
describe("flow assignment request contracts", () => {
|
|
9
|
+
it("accepts runtime targeting context for listPending", () => {
|
|
10
|
+
const result = flowAssignmentRecipientRequestSchema.parse({
|
|
11
|
+
identities: [{ type: "userId", value: "user_1" }],
|
|
12
|
+
context: {
|
|
13
|
+
flags: [{ key: "new_checkout", value: true }],
|
|
14
|
+
capabilities: [{ key: "checkout.drawer", source: "host-app" }],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(result.context?.flags?.[0]).toEqual({
|
|
19
|
+
key: "new_checkout",
|
|
20
|
+
value: true,
|
|
21
|
+
});
|
|
22
|
+
expect(result.context?.capabilities?.[0]).toEqual({
|
|
23
|
+
key: "checkout.drawer",
|
|
24
|
+
source: "host-app",
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("accepts runtime targeting context for claimPending", () => {
|
|
29
|
+
const result = claimPendingFlowAssignmentsRequestSchema.parse({
|
|
30
|
+
holdOwnerKey: "inst_1",
|
|
31
|
+
context: {
|
|
32
|
+
flags: [{ key: "pricing_test", value: "variant_a" }],
|
|
33
|
+
capabilities: [{ key: "billing.portal" }],
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(result.identities).toEqual([]);
|
|
38
|
+
expect(result.context?.flags?.[0]?.key).toBe("pricing_test");
|
|
39
|
+
expect(result.context?.capabilities?.[0]?.key).toBe("billing.portal");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("accepts external ID identities for recipient continuity", () => {
|
|
43
|
+
const result = claimPendingFlowAssignmentsRequestSchema.parse({
|
|
44
|
+
holdOwnerKey: "inst_1",
|
|
45
|
+
identities: [
|
|
46
|
+
{
|
|
47
|
+
type: "externalId.shopify_customer_id",
|
|
48
|
+
value: "gid://shopify/Customer/123",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(result.identities).toEqual([
|
|
54
|
+
{
|
|
55
|
+
type: "externalId.shopify_customer_id",
|
|
56
|
+
value: "gid://shopify/Customer/123",
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("accepts runtime targeting context for lifecycle updates", () => {
|
|
62
|
+
const result = updateFlowAssignmentLifecycleRequestSchema.parse({
|
|
63
|
+
identities: [{ type: "userId", value: "user_1" }],
|
|
64
|
+
flowAssignmentId: "flasn_1",
|
|
65
|
+
flowVersionId: "flv_1",
|
|
66
|
+
holdGroupToken: "fahold_1",
|
|
67
|
+
context: {
|
|
68
|
+
flags: [{ key: "new_checkout", value: true }],
|
|
69
|
+
capabilities: [{ key: "checkout.drawer", source: "host-app" }],
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(result.context?.flags?.[0]).toEqual({
|
|
74
|
+
key: "new_checkout",
|
|
75
|
+
value: true,
|
|
76
|
+
});
|
|
77
|
+
expect(result.context?.capabilities?.[0]).toEqual({
|
|
78
|
+
key: "checkout.drawer",
|
|
79
|
+
source: "host-app",
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
appEventCapabilitySchema,
|
|
5
|
+
appEventFlagSchema,
|
|
6
|
+
appEventIdentityTypeSchema,
|
|
7
|
+
} from "./app-event.js";
|
|
8
|
+
import type { IdentityTypeValue } from "./identity-type.js";
|
|
9
|
+
|
|
10
|
+
export type FlowAssignmentExternalIdIdentityTypeValue = `externalId.${string}`;
|
|
11
|
+
export type FlowAssignmentIdentityTypeValue =
|
|
12
|
+
| IdentityTypeValue
|
|
13
|
+
| FlowAssignmentExternalIdIdentityTypeValue;
|
|
14
|
+
|
|
15
|
+
const flowAssignmentExternalIdIdentityTypeSchema = z.string().check(
|
|
16
|
+
z.trim(),
|
|
17
|
+
z.minLength(1),
|
|
18
|
+
z.regex(/^externalId\.[a-z0-9][a-z0-9_.-]*$/, {
|
|
19
|
+
message:
|
|
20
|
+
"External ID identity type must use externalId.<type> with a lowercase Segment external ID type.",
|
|
21
|
+
}),
|
|
22
|
+
) as z.ZodMiniType<
|
|
23
|
+
FlowAssignmentExternalIdIdentityTypeValue,
|
|
24
|
+
FlowAssignmentExternalIdIdentityTypeValue
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
const flowAssignmentIdentitySchema = z.object({
|
|
28
|
+
type: z.union([
|
|
29
|
+
appEventIdentityTypeSchema,
|
|
30
|
+
flowAssignmentExternalIdIdentityTypeSchema,
|
|
31
|
+
]),
|
|
32
|
+
value: z.string().check(z.trim(), z.minLength(1)),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const flowAssignmentStatusSchema = z.enum([
|
|
36
|
+
"pending",
|
|
37
|
+
"held",
|
|
38
|
+
"presented",
|
|
39
|
+
"completed",
|
|
40
|
+
"expired",
|
|
41
|
+
"superseded",
|
|
42
|
+
"withdrawn_pending",
|
|
43
|
+
"withdrawn_presented",
|
|
44
|
+
"withdrawn_final",
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export const flowAssignmentTargetingContextSchema = z.object({
|
|
48
|
+
flags: z.optional(z.array(appEventFlagSchema)),
|
|
49
|
+
capabilities: z.optional(z.array(appEventCapabilitySchema)),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const flowAssignmentRecipientRequestSchema = z.object({
|
|
53
|
+
identities: z._default(z.array(flowAssignmentIdentitySchema), []),
|
|
54
|
+
context: z.optional(flowAssignmentTargetingContextSchema),
|
|
55
|
+
at: z.optional(z.number()),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const flowAssignmentRecordSchema = z.object({
|
|
59
|
+
flowAssignmentId: z.string().check(z.trim(), z.minLength(1)),
|
|
60
|
+
flowId: z.string().check(z.trim(), z.minLength(1)),
|
|
61
|
+
holdGroupToken: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
62
|
+
presentedFlowVersionId: z.optional(
|
|
63
|
+
z.string().check(z.trim(), z.minLength(1)),
|
|
64
|
+
),
|
|
65
|
+
latestFlowVersionId: z.string().check(z.trim(), z.minLength(1)),
|
|
66
|
+
status: flowAssignmentStatusSchema,
|
|
67
|
+
assignedAt: z.string(),
|
|
68
|
+
expiresAt: z.optional(z.string()),
|
|
69
|
+
heldAt: z.optional(z.string()),
|
|
70
|
+
holdExpiresAt: z.optional(z.string()),
|
|
71
|
+
presentedAt: z.optional(z.string()),
|
|
72
|
+
completedAt: z.optional(z.string()),
|
|
73
|
+
expiredAt: z.optional(z.string()),
|
|
74
|
+
supersededAt: z.optional(z.string()),
|
|
75
|
+
withdrawnAt: z.optional(z.string()),
|
|
76
|
+
withdrawnFinalAt: z.optional(z.string()),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const listPendingFlowAssignmentsResponseSchema = z.object({
|
|
80
|
+
fetchedAt: z.string(),
|
|
81
|
+
flowAssignments: z.array(flowAssignmentRecordSchema),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const claimPendingFlowAssignmentsRequestSchema = z.object({
|
|
85
|
+
identities: z._default(z.array(flowAssignmentIdentitySchema), []),
|
|
86
|
+
context: z.optional(flowAssignmentTargetingContextSchema),
|
|
87
|
+
holdOwnerKey: z.string().check(z.trim(), z.minLength(1)),
|
|
88
|
+
at: z.optional(z.number()),
|
|
89
|
+
limit: z.optional(z.number()),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export const claimPendingFlowAssignmentsResponseSchema = z.object({
|
|
93
|
+
fetchedAt: z.string(),
|
|
94
|
+
flowAssignments: z.array(flowAssignmentRecordSchema),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const updateFlowAssignmentLifecycleRequestSchema = z.object({
|
|
98
|
+
identities: z._default(z.array(flowAssignmentIdentitySchema), []),
|
|
99
|
+
context: z.optional(flowAssignmentTargetingContextSchema),
|
|
100
|
+
flowAssignmentId: z.string().check(z.trim(), z.minLength(1)),
|
|
101
|
+
flowVersionId: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
102
|
+
holdGroupToken: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
103
|
+
at: z.optional(z.number()),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export const releaseFlowAssignmentClaimRequestSchema = z.object({
|
|
107
|
+
identities: z._default(z.array(flowAssignmentIdentitySchema), []),
|
|
108
|
+
holdGroupToken: z.string().check(z.trim(), z.minLength(1)),
|
|
109
|
+
holdOwnerKey: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
110
|
+
at: z.optional(z.number()),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export const releaseFlowAssignmentClaimResponseSchema = z.object({
|
|
114
|
+
releasedCount: z.number(),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export const updateFlowAssignmentLifecycleResponseSchema =
|
|
118
|
+
flowAssignmentRecordSchema;
|
|
119
|
+
export const nullableUpdateFlowAssignmentLifecycleResponseSchema = z.nullable(
|
|
120
|
+
updateFlowAssignmentLifecycleResponseSchema,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
export type FlowAssignmentStatus = z.output<typeof flowAssignmentStatusSchema>;
|
|
124
|
+
export type FlowAssignmentRecipientRequest = z.output<
|
|
125
|
+
typeof flowAssignmentRecipientRequestSchema
|
|
126
|
+
>;
|
|
127
|
+
export type FlowAssignmentTargetingContext = z.output<
|
|
128
|
+
typeof flowAssignmentTargetingContextSchema
|
|
129
|
+
>;
|
|
130
|
+
export type FlowAssignmentRecord = z.output<typeof flowAssignmentRecordSchema>;
|
|
131
|
+
export type ClaimPendingFlowAssignmentsRequest = z.output<
|
|
132
|
+
typeof claimPendingFlowAssignmentsRequestSchema
|
|
133
|
+
>;
|
|
134
|
+
export type ClaimPendingFlowAssignmentsResponse = z.output<
|
|
135
|
+
typeof claimPendingFlowAssignmentsResponseSchema
|
|
136
|
+
>;
|
|
137
|
+
export type ListPendingFlowAssignmentsResponse = z.output<
|
|
138
|
+
typeof listPendingFlowAssignmentsResponseSchema
|
|
139
|
+
>;
|
|
140
|
+
export type ReleaseFlowAssignmentClaimRequest = z.output<
|
|
141
|
+
typeof releaseFlowAssignmentClaimRequestSchema
|
|
142
|
+
>;
|
|
143
|
+
export type ReleaseFlowAssignmentClaimResponse = z.output<
|
|
144
|
+
typeof releaseFlowAssignmentClaimResponseSchema
|
|
145
|
+
>;
|
|
146
|
+
export type UpdateFlowAssignmentLifecycleRequest = z.output<
|
|
147
|
+
typeof updateFlowAssignmentLifecycleRequestSchema
|
|
148
|
+
>;
|
|
149
|
+
export type UpdateFlowAssignmentLifecycleResponse = z.output<
|
|
150
|
+
typeof updateFlowAssignmentLifecycleResponseSchema
|
|
151
|
+
>;
|