@offerkit/sdk 0.2.4 → 0.2.5

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/index.cjs ADDED
@@ -0,0 +1,1898 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_crypto = require("node:crypto");
3
+ let _orpc_client = require("@orpc/client");
4
+ let _orpc_openapi_client_fetch = require("@orpc/openapi-client/fetch");
5
+ let _orpc_contract = require("@orpc/contract");
6
+ let zod = require("zod");
7
+ //#region ../contract/src/schemas/api-key.ts
8
+ const apiKeyOutput = zod.z.object({
9
+ id: zod.z.string(),
10
+ name: zod.z.string(),
11
+ prefix: zod.z.string(),
12
+ scopes: zod.z.array(zod.z.string()),
13
+ rateLimitRps: zod.z.number().int(),
14
+ lastUsedAt: zod.z.string().datetime().nullable(),
15
+ disabledAt: zod.z.string().datetime().nullable(),
16
+ createdAt: zod.z.string().datetime()
17
+ });
18
+ const apiKeyCreateInput = zod.z.object({
19
+ name: zod.z.string().min(1).max(100),
20
+ scopes: zod.z.array(zod.z.string()).optional(),
21
+ rateLimitRps: zod.z.number().int().min(1).max(1e4).optional()
22
+ });
23
+ const apiKeyCreateOutput = apiKeyOutput.extend({
24
+ /** The plaintext token. Shown once at creation; never returned again. */
25
+ token: zod.z.string() });
26
+ //#endregion
27
+ //#region ../contract/src/routes/api-keys.ts
28
+ const apiKeys = {
29
+ list: _orpc_contract.oc.route({
30
+ method: "GET",
31
+ path: "/api-keys",
32
+ summary: "List API keys"
33
+ }).output(zod.z.object({ data: zod.z.array(apiKeyOutput) })),
34
+ create: _orpc_contract.oc.route({
35
+ method: "POST",
36
+ path: "/api-keys",
37
+ summary: "Mint a new API key"
38
+ }).input(apiKeyCreateInput).output(apiKeyCreateOutput),
39
+ revoke: _orpc_contract.oc.route({
40
+ method: "DELETE",
41
+ path: "/api-keys/{id}",
42
+ summary: "Disable an API key (cannot be re-enabled)",
43
+ inputStructure: "detailed"
44
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
45
+ };
46
+ //#endregion
47
+ //#region ../contract/src/mcp.ts
48
+ /**
49
+ * Returns a `meta` object that attaches MCP exposure to a procedure.
50
+ * Wrapping in a helper keeps the call sites typed and uniform.
51
+ */
52
+ function mcpMeta(meta) {
53
+ return { mcp: meta };
54
+ }
55
+ //#endregion
56
+ //#region ../contract/src/schemas/campaign.ts
57
+ const campaignType = zod.z.enum([
58
+ "DISCOUNT",
59
+ "GIFT_VOUCHERS",
60
+ "LOYALTY_PROGRAM",
61
+ "REFERRAL_PROGRAM",
62
+ "PROMOTION"
63
+ ]);
64
+ const campaignStatus = zod.z.enum([
65
+ "draft",
66
+ "active",
67
+ "paused",
68
+ "ended"
69
+ ]);
70
+ const codeConfig = zod.z.object({
71
+ length: zod.z.number().int().min(4).max(32).optional(),
72
+ prefix: zod.z.string().max(20).optional(),
73
+ suffix: zod.z.string().max(20).optional(),
74
+ charset: zod.z.enum([
75
+ "alphanumeric",
76
+ "uppercase",
77
+ "lowercase",
78
+ "numeric"
79
+ ]).optional(),
80
+ excludeConfusable: zod.z.boolean().optional()
81
+ });
82
+ const campaignOutput = zod.z.object({
83
+ id: zod.z.string().uuid(),
84
+ name: zod.z.string(),
85
+ description: zod.z.string().nullable(),
86
+ type: campaignType,
87
+ status: campaignStatus,
88
+ currency: zod.z.string().length(3),
89
+ timezone: zod.z.string(),
90
+ startDate: zod.z.string().datetime().nullable(),
91
+ endDate: zod.z.string().datetime().nullable(),
92
+ codeConfig,
93
+ validationRuleId: zod.z.string().uuid().nullable(),
94
+ autoApply: zod.z.boolean(),
95
+ voucherCount: zod.z.number().int(),
96
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()),
97
+ createdAt: zod.z.string().datetime(),
98
+ updatedAt: zod.z.string().datetime()
99
+ });
100
+ const campaignCreateInput = zod.z.object({
101
+ name: zod.z.string().min(1).max(100),
102
+ description: zod.z.string().max(500).optional(),
103
+ type: campaignType,
104
+ currency: zod.z.string().length(3),
105
+ timezone: zod.z.string().optional(),
106
+ startDate: zod.z.string().datetime().optional(),
107
+ endDate: zod.z.string().datetime().optional(),
108
+ codeConfig: codeConfig.optional(),
109
+ validationRuleId: zod.z.string().uuid().nullable().optional(),
110
+ autoApply: zod.z.boolean().optional(),
111
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
112
+ });
113
+ const campaignUpdateInput = campaignCreateInput.omit({ type: true }).partial().extend({ status: campaignStatus.optional() });
114
+ //#endregion
115
+ //#region ../contract/src/schemas/pagination.ts
116
+ const limit = zod.z.union([zod.z.number(), zod.z.string().regex(/^\d+$/)]).transform((v) => typeof v === "string" ? Number.parseInt(v, 10) : v).pipe(zod.z.number().int().min(1).max(100)).default(20);
117
+ const paginationInput = zod.z.object({
118
+ cursor: zod.z.string().optional(),
119
+ limit
120
+ });
121
+ function paginatedOutput(item) {
122
+ return zod.z.object({
123
+ data: zod.z.array(item),
124
+ next: zod.z.string().optional(),
125
+ prev: zod.z.string().optional()
126
+ });
127
+ }
128
+ //#endregion
129
+ //#region ../contract/src/routes/campaigns.ts
130
+ const campaigns = {
131
+ list: _orpc_contract.oc.meta(mcpMeta({
132
+ expose: true,
133
+ riskLevel: "safe"
134
+ })).route({
135
+ method: "GET",
136
+ path: "/campaigns",
137
+ summary: "List campaigns"
138
+ }).input(paginationInput.extend({ search: zod.z.string().optional() })).output(paginatedOutput(campaignOutput)),
139
+ get: _orpc_contract.oc.meta(mcpMeta({
140
+ expose: true,
141
+ riskLevel: "safe"
142
+ })).route({
143
+ method: "GET",
144
+ path: "/campaigns/{id}",
145
+ summary: "Get campaign",
146
+ inputStructure: "detailed"
147
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(campaignOutput),
148
+ create: _orpc_contract.oc.route({
149
+ method: "POST",
150
+ path: "/campaigns",
151
+ summary: "Create campaign"
152
+ }).input(campaignCreateInput).output(campaignOutput),
153
+ update: _orpc_contract.oc.route({
154
+ method: "PATCH",
155
+ path: "/campaigns/{id}",
156
+ summary: "Update campaign",
157
+ inputStructure: "detailed"
158
+ }).input(zod.z.object({
159
+ params: zod.z.object({ id: zod.z.string().uuid() }),
160
+ body: zod.z.object({ patch: campaignUpdateInput })
161
+ })).output(campaignOutput),
162
+ delete: _orpc_contract.oc.route({
163
+ method: "DELETE",
164
+ path: "/campaigns/{id}",
165
+ summary: "Soft-delete campaign",
166
+ inputStructure: "detailed"
167
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
168
+ };
169
+ //#endregion
170
+ //#region ../contract/src/schemas/customer.ts
171
+ const customerAddress = zod.z.object({
172
+ line1: zod.z.string().optional(),
173
+ line2: zod.z.string().optional(),
174
+ city: zod.z.string().optional(),
175
+ state: zod.z.string().optional(),
176
+ postalCode: zod.z.string().optional(),
177
+ country: zod.z.string().length(2).optional()
178
+ });
179
+ const customerSummary = zod.z.object({
180
+ totalSpent: zod.z.number().int().nonnegative().optional(),
181
+ redemptionCount: zod.z.number().int().nonnegative().optional(),
182
+ lastRedeemedAt: zod.z.string().datetime().optional()
183
+ });
184
+ const customerOutput = zod.z.object({
185
+ id: zod.z.string().uuid(),
186
+ email: zod.z.string().email().nullable(),
187
+ name: zod.z.string().nullable(),
188
+ phone: zod.z.string().nullable(),
189
+ externalId: zod.z.string().nullable(),
190
+ address: customerAddress.nullable(),
191
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()),
192
+ summary: customerSummary,
193
+ createdAt: zod.z.string().datetime(),
194
+ updatedAt: zod.z.string().datetime()
195
+ });
196
+ const customerCreateInput = zod.z.object({
197
+ email: zod.z.string().email().optional(),
198
+ name: zod.z.string().optional(),
199
+ phone: zod.z.string().optional(),
200
+ externalId: zod.z.string().min(1).max(256).optional(),
201
+ address: customerAddress.optional(),
202
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
203
+ });
204
+ const customerUpdateInput = customerCreateInput.partial();
205
+ const customerUpsertInput = zod.z.object({
206
+ externalId: zod.z.string().min(1).max(256),
207
+ email: zod.z.string().email().optional(),
208
+ name: zod.z.string().optional(),
209
+ phone: zod.z.string().optional(),
210
+ address: customerAddress.optional(),
211
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
212
+ });
213
+ const customerUpsertOutput = zod.z.object({
214
+ customer: customerOutput,
215
+ created: zod.z.boolean()
216
+ });
217
+ //#endregion
218
+ //#region ../contract/src/routes/customers.ts
219
+ const customers = {
220
+ list: _orpc_contract.oc.meta(mcpMeta({
221
+ expose: true,
222
+ riskLevel: "safe"
223
+ })).route({
224
+ method: "GET",
225
+ path: "/customers",
226
+ summary: "List customers"
227
+ }).input(paginationInput.extend({ search: zod.z.string().optional() })).output(paginatedOutput(customerOutput)),
228
+ get: _orpc_contract.oc.meta(mcpMeta({
229
+ expose: true,
230
+ riskLevel: "safe"
231
+ })).route({
232
+ method: "GET",
233
+ path: "/customers/{id}",
234
+ summary: "Get customer",
235
+ inputStructure: "detailed"
236
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(customerOutput),
237
+ getByExternalId: _orpc_contract.oc.meta(mcpMeta({
238
+ expose: true,
239
+ riskLevel: "safe"
240
+ })).route({
241
+ method: "GET",
242
+ path: "/customers/by-external-id/{externalId}",
243
+ summary: "Get a customer by integrator-supplied externalId",
244
+ inputStructure: "detailed"
245
+ }).input(zod.z.object({ params: zod.z.object({ externalId: zod.z.string().min(1).max(256) }) })).output(customerOutput),
246
+ create: _orpc_contract.oc.route({
247
+ method: "POST",
248
+ path: "/customers",
249
+ summary: "Create customer"
250
+ }).input(customerCreateInput).output(customerOutput),
251
+ upsert: _orpc_contract.oc.route({
252
+ method: "PUT",
253
+ path: "/customers/by-external-id",
254
+ summary: "Create or update a customer keyed by externalId (idempotent)"
255
+ }).input(customerUpsertInput).output(customerUpsertOutput),
256
+ update: _orpc_contract.oc.route({
257
+ method: "PATCH",
258
+ path: "/customers/{id}",
259
+ summary: "Update customer",
260
+ inputStructure: "detailed"
261
+ }).input(zod.z.object({
262
+ params: zod.z.object({ id: zod.z.string().uuid() }),
263
+ body: zod.z.object({ patch: customerUpdateInput })
264
+ })).output(customerOutput),
265
+ delete: _orpc_contract.oc.route({
266
+ method: "DELETE",
267
+ path: "/customers/{id}",
268
+ summary: "Soft-delete customer",
269
+ inputStructure: "detailed"
270
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
271
+ };
272
+ //#endregion
273
+ //#region ../contract/src/schemas/loyalty.ts
274
+ const loyaltyProgramOutput = zod.z.object({
275
+ id: zod.z.string().uuid(),
276
+ campaignId: zod.z.string().uuid(),
277
+ pointsExpiryDays: zod.z.number().int().nullable(),
278
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()),
279
+ createdAt: zod.z.string().datetime(),
280
+ updatedAt: zod.z.string().datetime()
281
+ });
282
+ const loyaltyProgramCreateInput = zod.z.object({
283
+ campaignId: zod.z.string().uuid(),
284
+ pointsExpiryDays: zod.z.number().int().min(1).optional(),
285
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
286
+ });
287
+ const loyaltyProgramUpdateInput = zod.z.object({
288
+ pointsExpiryDays: zod.z.number().int().min(1).nullable().optional(),
289
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
290
+ }).partial();
291
+ const loyaltyTierOutput = zod.z.object({
292
+ id: zod.z.string().uuid(),
293
+ programId: zod.z.string().uuid(),
294
+ name: zod.z.string(),
295
+ threshold: zod.z.number().int(),
296
+ earnMultiplier: zod.z.number().int(),
297
+ sortOrder: zod.z.number().int()
298
+ });
299
+ const loyaltyTierCreateInput = zod.z.object({
300
+ programId: zod.z.string().uuid(),
301
+ name: zod.z.string().min(1).max(100),
302
+ threshold: zod.z.number().int().min(0),
303
+ earnMultiplier: zod.z.number().int().min(0).default(1e4),
304
+ sortOrder: zod.z.number().int().default(0)
305
+ });
306
+ const loyaltyTierUpdateInput = loyaltyTierCreateInput.omit({ programId: true }).partial();
307
+ const loyaltyEarningRuleFormula = zod.z.object({
308
+ kind: zod.z.enum([
309
+ "fixed",
310
+ "per_cents",
311
+ "custom"
312
+ ]),
313
+ value: zod.z.number().int().min(0).optional(),
314
+ divisor: zod.z.number().int().min(1).optional()
315
+ });
316
+ const loyaltyEarningRuleOutput = zod.z.object({
317
+ id: zod.z.string().uuid(),
318
+ programId: zod.z.string().uuid(),
319
+ name: zod.z.string(),
320
+ event: zod.z.string(),
321
+ validationRuleId: zod.z.string().uuid().nullable(),
322
+ formula: loyaltyEarningRuleFormula,
323
+ active: zod.z.enum(["yes", "no"])
324
+ });
325
+ const loyaltyEarningRuleCreateInput = zod.z.object({
326
+ programId: zod.z.string().uuid(),
327
+ name: zod.z.string().min(1).max(100),
328
+ event: zod.z.string().min(1).max(100),
329
+ validationRuleId: zod.z.string().uuid().optional(),
330
+ formula: loyaltyEarningRuleFormula,
331
+ active: zod.z.enum(["yes", "no"]).optional()
332
+ });
333
+ const loyaltyEarningRuleUpdateInput = loyaltyEarningRuleCreateInput.omit({ programId: true }).partial();
334
+ const loyaltyRewardPayload = zod.z.object({
335
+ kind: zod.z.enum([
336
+ "discount",
337
+ "gift_card",
338
+ "custom"
339
+ ]),
340
+ discount: zod.z.object({
341
+ type: zod.z.enum(["AMOUNT", "PERCENTAGE"]),
342
+ amount: zod.z.number().int().min(0).optional(),
343
+ percent: zod.z.number().int().min(0).max(1e4).optional(),
344
+ maxDiscountAmount: zod.z.number().int().min(0).optional()
345
+ }).optional(),
346
+ creditCents: zod.z.number().int().min(0).optional(),
347
+ typeKey: zod.z.string().optional(),
348
+ payload: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
349
+ });
350
+ const loyaltyRewardOutput = zod.z.object({
351
+ id: zod.z.string().uuid(),
352
+ programId: zod.z.string().uuid(),
353
+ name: zod.z.string(),
354
+ description: zod.z.string().nullable(),
355
+ cost: zod.z.number().int(),
356
+ payload: loyaltyRewardPayload
357
+ });
358
+ const loyaltyRewardCreateInput = zod.z.object({
359
+ programId: zod.z.string().uuid(),
360
+ name: zod.z.string().min(1).max(100),
361
+ description: zod.z.string().max(500).optional(),
362
+ cost: zod.z.number().int().min(1),
363
+ payload: loyaltyRewardPayload
364
+ });
365
+ const loyaltyRewardUpdateInput = loyaltyRewardCreateInput.omit({ programId: true }).partial();
366
+ const loyaltyMemberOutput = zod.z.object({
367
+ id: zod.z.string().uuid(),
368
+ customerId: zod.z.string().uuid(),
369
+ programId: zod.z.string().uuid(),
370
+ balance: zod.z.number().int(),
371
+ lifetimePoints: zod.z.number().int(),
372
+ currentTierId: zod.z.string().uuid().nullable(),
373
+ enrolledAt: zod.z.string().datetime()
374
+ });
375
+ const loyaltyMemberEnrollInput = zod.z.object({
376
+ programId: zod.z.string().uuid(),
377
+ customerId: zod.z.string().uuid()
378
+ });
379
+ const loyaltyEarnInput = zod.z.object({
380
+ memberId: zod.z.string().uuid(),
381
+ basePoints: zod.z.number().int().min(1),
382
+ earningRuleId: zod.z.string().uuid().optional(),
383
+ eventId: zod.z.string().optional(),
384
+ note: zod.z.string().max(500).optional(),
385
+ expiresAt: zod.z.string().datetime().optional(),
386
+ applyMultiplier: zod.z.boolean().optional()
387
+ });
388
+ const loyaltyAdjustInput = zod.z.object({
389
+ memberId: zod.z.string().uuid(),
390
+ delta: zod.z.number().int(),
391
+ note: zod.z.string().max(500).optional()
392
+ });
393
+ const loyaltyRedeemInput = zod.z.object({
394
+ memberId: zod.z.string().uuid(),
395
+ rewardId: zod.z.string().uuid(),
396
+ note: zod.z.string().max(500).optional()
397
+ });
398
+ const loyaltyTransactionOutput = zod.z.object({
399
+ id: zod.z.string().uuid(),
400
+ memberId: zod.z.string().uuid(),
401
+ delta: zod.z.number().int(),
402
+ balanceAfter: zod.z.number().int(),
403
+ reason: zod.z.enum([
404
+ "EARN",
405
+ "REDEEM",
406
+ "ADJUSTMENT",
407
+ "EXPIRY",
408
+ "ROLLBACK"
409
+ ]),
410
+ rewardId: zod.z.string().uuid().nullable(),
411
+ earningRuleId: zod.z.string().uuid().nullable(),
412
+ eventId: zod.z.string().nullable(),
413
+ note: zod.z.string().nullable(),
414
+ expiresAt: zod.z.string().datetime().nullable(),
415
+ expiredAt: zod.z.string().datetime().nullable(),
416
+ createdAt: zod.z.string().datetime()
417
+ });
418
+ const loyalty = {
419
+ programs: {
420
+ list: _orpc_contract.oc.route({
421
+ method: "GET",
422
+ path: "/loyalty/programs",
423
+ summary: "List loyalty programs"
424
+ }).input(paginationInput).output(paginatedOutput(loyaltyProgramOutput)),
425
+ get: _orpc_contract.oc.route({
426
+ method: "GET",
427
+ path: "/loyalty/programs/{id}",
428
+ summary: "Get loyalty program",
429
+ inputStructure: "detailed"
430
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(loyaltyProgramOutput),
431
+ create: _orpc_contract.oc.route({
432
+ method: "POST",
433
+ path: "/loyalty/programs",
434
+ summary: "Create loyalty program"
435
+ }).input(loyaltyProgramCreateInput).output(loyaltyProgramOutput),
436
+ update: _orpc_contract.oc.route({
437
+ method: "PATCH",
438
+ path: "/loyalty/programs/{id}",
439
+ summary: "Update loyalty program",
440
+ inputStructure: "detailed"
441
+ }).input(zod.z.object({
442
+ params: zod.z.object({ id: zod.z.string().uuid() }),
443
+ body: zod.z.object({ patch: loyaltyProgramUpdateInput })
444
+ })).output(loyaltyProgramOutput),
445
+ delete: _orpc_contract.oc.route({
446
+ method: "DELETE",
447
+ path: "/loyalty/programs/{id}",
448
+ summary: "Soft-delete program",
449
+ inputStructure: "detailed"
450
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
451
+ },
452
+ tiers: {
453
+ list: _orpc_contract.oc.route({
454
+ method: "GET",
455
+ path: "/loyalty/programs/{programId}/tiers",
456
+ summary: "List tiers",
457
+ inputStructure: "detailed"
458
+ }).input(zod.z.object({ params: zod.z.object({ programId: zod.z.string().uuid() }) })).output(zod.z.object({ data: zod.z.array(loyaltyTierOutput) })),
459
+ create: _orpc_contract.oc.route({
460
+ method: "POST",
461
+ path: "/loyalty/tiers",
462
+ summary: "Create tier"
463
+ }).input(loyaltyTierCreateInput).output(loyaltyTierOutput),
464
+ update: _orpc_contract.oc.route({
465
+ method: "PATCH",
466
+ path: "/loyalty/tiers/{id}",
467
+ summary: "Update tier",
468
+ inputStructure: "detailed"
469
+ }).input(zod.z.object({
470
+ params: zod.z.object({ id: zod.z.string().uuid() }),
471
+ body: zod.z.object({ patch: loyaltyTierUpdateInput })
472
+ })).output(loyaltyTierOutput),
473
+ delete: _orpc_contract.oc.route({
474
+ method: "DELETE",
475
+ path: "/loyalty/tiers/{id}",
476
+ summary: "Delete tier",
477
+ inputStructure: "detailed"
478
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
479
+ },
480
+ earningRules: {
481
+ list: _orpc_contract.oc.route({
482
+ method: "GET",
483
+ path: "/loyalty/programs/{programId}/earning-rules",
484
+ summary: "List earning rules",
485
+ inputStructure: "detailed"
486
+ }).input(zod.z.object({ params: zod.z.object({ programId: zod.z.string().uuid() }) })).output(zod.z.object({ data: zod.z.array(loyaltyEarningRuleOutput) })),
487
+ create: _orpc_contract.oc.route({
488
+ method: "POST",
489
+ path: "/loyalty/earning-rules",
490
+ summary: "Create earning rule"
491
+ }).input(loyaltyEarningRuleCreateInput).output(loyaltyEarningRuleOutput),
492
+ update: _orpc_contract.oc.route({
493
+ method: "PATCH",
494
+ path: "/loyalty/earning-rules/{id}",
495
+ summary: "Update earning rule",
496
+ inputStructure: "detailed"
497
+ }).input(zod.z.object({
498
+ params: zod.z.object({ id: zod.z.string().uuid() }),
499
+ body: zod.z.object({ patch: loyaltyEarningRuleUpdateInput })
500
+ })).output(loyaltyEarningRuleOutput),
501
+ delete: _orpc_contract.oc.route({
502
+ method: "DELETE",
503
+ path: "/loyalty/earning-rules/{id}",
504
+ summary: "Delete earning rule",
505
+ inputStructure: "detailed"
506
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
507
+ },
508
+ rewards: {
509
+ list: _orpc_contract.oc.route({
510
+ method: "GET",
511
+ path: "/loyalty/programs/{programId}/rewards",
512
+ summary: "List rewards",
513
+ inputStructure: "detailed"
514
+ }).input(zod.z.object({ params: zod.z.object({ programId: zod.z.string().uuid() }) })).output(zod.z.object({ data: zod.z.array(loyaltyRewardOutput) })),
515
+ create: _orpc_contract.oc.route({
516
+ method: "POST",
517
+ path: "/loyalty/rewards",
518
+ summary: "Create reward"
519
+ }).input(loyaltyRewardCreateInput).output(loyaltyRewardOutput),
520
+ update: _orpc_contract.oc.route({
521
+ method: "PATCH",
522
+ path: "/loyalty/rewards/{id}",
523
+ summary: "Update reward",
524
+ inputStructure: "detailed"
525
+ }).input(zod.z.object({
526
+ params: zod.z.object({ id: zod.z.string().uuid() }),
527
+ body: zod.z.object({ patch: loyaltyRewardUpdateInput })
528
+ })).output(loyaltyRewardOutput),
529
+ delete: _orpc_contract.oc.route({
530
+ method: "DELETE",
531
+ path: "/loyalty/rewards/{id}",
532
+ summary: "Soft-delete reward",
533
+ inputStructure: "detailed"
534
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
535
+ },
536
+ members: {
537
+ list: _orpc_contract.oc.route({
538
+ method: "GET",
539
+ path: "/loyalty/programs/{programId}/members",
540
+ summary: "List members of a program",
541
+ inputStructure: "detailed"
542
+ }).input(zod.z.object({
543
+ params: zod.z.object({ programId: zod.z.string().uuid() }),
544
+ query: paginationInput
545
+ })).output(paginatedOutput(loyaltyMemberOutput)),
546
+ get: _orpc_contract.oc.route({
547
+ method: "GET",
548
+ path: "/loyalty/members/{id}",
549
+ summary: "Get member",
550
+ inputStructure: "detailed"
551
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(loyaltyMemberOutput),
552
+ enroll: _orpc_contract.oc.route({
553
+ method: "POST",
554
+ path: "/loyalty/members",
555
+ summary: "Enroll a customer"
556
+ }).input(loyaltyMemberEnrollInput).output(loyaltyMemberOutput),
557
+ earn: _orpc_contract.oc.route({
558
+ method: "POST",
559
+ path: "/loyalty/members/earn",
560
+ summary: "Earn points"
561
+ }).input(loyaltyEarnInput).output(zod.z.object({
562
+ ok: zod.z.boolean(),
563
+ transactionId: zod.z.string().uuid().optional(),
564
+ delta: zod.z.number().int().optional(),
565
+ balance: zod.z.number().int().optional(),
566
+ lifetimePoints: zod.z.number().int().optional(),
567
+ tierId: zod.z.string().uuid().nullable().optional(),
568
+ code: zod.z.string().optional(),
569
+ message: zod.z.string().optional()
570
+ })),
571
+ adjust: _orpc_contract.oc.route({
572
+ method: "POST",
573
+ path: "/loyalty/members/adjust",
574
+ summary: "Manual adjustment"
575
+ }).input(loyaltyAdjustInput).output(zod.z.object({
576
+ ok: zod.z.boolean(),
577
+ transactionId: zod.z.string().uuid().optional(),
578
+ balance: zod.z.number().int().optional(),
579
+ code: zod.z.string().optional(),
580
+ message: zod.z.string().optional()
581
+ })),
582
+ redeem: _orpc_contract.oc.route({
583
+ method: "POST",
584
+ path: "/loyalty/members/redeem",
585
+ summary: "Redeem a reward"
586
+ }).input(loyaltyRedeemInput).output(zod.z.object({
587
+ ok: zod.z.boolean(),
588
+ transactionId: zod.z.string().uuid().optional(),
589
+ rewardId: zod.z.string().uuid().optional(),
590
+ cost: zod.z.number().int().optional(),
591
+ balance: zod.z.number().int().optional(),
592
+ payload: loyaltyRewardPayload.optional(),
593
+ code: zod.z.string().optional(),
594
+ message: zod.z.string().optional()
595
+ })),
596
+ history: _orpc_contract.oc.meta(mcpMeta({
597
+ expose: true,
598
+ riskLevel: "safe",
599
+ description: "List a loyalty member's transaction history (earn / redeem / adjust / expiry)."
600
+ })).route({
601
+ method: "GET",
602
+ path: "/loyalty/members/{id}/transactions",
603
+ summary: "Member transaction ledger",
604
+ inputStructure: "detailed"
605
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ data: zod.z.array(loyaltyTransactionOutput) }))
606
+ }
607
+ };
608
+ //#endregion
609
+ //#region ../contract/src/schemas/referral.ts
610
+ const referralReward = zod.z.object({
611
+ kind: zod.z.enum([
612
+ "discount",
613
+ "gift_card",
614
+ "loyalty_points",
615
+ "custom"
616
+ ]),
617
+ discount: zod.z.object({
618
+ type: zod.z.enum(["AMOUNT", "PERCENTAGE"]),
619
+ amount: zod.z.number().int().min(0).optional(),
620
+ percent: zod.z.number().int().min(0).max(1e4).optional(),
621
+ maxDiscountAmount: zod.z.number().int().min(0).optional()
622
+ }).optional(),
623
+ creditCents: zod.z.number().int().min(0).optional(),
624
+ loyaltyProgramId: zod.z.string().uuid().optional(),
625
+ loyaltyPoints: zod.z.number().int().min(0).optional(),
626
+ typeKey: zod.z.string().optional(),
627
+ payload: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
628
+ });
629
+ const referralProgramOutput = zod.z.object({
630
+ id: zod.z.string().uuid(),
631
+ campaignId: zod.z.string().uuid(),
632
+ referrerReward: referralReward,
633
+ refereeReward: referralReward,
634
+ codeLength: zod.z.number().int(),
635
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()),
636
+ createdAt: zod.z.string().datetime(),
637
+ updatedAt: zod.z.string().datetime()
638
+ });
639
+ const referralProgramCreateInput = zod.z.object({
640
+ campaignId: zod.z.string().uuid(),
641
+ referrerReward: referralReward,
642
+ refereeReward: referralReward,
643
+ codeLength: zod.z.number().int().min(4).max(32).optional(),
644
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
645
+ });
646
+ const referralProgramUpdateInput = referralProgramCreateInput.omit({ campaignId: true }).partial();
647
+ const referralOutcome = zod.z.object({
648
+ kind: zod.z.enum([
649
+ "discount",
650
+ "gift_card",
651
+ "loyalty_points",
652
+ "custom"
653
+ ]),
654
+ voucherCode: zod.z.string().optional(),
655
+ loyaltyTransactionId: zod.z.string().uuid().optional(),
656
+ payload: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
657
+ });
658
+ const referralCodeOutput = zod.z.object({
659
+ id: zod.z.string().uuid(),
660
+ programId: zod.z.string().uuid(),
661
+ referrerCustomerId: zod.z.string().uuid(),
662
+ code: zod.z.string(),
663
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()),
664
+ createdAt: zod.z.string().datetime(),
665
+ updatedAt: zod.z.string().datetime()
666
+ });
667
+ const referralConversionStatus = zod.z.enum(["converted", "rejected"]);
668
+ const referralConversionOutput = zod.z.object({
669
+ id: zod.z.string().uuid(),
670
+ codeId: zod.z.string().uuid(),
671
+ refereeCustomerId: zod.z.string().uuid(),
672
+ status: referralConversionStatus,
673
+ convertedAt: zod.z.string().datetime(),
674
+ conversionEventId: zod.z.string().nullable(),
675
+ referrerOutcome: referralOutcome,
676
+ refereeOutcome: referralOutcome,
677
+ createdAt: zod.z.string().datetime(),
678
+ updatedAt: zod.z.string().datetime()
679
+ });
680
+ const referralProgramConversionOutput = referralConversionOutput.extend({
681
+ code: zod.z.string(),
682
+ referrerCustomerId: zod.z.string().uuid()
683
+ });
684
+ const referralIssueInput = zod.z.object({
685
+ programId: zod.z.string().uuid(),
686
+ referrerCustomerId: zod.z.string().uuid(),
687
+ prefix: zod.z.string().min(1).max(12).optional()
688
+ });
689
+ const referralConvertInput = zod.z.object({
690
+ code: zod.z.string().min(1),
691
+ refereeCustomerId: zod.z.string().uuid(),
692
+ conversionEventId: zod.z.string().optional()
693
+ });
694
+ const referralIssueOutput = zod.z.object({
695
+ ok: zod.z.boolean(),
696
+ codeId: zod.z.string().uuid().optional(),
697
+ code: zod.z.string().optional(),
698
+ errorCode: zod.z.string().optional(),
699
+ message: zod.z.string().optional()
700
+ });
701
+ const referralConvertOutput = zod.z.object({
702
+ ok: zod.z.boolean(),
703
+ conversionId: zod.z.string().uuid().optional(),
704
+ codeId: zod.z.string().uuid().optional(),
705
+ code: zod.z.string().optional(),
706
+ referrerCustomerId: zod.z.string().uuid().optional(),
707
+ refereeCustomerId: zod.z.string().uuid().optional(),
708
+ referrerReward: referralOutcome.optional(),
709
+ refereeReward: referralOutcome.optional(),
710
+ idempotent: zod.z.boolean().optional(),
711
+ errorCode: zod.z.string().optional(),
712
+ message: zod.z.string().optional()
713
+ });
714
+ const referrals = {
715
+ programs: {
716
+ list: _orpc_contract.oc.route({
717
+ method: "GET",
718
+ path: "/referral-programs",
719
+ summary: "List referral programs"
720
+ }).input(paginationInput).output(paginatedOutput(referralProgramOutput)),
721
+ get: _orpc_contract.oc.route({
722
+ method: "GET",
723
+ path: "/referral-programs/{id}",
724
+ summary: "Get referral program",
725
+ inputStructure: "detailed"
726
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(referralProgramOutput),
727
+ create: _orpc_contract.oc.route({
728
+ method: "POST",
729
+ path: "/referral-programs",
730
+ summary: "Create referral program"
731
+ }).input(referralProgramCreateInput).output(referralProgramOutput),
732
+ update: _orpc_contract.oc.route({
733
+ method: "PATCH",
734
+ path: "/referral-programs/{id}",
735
+ summary: "Update referral program",
736
+ inputStructure: "detailed"
737
+ }).input(zod.z.object({
738
+ params: zod.z.object({ id: zod.z.string().uuid() }),
739
+ body: zod.z.object({ patch: referralProgramUpdateInput })
740
+ })).output(referralProgramOutput),
741
+ delete: _orpc_contract.oc.route({
742
+ method: "DELETE",
743
+ path: "/referral-programs/{id}",
744
+ summary: "Soft-delete referral program",
745
+ inputStructure: "detailed"
746
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
747
+ },
748
+ listCodes: _orpc_contract.oc.route({
749
+ method: "GET",
750
+ path: "/referral-programs/{programId}/codes",
751
+ summary: "List referral codes in a program",
752
+ inputStructure: "detailed"
753
+ }).input(zod.z.object({
754
+ params: zod.z.object({ programId: zod.z.string().uuid() }),
755
+ query: paginationInput
756
+ })).output(paginatedOutput(referralCodeOutput)),
757
+ listConversions: _orpc_contract.oc.route({
758
+ method: "GET",
759
+ path: "/referral-codes/{codeId}/conversions",
760
+ summary: "List conversions for a referral code",
761
+ inputStructure: "detailed"
762
+ }).input(zod.z.object({
763
+ params: zod.z.object({ codeId: zod.z.string().uuid() }),
764
+ query: paginationInput
765
+ })).output(paginatedOutput(referralConversionOutput)),
766
+ listProgramConversions: _orpc_contract.oc.route({
767
+ method: "GET",
768
+ path: "/referral-programs/{programId}/conversions",
769
+ summary: "List conversions in a referral program",
770
+ inputStructure: "detailed"
771
+ }).input(zod.z.object({
772
+ params: zod.z.object({ programId: zod.z.string().uuid() }),
773
+ query: paginationInput
774
+ })).output(paginatedOutput(referralProgramConversionOutput)),
775
+ getByCode: _orpc_contract.oc.route({
776
+ method: "GET",
777
+ path: "/referrals/{code}",
778
+ summary: "Look up a referral code",
779
+ inputStructure: "detailed"
780
+ }).input(zod.z.object({ params: zod.z.object({ code: zod.z.string() }) })).output(referralCodeOutput),
781
+ issue: _orpc_contract.oc.route({
782
+ method: "POST",
783
+ path: "/referrals/issue",
784
+ summary: "Issue (or fetch) a referral code for a customer"
785
+ }).input(referralIssueInput).output(referralIssueOutput),
786
+ convert: _orpc_contract.oc.route({
787
+ method: "POST",
788
+ path: "/referrals/convert",
789
+ summary: "Convert a referral and issue both rewards"
790
+ }).input(referralConvertInput).output(referralConvertOutput)
791
+ };
792
+ //#endregion
793
+ //#region ../contract/src/schemas/voucher.ts
794
+ const voucherType = zod.z.enum(["DISCOUNT", "GIFT_CARD"]);
795
+ const voucherDiscount = zod.z.object({
796
+ type: zod.z.enum(["AMOUNT", "PERCENTAGE"]),
797
+ amount: zod.z.number().int().min(0).optional(),
798
+ percent: zod.z.number().int().min(0).max(1e4).optional(),
799
+ maxDiscountAmount: zod.z.number().int().min(0).optional(),
800
+ appliesTo: zod.z.object({
801
+ productIds: zod.z.array(zod.z.string()).optional(),
802
+ collectionIds: zod.z.array(zod.z.string()).optional()
803
+ }).optional()
804
+ });
805
+ const customReward = zod.z.object({
806
+ typeKey: zod.z.string().min(1),
807
+ payload: zod.z.record(zod.z.string(), zod.z.unknown())
808
+ });
809
+ const voucherOutput = zod.z.object({
810
+ id: zod.z.string().uuid(),
811
+ code: zod.z.string(),
812
+ campaignId: zod.z.string().uuid().nullable(),
813
+ type: voucherType,
814
+ discount: voucherDiscount.nullable(),
815
+ customRewards: zod.z.array(customReward),
816
+ giftBalance: zod.z.number().int().nullable(),
817
+ redemptionLimit: zod.z.number().int().nullable(),
818
+ redemptionCount: zod.z.number().int(),
819
+ priority: zod.z.number().int(),
820
+ exclusive: zod.z.boolean(),
821
+ active: zod.z.boolean(),
822
+ startDate: zod.z.string().datetime().nullable(),
823
+ endDate: zod.z.string().datetime().nullable(),
824
+ customerId: zod.z.string().uuid().nullable(),
825
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()),
826
+ createdAt: zod.z.string().datetime(),
827
+ updatedAt: zod.z.string().datetime()
828
+ });
829
+ const voucherCreateInput = zod.z.object({
830
+ code: zod.z.string().min(1).optional(),
831
+ campaignId: zod.z.string().uuid().optional(),
832
+ type: voucherType,
833
+ discount: voucherDiscount.optional(),
834
+ customRewards: zod.z.array(customReward).optional(),
835
+ giftBalance: zod.z.number().int().min(0).optional(),
836
+ redemptionLimit: zod.z.number().int().min(1).optional(),
837
+ priority: zod.z.number().int().optional(),
838
+ exclusive: zod.z.boolean().optional(),
839
+ startDate: zod.z.string().datetime().optional(),
840
+ endDate: zod.z.string().datetime().optional(),
841
+ customerId: zod.z.string().uuid().optional(),
842
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
843
+ });
844
+ const voucherUpdateInput = voucherCreateInput.omit({
845
+ type: true,
846
+ code: true
847
+ }).partial().extend({ active: zod.z.boolean().optional() });
848
+ const voucherBulkCreateInput = zod.z.object({
849
+ campaignId: zod.z.string().uuid(),
850
+ count: zod.z.number().int().min(1).max(1e5),
851
+ discount: voucherDiscount.optional(),
852
+ giftBalance: zod.z.number().int().min(1).optional()
853
+ });
854
+ //#endregion
855
+ //#region ../contract/src/schemas/redemption.ts
856
+ const orderItem$1 = zod.z.object({
857
+ productId: zod.z.string(),
858
+ collectionId: zod.z.string().optional(),
859
+ quantity: zod.z.number().int().min(1),
860
+ unitPrice: zod.z.number().int().min(0)
861
+ });
862
+ const orderInput = zod.z.object({
863
+ amount: zod.z.number().int().min(0),
864
+ currency: zod.z.string().length(3),
865
+ items: zod.z.array(orderItem$1).optional()
866
+ });
867
+ const validateInput = zod.z.object({
868
+ code: zod.z.string().min(1),
869
+ customerId: zod.z.string().uuid().optional(),
870
+ order: orderInput.optional()
871
+ });
872
+ const redeemInput = validateInput.extend({
873
+ /** uuid of an `order` row created via the orders API. */
874
+ orderId: zod.z.string().uuid().optional(),
875
+ /** Free-form integrator order reference (Shopify id, etc). */
876
+ externalOrderId: zod.z.string().min(1).max(120).optional(),
877
+ idempotencyKey: zod.z.string().min(1).max(128).optional()
878
+ });
879
+ const breakdownEntry = zod.z.object({
880
+ voucherId: zod.z.string().uuid(),
881
+ code: zod.z.string(),
882
+ amount: zod.z.number().int(),
883
+ type: zod.z.enum(["AMOUNT", "PERCENTAGE"]).optional(),
884
+ reason: zod.z.enum(["exclusivity_lost", "zero_after_running_total"]).optional()
885
+ });
886
+ const redemptionExplanation = zod.z.object({
887
+ code: zod.z.enum([
888
+ "voucher_not_found",
889
+ "campaign_inactive",
890
+ "voucher_disabled",
891
+ "voucher_expired",
892
+ "redemption_limit_reached",
893
+ "validation_failed",
894
+ "currency_mismatch",
895
+ "gift_balance_zero",
896
+ "no_discount_effect",
897
+ "order_required",
898
+ "exclusivity_lost",
899
+ "zero_after_running_total",
900
+ "gift_card_stacking_unsupported"
901
+ ]),
902
+ message: zod.z.string(),
903
+ voucherId: zod.z.string().uuid().optional(),
904
+ voucherCode: zod.z.string().optional(),
905
+ details: zod.z.record(zod.z.string(), zod.z.union([
906
+ zod.z.string(),
907
+ zod.z.number(),
908
+ zod.z.boolean(),
909
+ zod.z.null()
910
+ ])).optional()
911
+ });
912
+ const validateOutput = zod.z.object({
913
+ valid: zod.z.boolean(),
914
+ code: zod.z.string().optional(),
915
+ message: zod.z.string().optional(),
916
+ explanations: zod.z.array(redemptionExplanation).optional(),
917
+ preview: zod.z.object({
918
+ amount: zod.z.number().int(),
919
+ finalOrder: zod.z.object({
920
+ amount: zod.z.number().int(),
921
+ currency: zod.z.string()
922
+ }),
923
+ breakdown: zod.z.array(breakdownEntry)
924
+ }).optional()
925
+ });
926
+ const qualifyInput = zod.z.object({
927
+ customerId: zod.z.string().uuid(),
928
+ order: orderInput,
929
+ filters: zod.z.object({
930
+ campaignIds: zod.z.array(zod.z.string().uuid()).max(100).optional(),
931
+ includeSkipped: zod.z.boolean().optional()
932
+ }).optional()
933
+ });
934
+ const qualifyOutput = zod.z.object({
935
+ eligible: zod.z.array(zod.z.object({
936
+ code: zod.z.string(),
937
+ campaignId: zod.z.string().uuid().nullable(),
938
+ discount: voucherDiscount.omit({ appliesTo: true }).nullable(),
939
+ endDate: zod.z.string().datetime().nullable(),
940
+ preview: zod.z.object({
941
+ amount: zod.z.number().int(),
942
+ finalOrder: zod.z.object({
943
+ amount: zod.z.number().int(),
944
+ currency: zod.z.string()
945
+ }),
946
+ breakdown: zod.z.array(breakdownEntry)
947
+ }).optional()
948
+ })),
949
+ skipped: zod.z.array(zod.z.object({
950
+ code: zod.z.string(),
951
+ campaignId: zod.z.string().uuid().nullable(),
952
+ reason: zod.z.string(),
953
+ message: zod.z.string()
954
+ }))
955
+ });
956
+ const redeemOutput = zod.z.object({
957
+ ok: zod.z.boolean(),
958
+ redemptionId: zod.z.string().uuid().optional(),
959
+ amount: zod.z.number().int().optional(),
960
+ finalOrder: zod.z.object({
961
+ amount: zod.z.number().int(),
962
+ currency: zod.z.string()
963
+ }).optional(),
964
+ breakdown: zod.z.array(breakdownEntry).optional(),
965
+ idempotent: zod.z.boolean().optional(),
966
+ code: zod.z.string().optional(),
967
+ message: zod.z.string().optional(),
968
+ explanations: zod.z.array(redemptionExplanation).optional()
969
+ });
970
+ const stackRedeemInput = zod.z.object({
971
+ codes: zod.z.array(zod.z.string().min(1)).min(1).max(20),
972
+ customerId: zod.z.string().uuid().optional(),
973
+ orderId: zod.z.string().uuid().optional(),
974
+ externalOrderId: zod.z.string().min(1).max(120).optional(),
975
+ order: orderInput,
976
+ idempotencyKey: zod.z.string().min(1).max(128).optional()
977
+ });
978
+ const stackEntry = zod.z.object({
979
+ voucherCode: zod.z.string(),
980
+ voucherId: zod.z.string().uuid(),
981
+ redemptionId: zod.z.string().uuid(),
982
+ amount: zod.z.number().int()
983
+ });
984
+ const stackRedeemOutput = zod.z.object({
985
+ ok: zod.z.boolean(),
986
+ batchId: zod.z.string().uuid().optional(),
987
+ amount: zod.z.number().int().optional(),
988
+ finalOrder: zod.z.object({
989
+ amount: zod.z.number().int(),
990
+ currency: zod.z.string()
991
+ }).optional(),
992
+ breakdown: zod.z.array(breakdownEntry).optional(),
993
+ entries: zod.z.array(stackEntry).optional(),
994
+ idempotent: zod.z.boolean().optional(),
995
+ code: zod.z.string().optional(),
996
+ message: zod.z.string().optional(),
997
+ explanations: zod.z.array(redemptionExplanation).optional()
998
+ });
999
+ //#endregion
1000
+ //#region ../contract/src/schemas/promotion.ts
1001
+ const promotionTierOutput = zod.z.object({
1002
+ id: zod.z.string().uuid(),
1003
+ campaignId: zod.z.string().uuid(),
1004
+ name: zod.z.string(),
1005
+ description: zod.z.string().nullable(),
1006
+ effect: voucherDiscount,
1007
+ customRewards: zod.z.array(customReward),
1008
+ validationRuleId: zod.z.string().uuid().nullable(),
1009
+ active: zod.z.boolean(),
1010
+ priority: zod.z.number().int(),
1011
+ exclusive: zod.z.boolean(),
1012
+ startDate: zod.z.string().datetime().nullable(),
1013
+ endDate: zod.z.string().datetime().nullable(),
1014
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()),
1015
+ createdAt: zod.z.string().datetime(),
1016
+ updatedAt: zod.z.string().datetime()
1017
+ });
1018
+ const promotionTierCreateInput = zod.z.object({
1019
+ campaignId: zod.z.string().uuid(),
1020
+ name: zod.z.string().min(1),
1021
+ description: zod.z.string().optional(),
1022
+ effect: voucherDiscount,
1023
+ customRewards: zod.z.array(customReward).optional(),
1024
+ validationRuleId: zod.z.string().uuid().optional(),
1025
+ active: zod.z.boolean().optional(),
1026
+ priority: zod.z.number().int().optional(),
1027
+ exclusive: zod.z.boolean().optional(),
1028
+ startDate: zod.z.string().datetime().optional(),
1029
+ endDate: zod.z.string().datetime().optional(),
1030
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
1031
+ });
1032
+ const promotionTierUpdateInput = promotionTierCreateInput.omit({ campaignId: true }).partial();
1033
+ const qualificationInput = zod.z.object({
1034
+ customerId: zod.z.string().uuid().optional(),
1035
+ order: orderInput,
1036
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
1037
+ filters: zod.z.object({
1038
+ campaignIds: zod.z.array(zod.z.string().uuid()).optional(),
1039
+ includeSkipped: zod.z.boolean().optional()
1040
+ }).optional()
1041
+ });
1042
+ const qualificationReason = zod.z.enum([
1043
+ "campaign_inactive",
1044
+ "campaign_not_active",
1045
+ "promotion_inactive",
1046
+ "promotion_not_active",
1047
+ "currency_mismatch",
1048
+ "rule_failed",
1049
+ "rule_error",
1050
+ "no_discount_effect",
1051
+ "exclusivity_lost",
1052
+ "zero_after_running_total"
1053
+ ]);
1054
+ const qualifiedPromotion = zod.z.object({
1055
+ source: zod.z.literal("promotion"),
1056
+ campaignId: zod.z.string().uuid(),
1057
+ promotionTierId: zod.z.string().uuid(),
1058
+ name: zod.z.string(),
1059
+ discount: voucherDiscount,
1060
+ customRewards: zod.z.array(customReward),
1061
+ priority: zod.z.number().int(),
1062
+ exclusive: zod.z.boolean(),
1063
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown())
1064
+ });
1065
+ const skippedPromotion = qualifiedPromotion.extend({
1066
+ reason: qualificationReason,
1067
+ message: zod.z.string(),
1068
+ trace: zod.z.unknown().optional()
1069
+ });
1070
+ const qualificationOutput = zod.z.object({
1071
+ eligible: zod.z.array(qualifiedPromotion),
1072
+ skipped: zod.z.array(skippedPromotion),
1073
+ preview: zod.z.object({
1074
+ amount: zod.z.number().int(),
1075
+ finalOrder: zod.z.object({
1076
+ amount: zod.z.number().int(),
1077
+ currency: zod.z.string()
1078
+ }),
1079
+ breakdown: zod.z.array(breakdownEntry)
1080
+ })
1081
+ });
1082
+ //#endregion
1083
+ //#region ../contract/src/routes/promotions.ts
1084
+ const promotions = {
1085
+ tiers: {
1086
+ list: _orpc_contract.oc.route({
1087
+ method: "GET",
1088
+ path: "/promotions/tiers",
1089
+ summary: "List promotion tiers"
1090
+ }).input(paginationInput.extend({
1091
+ campaignId: zod.z.string().uuid().optional(),
1092
+ active: zod.z.boolean().optional()
1093
+ })).output(paginatedOutput(promotionTierOutput)),
1094
+ create: _orpc_contract.oc.route({
1095
+ method: "POST",
1096
+ path: "/promotions/tiers",
1097
+ summary: "Create promotion tier"
1098
+ }).input(promotionTierCreateInput).output(promotionTierOutput),
1099
+ update: _orpc_contract.oc.route({
1100
+ method: "PATCH",
1101
+ path: "/promotions/tiers/{id}",
1102
+ summary: "Update promotion tier",
1103
+ inputStructure: "detailed"
1104
+ }).input(zod.z.object({
1105
+ params: zod.z.object({ id: zod.z.string().uuid() }),
1106
+ body: zod.z.object({ patch: promotionTierUpdateInput })
1107
+ })).output(promotionTierOutput),
1108
+ delete: _orpc_contract.oc.route({
1109
+ method: "DELETE",
1110
+ path: "/promotions/tiers/{id}",
1111
+ summary: "Soft-delete promotion tier",
1112
+ inputStructure: "detailed"
1113
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
1114
+ },
1115
+ qualify: _orpc_contract.oc.meta(mcpMeta({
1116
+ expose: true,
1117
+ riskLevel: "safe"
1118
+ })).route({
1119
+ method: "POST",
1120
+ path: "/promotions/qualify",
1121
+ summary: "Return auto-applied promotions a cart qualifies for"
1122
+ }).input(qualificationInput).output(qualificationOutput)
1123
+ };
1124
+ //#endregion
1125
+ //#region ../contract/src/schemas/reward-type.ts
1126
+ const rewardTypeOutput = zod.z.object({
1127
+ id: zod.z.string().uuid(),
1128
+ key: zod.z.string(),
1129
+ name: zod.z.string(),
1130
+ description: zod.z.string().nullable(),
1131
+ payloadSchema: zod.z.record(zod.z.string(), zod.z.unknown()),
1132
+ createdAt: zod.z.string().datetime(),
1133
+ updatedAt: zod.z.string().datetime()
1134
+ });
1135
+ const rewardTypeCreateInput = zod.z.object({
1136
+ key: zod.z.string().min(2).max(50).regex(/^[A-Z][A-Z0-9_]*$/, "Use SCREAMING_SNAKE_CASE: e.g. FREE_SHIPPING"),
1137
+ name: zod.z.string().min(1).max(100),
1138
+ description: zod.z.string().max(500).optional(),
1139
+ payloadSchema: zod.z.record(zod.z.string(), zod.z.unknown())
1140
+ });
1141
+ const rewardTypeUpdateInput = rewardTypeCreateInput.omit({ key: true }).partial();
1142
+ //#endregion
1143
+ //#region ../contract/src/routes/reward-types.ts
1144
+ const rewardTypes = {
1145
+ list: _orpc_contract.oc.route({
1146
+ method: "GET",
1147
+ path: "/reward-types",
1148
+ summary: "List reward types"
1149
+ }).input(paginationInput.extend({ search: zod.z.string().optional() })).output(paginatedOutput(rewardTypeOutput)),
1150
+ get: _orpc_contract.oc.route({
1151
+ method: "GET",
1152
+ path: "/reward-types/{id}",
1153
+ summary: "Get reward type",
1154
+ inputStructure: "detailed"
1155
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(rewardTypeOutput),
1156
+ create: _orpc_contract.oc.route({
1157
+ method: "POST",
1158
+ path: "/reward-types",
1159
+ summary: "Create reward type"
1160
+ }).input(rewardTypeCreateInput).output(rewardTypeOutput),
1161
+ update: _orpc_contract.oc.route({
1162
+ method: "PATCH",
1163
+ path: "/reward-types/{id}",
1164
+ summary: "Update reward type",
1165
+ inputStructure: "detailed"
1166
+ }).input(zod.z.object({
1167
+ params: zod.z.object({ id: zod.z.string().uuid() }),
1168
+ body: zod.z.object({ patch: rewardTypeUpdateInput })
1169
+ })).output(rewardTypeOutput),
1170
+ delete: _orpc_contract.oc.route({
1171
+ method: "DELETE",
1172
+ path: "/reward-types/{id}",
1173
+ summary: "Soft-delete reward type",
1174
+ inputStructure: "detailed"
1175
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
1176
+ };
1177
+ //#endregion
1178
+ //#region ../contract/src/schemas/segment.ts
1179
+ const segmentRule = zod.z.record(zod.z.string(), zod.z.unknown());
1180
+ const segmentOutput = zod.z.object({
1181
+ id: zod.z.string().uuid(),
1182
+ name: zod.z.string(),
1183
+ description: zod.z.string().nullable(),
1184
+ rule: segmentRule,
1185
+ createdAt: zod.z.string().datetime(),
1186
+ updatedAt: zod.z.string().datetime()
1187
+ });
1188
+ const segmentCreateInput = zod.z.object({
1189
+ name: zod.z.string().min(1).max(100),
1190
+ description: zod.z.string().max(500).optional(),
1191
+ rule: segmentRule
1192
+ });
1193
+ const segmentUpdateInput = segmentCreateInput.partial();
1194
+ //#endregion
1195
+ //#region ../contract/src/routes/segments.ts
1196
+ const segments = {
1197
+ list: _orpc_contract.oc.route({
1198
+ method: "GET",
1199
+ path: "/segments",
1200
+ summary: "List segments"
1201
+ }).input(paginationInput.extend({ search: zod.z.string().optional() })).output(paginatedOutput(segmentOutput)),
1202
+ get: _orpc_contract.oc.route({
1203
+ method: "GET",
1204
+ path: "/segments/{id}",
1205
+ summary: "Get segment",
1206
+ inputStructure: "detailed"
1207
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(segmentOutput),
1208
+ create: _orpc_contract.oc.route({
1209
+ method: "POST",
1210
+ path: "/segments",
1211
+ summary: "Create segment"
1212
+ }).input(segmentCreateInput).output(segmentOutput),
1213
+ update: _orpc_contract.oc.route({
1214
+ method: "PATCH",
1215
+ path: "/segments/{id}",
1216
+ summary: "Update segment",
1217
+ inputStructure: "detailed"
1218
+ }).input(zod.z.object({
1219
+ params: zod.z.object({ id: zod.z.string().uuid() }),
1220
+ body: zod.z.object({ patch: segmentUpdateInput })
1221
+ })).output(segmentOutput),
1222
+ delete: _orpc_contract.oc.route({
1223
+ method: "DELETE",
1224
+ path: "/segments/{id}",
1225
+ summary: "Soft-delete segment",
1226
+ inputStructure: "detailed"
1227
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) })),
1228
+ preview: _orpc_contract.oc.meta(mcpMeta({
1229
+ expose: true,
1230
+ riskLevel: "safe",
1231
+ description: "Run a JSON Logic rule against existing customers and report match count plus a sample."
1232
+ })).route({
1233
+ method: "POST",
1234
+ path: "/segments/preview",
1235
+ summary: "Preview rule against customers"
1236
+ }).input(zod.z.object({
1237
+ rule: segmentRule,
1238
+ sampleSize: zod.z.number().int().min(1).max(50).default(10)
1239
+ })).output(zod.z.object({
1240
+ matchedCount: zod.z.number().int().nonnegative(),
1241
+ sample: zod.z.array(customerOutput)
1242
+ }))
1243
+ };
1244
+ //#endregion
1245
+ //#region ../contract/src/schemas/validation-rule.ts
1246
+ const validationRuleAppliesTo = zod.z.enum([
1247
+ "voucher",
1248
+ "promotion",
1249
+ "earn",
1250
+ "reward"
1251
+ ]);
1252
+ const validationRuleOutput = zod.z.object({
1253
+ id: zod.z.string().uuid(),
1254
+ name: zod.z.string(),
1255
+ description: zod.z.string().nullable(),
1256
+ rule: zod.z.record(zod.z.string(), zod.z.unknown()),
1257
+ appliesTo: validationRuleAppliesTo,
1258
+ createdAt: zod.z.string().datetime(),
1259
+ updatedAt: zod.z.string().datetime()
1260
+ });
1261
+ const validationRuleCreateInput = zod.z.object({
1262
+ name: zod.z.string().min(1).max(100),
1263
+ description: zod.z.string().max(500).optional(),
1264
+ rule: zod.z.record(zod.z.string(), zod.z.unknown()),
1265
+ appliesTo: validationRuleAppliesTo.optional()
1266
+ });
1267
+ const validationRuleUpdateInput = validationRuleCreateInput.partial();
1268
+ //#endregion
1269
+ //#region ../contract/src/routes/validation-rules.ts
1270
+ const validationRules = {
1271
+ list: _orpc_contract.oc.route({
1272
+ method: "GET",
1273
+ path: "/validation-rules",
1274
+ summary: "List validation rules"
1275
+ }).input(paginationInput.extend({ search: zod.z.string().optional() })).output(paginatedOutput(validationRuleOutput)),
1276
+ get: _orpc_contract.oc.route({
1277
+ method: "GET",
1278
+ path: "/validation-rules/{id}",
1279
+ summary: "Get validation rule",
1280
+ inputStructure: "detailed"
1281
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(validationRuleOutput),
1282
+ create: _orpc_contract.oc.route({
1283
+ method: "POST",
1284
+ path: "/validation-rules",
1285
+ summary: "Create validation rule"
1286
+ }).input(validationRuleCreateInput).output(validationRuleOutput),
1287
+ update: _orpc_contract.oc.route({
1288
+ method: "PATCH",
1289
+ path: "/validation-rules/{id}",
1290
+ summary: "Update validation rule",
1291
+ inputStructure: "detailed"
1292
+ }).input(zod.z.object({
1293
+ params: zod.z.object({ id: zod.z.string().uuid() }),
1294
+ body: zod.z.object({ patch: validationRuleUpdateInput })
1295
+ })).output(validationRuleOutput),
1296
+ delete: _orpc_contract.oc.route({
1297
+ method: "DELETE",
1298
+ path: "/validation-rules/{id}",
1299
+ summary: "Soft-delete validation rule",
1300
+ inputStructure: "detailed"
1301
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
1302
+ };
1303
+ //#endregion
1304
+ //#region ../contract/src/routes/vouchers.ts
1305
+ const vouchers = {
1306
+ list: _orpc_contract.oc.meta(mcpMeta({
1307
+ expose: true,
1308
+ riskLevel: "safe"
1309
+ })).route({
1310
+ method: "GET",
1311
+ path: "/vouchers",
1312
+ summary: "List vouchers"
1313
+ }).input(paginationInput.extend({
1314
+ search: zod.z.string().optional(),
1315
+ campaignId: zod.z.string().uuid().optional(),
1316
+ active: zod.z.boolean().optional(),
1317
+ customerId: zod.z.string().uuid().optional()
1318
+ })).output(paginatedOutput(voucherOutput)),
1319
+ get: _orpc_contract.oc.meta(mcpMeta({
1320
+ expose: true,
1321
+ riskLevel: "safe"
1322
+ })).route({
1323
+ method: "GET",
1324
+ path: "/vouchers/{code}",
1325
+ summary: "Get voucher by code",
1326
+ inputStructure: "detailed"
1327
+ }).input(zod.z.object({ params: zod.z.object({ code: zod.z.string() }) })).output(voucherOutput),
1328
+ create: _orpc_contract.oc.route({
1329
+ method: "POST",
1330
+ path: "/vouchers",
1331
+ summary: "Create voucher"
1332
+ }).input(voucherCreateInput).output(voucherOutput),
1333
+ update: _orpc_contract.oc.route({
1334
+ method: "PATCH",
1335
+ path: "/vouchers/{code}",
1336
+ summary: "Update voucher",
1337
+ inputStructure: "detailed"
1338
+ }).input(zod.z.object({
1339
+ params: zod.z.object({ code: zod.z.string() }),
1340
+ body: zod.z.object({ patch: voucherUpdateInput })
1341
+ })).output(voucherOutput),
1342
+ delete: _orpc_contract.oc.route({
1343
+ method: "DELETE",
1344
+ path: "/vouchers/{code}",
1345
+ summary: "Soft-delete voucher",
1346
+ inputStructure: "detailed"
1347
+ }).input(zod.z.object({ params: zod.z.object({ code: zod.z.string() }) })).output(zod.z.object({ ok: zod.z.literal(true) })),
1348
+ bulk: _orpc_contract.oc.route({
1349
+ method: "POST",
1350
+ path: "/vouchers/bulk",
1351
+ summary: "Generate vouchers in bulk"
1352
+ }).input(voucherBulkCreateInput).output(zod.z.object({
1353
+ campaignId: zod.z.string().uuid(),
1354
+ generated: zod.z.number().int(),
1355
+ jobId: zod.z.string().uuid().optional()
1356
+ })),
1357
+ validate: _orpc_contract.oc.meta(mcpMeta({
1358
+ expose: true,
1359
+ riskLevel: "safe"
1360
+ })).route({
1361
+ method: "POST",
1362
+ path: "/vouchers/{code}/validate",
1363
+ summary: "Validate a voucher against an optional order context",
1364
+ inputStructure: "detailed"
1365
+ }).input(zod.z.object({
1366
+ params: zod.z.object({ code: zod.z.string().min(1) }),
1367
+ body: validateInput.omit({ code: true }).optional()
1368
+ })).output(validateOutput),
1369
+ qualify: _orpc_contract.oc.meta(mcpMeta({
1370
+ expose: true,
1371
+ riskLevel: "safe"
1372
+ })).route({
1373
+ method: "POST",
1374
+ path: "/vouchers/qualify",
1375
+ summary: "Batch-qualify customer-held voucher codes for an order"
1376
+ }).input(qualifyInput).output(qualifyOutput),
1377
+ redeem: _orpc_contract.oc.meta(mcpMeta({
1378
+ expose: true,
1379
+ riskLevel: "mutating",
1380
+ description: "Commit a redemption against an order. Confirm with the user before calling. Use idempotencyKey to safely retry."
1381
+ })).route({
1382
+ method: "POST",
1383
+ path: "/vouchers/{code}/redemption",
1384
+ summary: "Redeem a voucher",
1385
+ inputStructure: "detailed"
1386
+ }).input(zod.z.object({
1387
+ params: zod.z.object({ code: zod.z.string().min(1) }),
1388
+ body: redeemInput.omit({ code: true }).optional()
1389
+ })).output(redeemOutput),
1390
+ stackRedeem: _orpc_contract.oc.meta(mcpMeta({
1391
+ expose: true,
1392
+ riskLevel: "mutating",
1393
+ description: "Apply N codes to one order atomically. Either every voucher commits or none. Confirm with the user before calling."
1394
+ })).route({
1395
+ method: "POST",
1396
+ path: "/redemptions/stack",
1397
+ summary: "Redeem multiple vouchers against one order in a single transaction"
1398
+ }).input(stackRedeemInput).output(stackRedeemOutput),
1399
+ transactions: _orpc_contract.oc.route({
1400
+ method: "GET",
1401
+ path: "/vouchers/{code}/transactions",
1402
+ summary: "Gift card balance ledger",
1403
+ inputStructure: "detailed"
1404
+ }).input(zod.z.object({ params: zod.z.object({ code: zod.z.string() }) })).output(zod.z.object({ data: zod.z.array(zod.z.object({
1405
+ id: zod.z.string().uuid(),
1406
+ redemptionId: zod.z.string().uuid().nullable(),
1407
+ delta: zod.z.number().int(),
1408
+ balanceAfter: zod.z.number().int(),
1409
+ reason: zod.z.enum([
1410
+ "CREDIT",
1411
+ "REDEMPTION",
1412
+ "ROLLBACK",
1413
+ "ADJUSTMENT"
1414
+ ]),
1415
+ createdAt: zod.z.string().datetime()
1416
+ })) }))
1417
+ };
1418
+ //#endregion
1419
+ //#region ../contract/src/schemas/webhook.ts
1420
+ const webhookOutput = zod.z.object({
1421
+ id: zod.z.string().uuid(),
1422
+ name: zod.z.string(),
1423
+ url: zod.z.string().url(),
1424
+ secretPrefix: zod.z.string(),
1425
+ events: zod.z.array(zod.z.string()),
1426
+ active: zod.z.boolean(),
1427
+ createdAt: zod.z.string().datetime(),
1428
+ updatedAt: zod.z.string().datetime()
1429
+ });
1430
+ const webhookCreateInput = zod.z.object({
1431
+ name: zod.z.string().min(1).max(100),
1432
+ url: zod.z.string().url(),
1433
+ events: zod.z.array(zod.z.string()).min(1).default(["*"]),
1434
+ active: zod.z.boolean().default(true)
1435
+ });
1436
+ const webhookCreateOutput = webhookOutput.extend({
1437
+ /** Plaintext signing secret. Shown once. */
1438
+ secret: zod.z.string() });
1439
+ const webhookUpdateInput = zod.z.object({
1440
+ name: zod.z.string().min(1).max(100).optional(),
1441
+ url: zod.z.string().url().optional(),
1442
+ events: zod.z.array(zod.z.string()).optional(),
1443
+ active: zod.z.boolean().optional()
1444
+ });
1445
+ const webhookDeliveryOutput = zod.z.object({
1446
+ id: zod.z.string().uuid(),
1447
+ webhookId: zod.z.string().uuid(),
1448
+ eventId: zod.z.string().uuid(),
1449
+ eventType: zod.z.string(),
1450
+ status: zod.z.enum([
1451
+ "pending",
1452
+ "succeeded",
1453
+ "failed",
1454
+ "dead"
1455
+ ]),
1456
+ attempts: zod.z.number().int(),
1457
+ responseStatus: zod.z.number().int().nullable(),
1458
+ responseBody: zod.z.string().nullable(),
1459
+ error: zod.z.string().nullable(),
1460
+ nextRetryAt: zod.z.string().datetime().nullable(),
1461
+ createdAt: zod.z.string().datetime(),
1462
+ updatedAt: zod.z.string().datetime()
1463
+ });
1464
+ const eventOutput = zod.z.object({
1465
+ id: zod.z.string().uuid(),
1466
+ type: zod.z.string(),
1467
+ payload: zod.z.record(zod.z.string(), zod.z.unknown()),
1468
+ entityId: zod.z.string().nullable(),
1469
+ createdAt: zod.z.string().datetime()
1470
+ });
1471
+ //#endregion
1472
+ //#region ../contract/src/routes/webhooks.ts
1473
+ const webhooks$1 = {
1474
+ list: _orpc_contract.oc.route({
1475
+ method: "GET",
1476
+ path: "/webhooks",
1477
+ summary: "List webhooks"
1478
+ }).output(zod.z.object({ data: zod.z.array(webhookOutput) })),
1479
+ get: _orpc_contract.oc.route({
1480
+ method: "GET",
1481
+ path: "/webhooks/{id}",
1482
+ summary: "Get webhook",
1483
+ inputStructure: "detailed"
1484
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(webhookOutput),
1485
+ create: _orpc_contract.oc.route({
1486
+ method: "POST",
1487
+ path: "/webhooks",
1488
+ summary: "Create webhook"
1489
+ }).input(webhookCreateInput).output(webhookCreateOutput),
1490
+ update: _orpc_contract.oc.route({
1491
+ method: "PATCH",
1492
+ path: "/webhooks/{id}",
1493
+ summary: "Update webhook",
1494
+ inputStructure: "detailed"
1495
+ }).input(zod.z.object({
1496
+ params: zod.z.object({ id: zod.z.string().uuid() }),
1497
+ body: zod.z.object({ patch: webhookUpdateInput })
1498
+ })).output(webhookOutput),
1499
+ delete: _orpc_contract.oc.route({
1500
+ method: "DELETE",
1501
+ path: "/webhooks/{id}",
1502
+ summary: "Soft-delete webhook",
1503
+ inputStructure: "detailed"
1504
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) })),
1505
+ deliveries: _orpc_contract.oc.route({
1506
+ method: "GET",
1507
+ path: "/webhooks/{id}/deliveries",
1508
+ summary: "Recent deliveries for a webhook",
1509
+ inputStructure: "detailed"
1510
+ }).input(zod.z.object({
1511
+ params: zod.z.object({ id: zod.z.string().uuid() }),
1512
+ query: zod.z.object({ limit: zod.z.number().int().min(1).max(100).default(50) })
1513
+ })).output(zod.z.object({ data: zod.z.array(webhookDeliveryOutput) })),
1514
+ replay: _orpc_contract.oc.route({
1515
+ method: "POST",
1516
+ path: "/webhooks/deliveries/{id}/replay",
1517
+ summary: "Re-enqueue a delivery (succeeded or otherwise)",
1518
+ inputStructure: "detailed"
1519
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) }))
1520
+ };
1521
+ const events = {
1522
+ list: _orpc_contract.oc.route({
1523
+ method: "GET",
1524
+ path: "/events",
1525
+ summary: "List events"
1526
+ }).input(paginationInput.extend({ type: zod.z.string().optional() })).output(paginatedOutput(eventOutput)),
1527
+ get: _orpc_contract.oc.route({
1528
+ method: "GET",
1529
+ path: "/events/{id}",
1530
+ summary: "Get event",
1531
+ inputStructure: "detailed"
1532
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(eventOutput)
1533
+ };
1534
+ //#endregion
1535
+ //#region ../contract/src/routes/insights.ts
1536
+ const counters = zod.z.object({
1537
+ redemptionsToday: zod.z.number().int(),
1538
+ redemptions7d: zod.z.number().int(),
1539
+ redemptions30d: zod.z.number().int()
1540
+ });
1541
+ const daily = zod.z.array(zod.z.object({
1542
+ day: zod.z.string(),
1543
+ total: zod.z.number().int()
1544
+ }));
1545
+ const topCampaigns = zod.z.array(zod.z.object({
1546
+ campaignId: zod.z.string().uuid(),
1547
+ campaignName: zod.z.string(),
1548
+ redemptions: zod.z.number().int()
1549
+ }));
1550
+ const failures = zod.z.array(zod.z.object({
1551
+ reason: zod.z.string(),
1552
+ total: zod.z.number().int()
1553
+ }));
1554
+ const webhooks = zod.z.array(zod.z.object({
1555
+ status: zod.z.enum([
1556
+ "pending",
1557
+ "succeeded",
1558
+ "failed",
1559
+ "dead"
1560
+ ]),
1561
+ total: zod.z.number().int()
1562
+ }));
1563
+ const insights = { summary: _orpc_contract.oc.route({
1564
+ method: "GET",
1565
+ path: "/insights/summary",
1566
+ summary: "Headline metrics for the dashboard insights page"
1567
+ }).output(zod.z.object({
1568
+ sinceDays: zod.z.number().int(),
1569
+ counters,
1570
+ daily,
1571
+ topCampaigns,
1572
+ failures,
1573
+ webhooks
1574
+ })) };
1575
+ //#endregion
1576
+ //#region ../contract/src/schemas/user.ts
1577
+ const userRole = zod.z.enum(["admin", "member"]);
1578
+ const userOutput = zod.z.object({
1579
+ id: zod.z.string(),
1580
+ email: zod.z.string().email(),
1581
+ name: zod.z.string().nullable(),
1582
+ role: userRole,
1583
+ mustChangePassword: zod.z.boolean(),
1584
+ disabledAt: zod.z.string().datetime().nullable(),
1585
+ createdAt: zod.z.string().datetime()
1586
+ });
1587
+ const userCreateInput = zod.z.object({
1588
+ email: zod.z.string().email(),
1589
+ name: zod.z.string().max(120).optional(),
1590
+ role: userRole.default("member")
1591
+ });
1592
+ const userCreateOutput = userOutput.extend({
1593
+ /** Generated password. Shown once at creation. */
1594
+ password: zod.z.string() });
1595
+ //#endregion
1596
+ //#region ../contract/src/routes/users.ts
1597
+ const users = {
1598
+ list: _orpc_contract.oc.route({
1599
+ method: "GET",
1600
+ path: "/users",
1601
+ summary: "List staff users (admin)"
1602
+ }).output(zod.z.object({ data: zod.z.array(userOutput) })),
1603
+ create: _orpc_contract.oc.route({
1604
+ method: "POST",
1605
+ path: "/users",
1606
+ summary: "Create staff user (admin)"
1607
+ }).input(userCreateInput).output(userCreateOutput),
1608
+ resetPassword: _orpc_contract.oc.route({
1609
+ method: "POST",
1610
+ path: "/users/{id}/reset-password",
1611
+ summary: "Reset a staff user's password (admin)",
1612
+ inputStructure: "detailed"
1613
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string() }) })).output(userCreateOutput),
1614
+ setRole: _orpc_contract.oc.route({
1615
+ method: "PATCH",
1616
+ path: "/users/{id}/role",
1617
+ summary: "Change a staff user's role (admin)",
1618
+ inputStructure: "detailed"
1619
+ }).input(zod.z.object({
1620
+ params: zod.z.object({ id: zod.z.string() }),
1621
+ body: zod.z.object({ role: userRole })
1622
+ })).output(userOutput),
1623
+ disable: _orpc_contract.oc.route({
1624
+ method: "POST",
1625
+ path: "/users/{id}/disable",
1626
+ summary: "Disable a staff user (admin)",
1627
+ inputStructure: "detailed"
1628
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string() }) })).output(userOutput),
1629
+ enable: _orpc_contract.oc.route({
1630
+ method: "POST",
1631
+ path: "/users/{id}/enable",
1632
+ summary: "Re-enable a staff user (admin)",
1633
+ inputStructure: "detailed"
1634
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string() }) })).output(userOutput)
1635
+ };
1636
+ //#endregion
1637
+ //#region ../contract/src/schemas/order.ts
1638
+ const orderStatus = zod.z.enum([
1639
+ "CREATED",
1640
+ "PAID",
1641
+ "CANCELED",
1642
+ "FULFILLED"
1643
+ ]);
1644
+ const orderItem = zod.z.object({
1645
+ productId: zod.z.string().optional(),
1646
+ sku: zod.z.string().optional(),
1647
+ name: zod.z.string().min(1),
1648
+ quantity: zod.z.number().int().positive(),
1649
+ unitPrice: zod.z.number().int().nonnegative()
1650
+ });
1651
+ const orderOutput = zod.z.object({
1652
+ id: zod.z.string().uuid(),
1653
+ externalId: zod.z.string().nullable(),
1654
+ customerId: zod.z.string().uuid().nullable(),
1655
+ items: zod.z.array(orderItem),
1656
+ amount: zod.z.number().int(),
1657
+ discountAmount: zod.z.number().int(),
1658
+ currency: zod.z.string(),
1659
+ status: orderStatus,
1660
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()),
1661
+ createdAt: zod.z.string().datetime(),
1662
+ updatedAt: zod.z.string().datetime()
1663
+ });
1664
+ const orderCreateInput = zod.z.object({
1665
+ externalId: zod.z.string().min(1).max(120).optional(),
1666
+ customerId: zod.z.string().uuid().optional(),
1667
+ items: zod.z.array(orderItem).min(1),
1668
+ amount: zod.z.number().int().nonnegative(),
1669
+ discountAmount: zod.z.number().int().nonnegative().optional(),
1670
+ currency: zod.z.string().length(3),
1671
+ status: orderStatus.optional(),
1672
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
1673
+ });
1674
+ const orderUpdateInput = zod.z.object({
1675
+ id: zod.z.string().uuid(),
1676
+ patch: zod.z.object({
1677
+ status: orderStatus.optional(),
1678
+ discountAmount: zod.z.number().int().nonnegative().optional(),
1679
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
1680
+ })
1681
+ });
1682
+ const orderListInput = zod.z.object({
1683
+ limit: zod.z.number().int().min(1).max(100).default(20),
1684
+ cursor: zod.z.string().optional(),
1685
+ customerId: zod.z.string().uuid().optional(),
1686
+ status: orderStatus.optional(),
1687
+ search: zod.z.string().optional()
1688
+ });
1689
+ //#endregion
1690
+ //#region ../contract/src/routes/orders.ts
1691
+ const orders = {
1692
+ list: _orpc_contract.oc.route({
1693
+ method: "GET",
1694
+ path: "/orders",
1695
+ summary: "List orders"
1696
+ }).input(orderListInput).output(paginatedOutput(orderOutput)),
1697
+ get: _orpc_contract.oc.route({
1698
+ method: "GET",
1699
+ path: "/orders/{id}",
1700
+ summary: "Fetch one order",
1701
+ inputStructure: "detailed"
1702
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(orderOutput),
1703
+ create: _orpc_contract.oc.route({
1704
+ method: "POST",
1705
+ path: "/orders",
1706
+ summary: "Create an order"
1707
+ }).input(orderCreateInput).output(orderOutput),
1708
+ update: _orpc_contract.oc.route({
1709
+ method: "PATCH",
1710
+ path: "/orders/{id}",
1711
+ summary: "Update an order",
1712
+ inputStructure: "detailed"
1713
+ }).input(zod.z.object({
1714
+ params: zod.z.object({ id: zod.z.string().uuid() }),
1715
+ body: orderUpdateInput.omit({ id: true })
1716
+ })).output(orderOutput),
1717
+ cancel: _orpc_contract.oc.route({
1718
+ method: "POST",
1719
+ path: "/orders/{id}/cancel",
1720
+ summary: "Cancel an order",
1721
+ inputStructure: "detailed"
1722
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(orderOutput),
1723
+ fulfill: _orpc_contract.oc.route({
1724
+ method: "POST",
1725
+ path: "/orders/{id}/fulfill",
1726
+ summary: "Mark order fulfilled",
1727
+ inputStructure: "detailed"
1728
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(orderOutput),
1729
+ delete: _orpc_contract.oc.route({
1730
+ method: "DELETE",
1731
+ path: "/orders/{id}",
1732
+ summary: "Soft-delete an order",
1733
+ inputStructure: "detailed"
1734
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ ok: zod.z.literal(true) })),
1735
+ redemptions: _orpc_contract.oc.route({
1736
+ method: "GET",
1737
+ path: "/orders/{id}/redemptions",
1738
+ summary: "List redemptions attached to an order",
1739
+ inputStructure: "detailed"
1740
+ }).input(zod.z.object({ params: zod.z.object({ id: zod.z.string().uuid() }) })).output(zod.z.object({ data: zod.z.array(zod.z.object({
1741
+ id: zod.z.string().uuid(),
1742
+ voucherCode: zod.z.string(),
1743
+ voucherId: zod.z.string().uuid(),
1744
+ customerId: zod.z.string().uuid().nullable(),
1745
+ result: zod.z.enum([
1746
+ "SUCCESS",
1747
+ "FAILURE",
1748
+ "ROLLBACK"
1749
+ ]),
1750
+ failureReason: zod.z.string().nullable(),
1751
+ amount: zod.z.number().int().nullable(),
1752
+ createdAt: zod.z.string().datetime()
1753
+ })) }))
1754
+ };
1755
+ //#endregion
1756
+ //#region ../contract/src/schemas/audit-log.ts
1757
+ const auditActor = zod.z.enum([
1758
+ "user",
1759
+ "api_key",
1760
+ "system"
1761
+ ]);
1762
+ const auditLogOutput = zod.z.object({
1763
+ id: zod.z.string().uuid(),
1764
+ actor: auditActor,
1765
+ actorId: zod.z.string().nullable(),
1766
+ action: zod.z.string(),
1767
+ entity: zod.z.string(),
1768
+ entityId: zod.z.string().nullable(),
1769
+ before: zod.z.unknown().nullable(),
1770
+ after: zod.z.unknown().nullable(),
1771
+ ip: zod.z.string().nullable(),
1772
+ userAgent: zod.z.string().nullable(),
1773
+ createdAt: zod.z.string().datetime()
1774
+ });
1775
+ const auditLogListInput = zod.z.object({
1776
+ limit: zod.z.number().int().min(1).max(100).default(50),
1777
+ cursor: zod.z.string().optional(),
1778
+ actor: auditActor.optional(),
1779
+ entity: zod.z.string().optional(),
1780
+ action: zod.z.string().optional(),
1781
+ entityId: zod.z.string().optional()
1782
+ });
1783
+ //#endregion
1784
+ //#region ../contract/src/routes/audit-log.ts
1785
+ const auditLog = { list: _orpc_contract.oc.route({
1786
+ method: "GET",
1787
+ path: "/audit-log",
1788
+ summary: "List audit log entries (admin)"
1789
+ }).input(auditLogListInput).output(zod.z.object({
1790
+ data: zod.z.array(auditLogOutput),
1791
+ next: zod.z.string().optional()
1792
+ })) };
1793
+ //#endregion
1794
+ //#region ../contract/src/routes/workspace.ts
1795
+ const workspaceOutput = zod.z.object({
1796
+ name: zod.z.string(),
1797
+ defaultCurrency: zod.z.string(),
1798
+ defaultTimezone: zod.z.string(),
1799
+ emailProvider: zod.z.enum(["resend", "log"]),
1800
+ updatedAt: zod.z.string().datetime()
1801
+ });
1802
+ const workspaceUpdateInput = zod.z.object({
1803
+ name: zod.z.string().min(1).max(120).optional(),
1804
+ defaultCurrency: zod.z.string().length(3).optional(),
1805
+ defaultTimezone: zod.z.string().min(1).max(64).optional()
1806
+ });
1807
+ const workspace = {
1808
+ get: _orpc_contract.oc.route({
1809
+ method: "GET",
1810
+ path: "/workspace",
1811
+ summary: "Workspace settings"
1812
+ }).output(workspaceOutput),
1813
+ update: _orpc_contract.oc.route({
1814
+ method: "PATCH",
1815
+ path: "/workspace",
1816
+ summary: "Update workspace settings (admin)"
1817
+ }).input(workspaceUpdateInput).output(workspaceOutput)
1818
+ };
1819
+ //#endregion
1820
+ //#region ../contract/src/router.ts
1821
+ const healthOutput = zod.z.object({
1822
+ status: zod.z.literal("ok"),
1823
+ version: zod.z.string()
1824
+ });
1825
+ const contract = {
1826
+ health: _orpc_contract.oc.route({
1827
+ method: "GET",
1828
+ path: "/health",
1829
+ summary: "Liveness probe"
1830
+ }).output(healthOutput),
1831
+ ready: _orpc_contract.oc.route({
1832
+ method: "GET",
1833
+ path: "/ready",
1834
+ summary: "Readiness probe (db + worker)"
1835
+ }).output(zod.z.object({
1836
+ status: zod.z.enum(["ok", "degraded"]),
1837
+ checks: zod.z.object({
1838
+ db: zod.z.boolean(),
1839
+ worker: zod.z.boolean()
1840
+ })
1841
+ })),
1842
+ customers,
1843
+ segments,
1844
+ campaigns,
1845
+ promotions,
1846
+ vouchers,
1847
+ validationRules,
1848
+ rewardTypes,
1849
+ loyalty,
1850
+ referrals,
1851
+ apiKeys,
1852
+ webhooks: webhooks$1,
1853
+ events,
1854
+ insights,
1855
+ users,
1856
+ orders,
1857
+ auditLog,
1858
+ workspace
1859
+ };
1860
+ //#endregion
1861
+ //#region src/index.ts
1862
+ function createClient(options) {
1863
+ return (0, _orpc_client.createORPCClient)(new _orpc_openapi_client_fetch.OpenAPILink(contract, {
1864
+ url: `${options.baseUrl.replace(/\/$/, "")}/api/v1`,
1865
+ headers: () => {
1866
+ const headers = {};
1867
+ if (options.apiKey) headers["Authorization"] = `Bearer ${options.apiKey}`;
1868
+ return headers;
1869
+ },
1870
+ ...options.fetch ? { fetch: options.fetch } : {}
1871
+ }));
1872
+ }
1873
+ /**
1874
+ * Verify the X-Offerkit-Signature header against the raw request body.
1875
+ *
1876
+ * Format: `t=<unix-seconds>,v1=<hex>`. v1 = HMAC-SHA256(secret, "${t}.${rawBody}").
1877
+ * Returns true on match within the tolerance window.
1878
+ */
1879
+ function verifyWebhook(rawBody, signature, secret, options = {}) {
1880
+ const tolerance = options.toleranceSeconds ?? 300;
1881
+ const now = options.now ?? Date.now();
1882
+ const parts = {};
1883
+ for (const segment of signature.split(",")) {
1884
+ const idx = segment.indexOf("=");
1885
+ if (idx <= 0) continue;
1886
+ parts[segment.slice(0, idx)] = segment.slice(idx + 1);
1887
+ }
1888
+ const t = Number(parts["t"]);
1889
+ const v1 = parts["v1"];
1890
+ if (!Number.isFinite(t) || !v1) return false;
1891
+ if (Math.abs(now / 1e3 - t) > tolerance) return false;
1892
+ const expected = (0, node_crypto.createHmac)("sha256", secret).update(`${String(t)}.${rawBody}`).digest("hex");
1893
+ if (expected.length !== v1.length) return false;
1894
+ return (0, node_crypto.timingSafeEqual)(Buffer.from(expected, "hex"), Buffer.from(v1, "hex"));
1895
+ }
1896
+ //#endregion
1897
+ exports.createClient = createClient;
1898
+ exports.verifyWebhook = verifyWebhook;