@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.
Files changed (59) hide show
  1. package/dist/app-event.d.ts +779 -10
  2. package/dist/app-event.d.ts.map +1 -1
  3. package/dist/app-event.js +81 -8
  4. package/dist/flow-assignments.d.ts +7 -4
  5. package/dist/flow-assignments.d.ts.map +1 -1
  6. package/dist/flow-assignments.js +15 -5
  7. package/dist/host/constants.d.ts +2 -2
  8. package/dist/host/constants.d.ts.map +1 -1
  9. package/dist/host/constants.js +3 -1
  10. package/dist/host/external-id-argument-guards.d.ts +10 -0
  11. package/dist/host/external-id-argument-guards.d.ts.map +1 -0
  12. package/dist/host/external-id-argument-guards.js +27 -0
  13. package/dist/host/index.d.ts +1 -0
  14. package/dist/host/index.d.ts.map +1 -1
  15. package/dist/host/index.js +1 -0
  16. package/dist/host/sdk-types.d.ts +92 -27
  17. package/dist/host/sdk-types.d.ts.map +1 -1
  18. package/dist/host/sdk.d.ts.map +1 -1
  19. package/dist/identity-type.d.ts +2 -1
  20. package/dist/identity-type.d.ts.map +1 -1
  21. package/dist/identity-type.js +1 -1
  22. package/dist/index.d.ts +3 -87
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -6
  25. package/dist/widget-commands.d.ts +124 -3
  26. package/dist/widget-commands.d.ts.map +1 -1
  27. package/dist/widget-commands.js +10 -0
  28. package/dist/widget-config.d.ts +23 -5
  29. package/dist/widget-config.d.ts.map +1 -1
  30. package/dist/widget-config.js +2 -1
  31. package/package.json +22 -2
  32. package/src/app-event.ts +235 -0
  33. package/src/client-meta.ts +25 -0
  34. package/src/defaults.ts +4 -0
  35. package/src/errors.ts +58 -0
  36. package/src/flow-assignments.test.ts +82 -0
  37. package/src/flow-assignments.ts +151 -0
  38. package/src/host/command-dispatch.ts +59 -0
  39. package/src/host/command-envelope.ts +41 -0
  40. package/src/host/command-settlement.ts +121 -0
  41. package/src/host/constants.ts +103 -0
  42. package/src/host/external-id-argument-guards.ts +57 -0
  43. package/src/host/host-event-contract.ts +277 -0
  44. package/src/host/index.ts +14 -0
  45. package/src/host/lazy-handle-client.ts +207 -0
  46. package/src/host/request-id.ts +3 -0
  47. package/src/host/sdk-types.ts +528 -0
  48. package/src/host/sdk.ts +43 -0
  49. package/src/host/unique-id.ts +17 -0
  50. package/src/identity-type.ts +96 -0
  51. package/src/index.ts +139 -0
  52. package/src/protocol-root.test.ts +582 -0
  53. package/src/public-grant-scope.ts +25 -0
  54. package/src/runtime-endpoints.ts +69 -0
  55. package/src/scopes.ts +79 -0
  56. package/src/trpc-envelope.ts +9 -0
  57. package/src/version-resolution.ts +18 -0
  58. package/src/widget-commands.ts +162 -0
  59. 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.6.1",
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",
@@ -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>;
@@ -0,0 +1,4 @@
1
+ export const DEFAULT_AUTO_DETECT_COLOR_SCHEME_ATTRS: readonly [
2
+ "class",
3
+ "data-theme",
4
+ ] = ["class", "data-theme"];
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
+ >;