@shopickup/adapters-foxpost 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/capabilities/index.d.ts +9 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +8 -0
- package/dist/capabilities/label.d.ts +27 -0
- package/dist/capabilities/label.d.ts.map +1 -0
- package/dist/capabilities/label.js +370 -0
- package/dist/capabilities/parcels.d.ts +21 -0
- package/dist/capabilities/parcels.d.ts.map +1 -0
- package/dist/capabilities/parcels.js +233 -0
- package/dist/capabilities/pickup-points.d.ts +38 -0
- package/dist/capabilities/pickup-points.d.ts.map +1 -0
- package/dist/capabilities/pickup-points.js +225 -0
- package/dist/capabilities/track.d.ts +16 -0
- package/dist/capabilities/track.d.ts.map +1 -0
- package/dist/capabilities/track.js +99 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +30 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +165 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +151 -0
- package/dist/mappers/index.d.ts +108 -0
- package/dist/mappers/index.d.ts.map +1 -0
- package/dist/mappers/index.js +270 -0
- package/dist/mappers/trackStatus.d.ts +58 -0
- package/dist/mappers/trackStatus.d.ts.map +1 -0
- package/dist/mappers/trackStatus.js +290 -0
- package/dist/types/generated.d.ts +177 -0
- package/dist/types/generated.d.ts.map +1 -0
- package/dist/types/generated.js +9 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/utils/httpUtils.d.ts +18 -0
- package/dist/utils/httpUtils.d.ts.map +1 -0
- package/dist/utils/httpUtils.js +33 -0
- package/dist/utils/resolveBaseUrl.d.ts +23 -0
- package/dist/utils/resolveBaseUrl.d.ts.map +1 -0
- package/dist/utils/resolveBaseUrl.js +19 -0
- package/dist/validation.d.ts +1723 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +799 -0
- package/package.json +68 -0
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schemas for runtime validation
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Money schema (ISO currency amount in smallest unit)
|
|
7
|
+
*/
|
|
8
|
+
const MoneySchema = z.object({
|
|
9
|
+
amount: z.number().int().nonnegative().describe('Amount must be non-negative'),
|
|
10
|
+
currency: z.string().length(3).describe('Currency must be ISO 4217 code (3 chars)'),
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Contact schema (phone/email optional)
|
|
14
|
+
*/
|
|
15
|
+
const ContactSchema = z.object({
|
|
16
|
+
name: z.string().min(1).describe('Name is required'),
|
|
17
|
+
phone: z.string().optional(),
|
|
18
|
+
email: z.string().optional(),
|
|
19
|
+
company: z.string().optional(),
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Address schema (full shipping address)
|
|
23
|
+
*/
|
|
24
|
+
const AddressSchema = z.object({
|
|
25
|
+
name: z.string().min(1).describe('Address name is required'),
|
|
26
|
+
street: z.string().min(1).describe('Street is required'),
|
|
27
|
+
city: z.string().min(1).describe('City is required'),
|
|
28
|
+
postalCode: z.string().min(1).describe('Postal code is required'),
|
|
29
|
+
country: z.string().min(1).describe('Country is required'),
|
|
30
|
+
phone: z.string().optional(),
|
|
31
|
+
email: z.email().optional(),
|
|
32
|
+
company: z.string().optional(),
|
|
33
|
+
province: z.string().optional(),
|
|
34
|
+
isPoBox: z.boolean().optional(),
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Home delivery schema
|
|
38
|
+
*/
|
|
39
|
+
const HomeDeliverySchema = z.object({
|
|
40
|
+
method: z.literal('HOME'),
|
|
41
|
+
address: AddressSchema,
|
|
42
|
+
instructions: z.string().optional(),
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Pickup point delivery schema
|
|
46
|
+
*/
|
|
47
|
+
const PickupPointDeliverySchema = z.object({
|
|
48
|
+
method: z.literal('PICKUP_POINT'),
|
|
49
|
+
pickupPoint: z.object({
|
|
50
|
+
id: z.string().min(1, 'Pickup point ID is required'),
|
|
51
|
+
provider: z.string().optional(),
|
|
52
|
+
name: z.string().optional(),
|
|
53
|
+
address: AddressSchema.optional(),
|
|
54
|
+
type: z.enum(['LOCKER', 'SHOP', 'POST_OFFICE', 'OTHER']).optional(),
|
|
55
|
+
}),
|
|
56
|
+
instructions: z.string().optional(),
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Delivery discriminated union
|
|
60
|
+
*/
|
|
61
|
+
const DeliverySchema = z.discriminatedUnion('method', [
|
|
62
|
+
HomeDeliverySchema,
|
|
63
|
+
PickupPointDeliverySchema,
|
|
64
|
+
]);
|
|
65
|
+
/**
|
|
66
|
+
* Parcel schema (canonical domain type)
|
|
67
|
+
*/
|
|
68
|
+
const ParcelSchema = z.object({
|
|
69
|
+
id: z.string().min(1),
|
|
70
|
+
shipper: z.object({
|
|
71
|
+
contact: ContactSchema,
|
|
72
|
+
address: AddressSchema,
|
|
73
|
+
}),
|
|
74
|
+
recipient: z.object({
|
|
75
|
+
contact: ContactSchema,
|
|
76
|
+
delivery: DeliverySchema,
|
|
77
|
+
}),
|
|
78
|
+
carrierIds: z.record(z.string(), z.string()).optional(),
|
|
79
|
+
service: z.enum(['standard', 'express', 'economy', 'overnight']),
|
|
80
|
+
carrierServiceCode: z.string().optional(),
|
|
81
|
+
package: z.object({
|
|
82
|
+
weightGrams: z.number().gt(0),
|
|
83
|
+
dimensionsCm: z.object({
|
|
84
|
+
length: z.number().gt(0),
|
|
85
|
+
width: z.number().gt(0),
|
|
86
|
+
height: z.number().gt(0),
|
|
87
|
+
}).optional(),
|
|
88
|
+
}),
|
|
89
|
+
handling: z.object({
|
|
90
|
+
fragile: z.boolean().optional(),
|
|
91
|
+
perishables: z.boolean().optional(),
|
|
92
|
+
batteries: z.enum(['NONE', 'LITHIUM_ION', 'LITHIUM_METAL']).optional(),
|
|
93
|
+
}).optional(),
|
|
94
|
+
cod: z.object({
|
|
95
|
+
amount: MoneySchema,
|
|
96
|
+
reference: z.string().optional(),
|
|
97
|
+
}).optional(),
|
|
98
|
+
declaredValue: MoneySchema.optional(),
|
|
99
|
+
insurance: z.object({
|
|
100
|
+
amount: MoneySchema,
|
|
101
|
+
}).optional(),
|
|
102
|
+
references: z.object({
|
|
103
|
+
orderId: z.string().optional(),
|
|
104
|
+
customerReference: z.string().optional(),
|
|
105
|
+
}).optional(),
|
|
106
|
+
items: z.array(z.object({
|
|
107
|
+
sku: z.string().optional(),
|
|
108
|
+
quantity: z.number().gt(0),
|
|
109
|
+
description: z.string().optional(),
|
|
110
|
+
weight: z.number().optional(),
|
|
111
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
112
|
+
})).optional(),
|
|
113
|
+
status: z.enum(['draft', 'created', 'closed', 'label_generated', 'shipped', 'delivered', 'exception']).optional(),
|
|
114
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
115
|
+
createdAt: z.date().optional(),
|
|
116
|
+
updatedAt: z.date().optional(),
|
|
117
|
+
}).strict();
|
|
118
|
+
/**
|
|
119
|
+
* Foxpost package size schema (used in both HD and APM)
|
|
120
|
+
*/
|
|
121
|
+
const FoxpostPackageSizeSchema = z.enum(['XS', 'S', 'M', 'L', 'XL', '1', '2', '3', '4', '5']);
|
|
122
|
+
/**
|
|
123
|
+
* Hungarian phone number schema (mobile only, +36 or 36 prefix)
|
|
124
|
+
*/
|
|
125
|
+
const HungarianPhoneSchema = z.string().regex(/^(\+36|36)(20|30|31|70|50|51)\d{7}$/, 'Phone must be Hungarian mobile');
|
|
126
|
+
/**
|
|
127
|
+
* Credentials schema - requires apiKey, basicUsername, and basicPassword all together
|
|
128
|
+
*/
|
|
129
|
+
const FoxpostCredentialsSchema = z.object({
|
|
130
|
+
apiKey: z.string().min(1, 'API key is required'),
|
|
131
|
+
basicUsername: z.string().min(1, 'Basic auth username is required'),
|
|
132
|
+
basicPassword: z.string().min(1, 'Basic auth password is required'),
|
|
133
|
+
}).strict();
|
|
134
|
+
/**
|
|
135
|
+
* Home Delivery parcel schema with discriminator
|
|
136
|
+
*/
|
|
137
|
+
const FoxpostParcelHDSchema = z.object({
|
|
138
|
+
type: z.literal('HD'),
|
|
139
|
+
cod: z.number().int().min(0).max(1000000).optional().default(0),
|
|
140
|
+
comment: z.string().max(50).optional(),
|
|
141
|
+
deliveryNote: z.string().optional(),
|
|
142
|
+
fragile: z.boolean().optional().default(false),
|
|
143
|
+
label: z.boolean().optional(),
|
|
144
|
+
recipientAddress: z.string().min(1).max(150),
|
|
145
|
+
recipientCity: z.string().min(1).max(50),
|
|
146
|
+
recipientCountry: z.string().optional(),
|
|
147
|
+
recipientEmail: z.email(),
|
|
148
|
+
recipientName: z.string().min(1).max(150),
|
|
149
|
+
recipientPhone: HungarianPhoneSchema,
|
|
150
|
+
recipientZip: z.string().min(1).max(4),
|
|
151
|
+
refCode: z.string().max(30).optional(),
|
|
152
|
+
size: FoxpostPackageSizeSchema,
|
|
153
|
+
}).strict();
|
|
154
|
+
/**
|
|
155
|
+
* APM (Automated Parcel Machine) parcel schema with discriminator
|
|
156
|
+
*/
|
|
157
|
+
const FoxpostParcelAPMSchema = z.object({
|
|
158
|
+
type: z.literal('APM'),
|
|
159
|
+
cod: z.number().int().min(0).max(1000000).optional().default(0),
|
|
160
|
+
comment: z.string().max(50).optional(),
|
|
161
|
+
destination: z.string().min(1),
|
|
162
|
+
label: z.boolean().optional(),
|
|
163
|
+
recipientEmail: z.email(),
|
|
164
|
+
recipientName: z.string().min(1).max(150),
|
|
165
|
+
recipientPhone: HungarianPhoneSchema,
|
|
166
|
+
refCode: z.string().max(30).optional(),
|
|
167
|
+
size: FoxpostPackageSizeSchema,
|
|
168
|
+
uniqueBarcode: z.string().min(4).max(20).regex(/^(?=.*[a-zA-Z].*[a-zA-Z].*[a-zA-Z].*[a-zA-Z])(?=.*\d.*\d.*\d.*\d)/).optional(),
|
|
169
|
+
}).strict();
|
|
170
|
+
/**
|
|
171
|
+
* Discriminated union of HD and APM parcel types
|
|
172
|
+
*/
|
|
173
|
+
export const FoxpostParcelSchema = z.discriminatedUnion('type', [
|
|
174
|
+
FoxpostParcelHDSchema,
|
|
175
|
+
FoxpostParcelAPMSchema
|
|
176
|
+
]);
|
|
177
|
+
/**
|
|
178
|
+
* CreateParcelRequest Zod schema
|
|
179
|
+
*/
|
|
180
|
+
export const CreateParcelRequestFoxpostSchema = z.object({
|
|
181
|
+
parcel: ParcelSchema,
|
|
182
|
+
credentials: FoxpostCredentialsSchema,
|
|
183
|
+
options: z.object({
|
|
184
|
+
useTestApi: z.boolean().optional()
|
|
185
|
+
}).optional()
|
|
186
|
+
});
|
|
187
|
+
export const CreateParcelsRequestFoxpostSchema = z.object({
|
|
188
|
+
parcels: z.array(ParcelSchema).min(1),
|
|
189
|
+
credentials: FoxpostCredentialsSchema,
|
|
190
|
+
options: z.object({
|
|
191
|
+
useTestApi: z.boolean().optional()
|
|
192
|
+
}).optional()
|
|
193
|
+
});
|
|
194
|
+
export const CreateLabelRequestFoxpostSchema = z.object({
|
|
195
|
+
parcelCarrierId: z.string().min(1, 'Parcel carrier ID is required'),
|
|
196
|
+
credentials: FoxpostCredentialsSchema,
|
|
197
|
+
options: z.object({
|
|
198
|
+
useTestApi: z.boolean().optional(),
|
|
199
|
+
size: z.enum(['A6', 'A7', '_85X85']).default('A7'),
|
|
200
|
+
foxpost: z.object({
|
|
201
|
+
startPos: z.number().int().min(0).max(7).optional(),
|
|
202
|
+
isPortrait: z.boolean().optional().default(false),
|
|
203
|
+
}).optional(),
|
|
204
|
+
}).optional()
|
|
205
|
+
});
|
|
206
|
+
/**
|
|
207
|
+
* Batch label request schema
|
|
208
|
+
* Similar to CreateParcelsRequest but for labels
|
|
209
|
+
*/
|
|
210
|
+
export const CreateLabelsRequestFoxpostSchema = z.object({
|
|
211
|
+
parcelCarrierIds: z.array(z.string().min(1)).min(1, 'At least one parcel ID is required'),
|
|
212
|
+
credentials: FoxpostCredentialsSchema,
|
|
213
|
+
options: z.object({
|
|
214
|
+
useTestApi: z.boolean().optional(),
|
|
215
|
+
size: z.enum(['A6', 'A7', '_85X85']).default('A7'),
|
|
216
|
+
foxpost: z.object({
|
|
217
|
+
startPos: z.number().int().min(0).max(7).optional(),
|
|
218
|
+
isPortrait: z.boolean().optional().default(false),
|
|
219
|
+
}).optional(),
|
|
220
|
+
}).optional()
|
|
221
|
+
});
|
|
222
|
+
/**
|
|
223
|
+
* TrackingRequest Zod schema
|
|
224
|
+
*/
|
|
225
|
+
export const TrackingRequestFoxpostSchema = z.object({
|
|
226
|
+
trackingNumber: z.string().min(1, 'Tracking number is required'),
|
|
227
|
+
credentials: FoxpostCredentialsSchema,
|
|
228
|
+
options: z.object({
|
|
229
|
+
useTestApi: z.boolean().optional()
|
|
230
|
+
}).optional()
|
|
231
|
+
});
|
|
232
|
+
/**
|
|
233
|
+
* Helper to validate and extract credentials from a request
|
|
234
|
+
* Throws ZodError if validation fails
|
|
235
|
+
*/
|
|
236
|
+
export function validateFoxpostCredentials(credentials) {
|
|
237
|
+
return FoxpostCredentialsSchema.parse(credentials);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Helper to safely validate credentials without throwing
|
|
241
|
+
* Returns { success: true, data } or { success: false, error }
|
|
242
|
+
*/
|
|
243
|
+
export function safeValidateFoxpostCredentials(credentials) {
|
|
244
|
+
return FoxpostCredentialsSchema.safeParse(credentials);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Helper to validate a CreateParcelRequest
|
|
248
|
+
* Returns parsed and validated request or throws ZodError
|
|
249
|
+
*/
|
|
250
|
+
export function validateCreateParcelRequest(req) {
|
|
251
|
+
return CreateParcelRequestFoxpostSchema.parse(req);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Helper to validate a CreateParcelsRequest
|
|
255
|
+
* Returns parsed and validated request or throws ZodError
|
|
256
|
+
*/
|
|
257
|
+
export function validateCreateParcelsRequest(req) {
|
|
258
|
+
return CreateParcelsRequestFoxpostSchema.parse(req);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Helper to safely validate without throwing
|
|
262
|
+
* Returns { success: true, data } or { success: false, error }
|
|
263
|
+
*/
|
|
264
|
+
export function safeValidateCreateParcelRequest(req) {
|
|
265
|
+
return CreateParcelRequestFoxpostSchema.safeParse(req);
|
|
266
|
+
}
|
|
267
|
+
export function safeValidateCreateParcelsRequest(req) {
|
|
268
|
+
return CreateParcelsRequestFoxpostSchema.safeParse(req);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Helper to safely validate a CreateLabelRequest without throwing
|
|
272
|
+
* Returns { success: true, data } or { success: false, error }
|
|
273
|
+
*/
|
|
274
|
+
export function safeValidateCreateLabelRequest(req) {
|
|
275
|
+
return CreateLabelRequestFoxpostSchema.safeParse(req);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Helper to safely validate a CreateLabelsRequest without throwing
|
|
279
|
+
* Returns { success: true, data } or { success: false, error }
|
|
280
|
+
*/
|
|
281
|
+
export function safeValidateCreateLabelsRequest(req) {
|
|
282
|
+
return CreateLabelsRequestFoxpostSchema.safeParse(req);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Helper to safely validate a TrackingRequest without throwing
|
|
286
|
+
* Returns { success: true, data } or { success: false, error }
|
|
287
|
+
*/
|
|
288
|
+
export function safeValidateTrackingRequest(req) {
|
|
289
|
+
return TrackingRequestFoxpostSchema.safeParse(req);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Validate a Foxpost parcel payload (after mapping from canonical Parcel)
|
|
293
|
+
* Used to catch mapping errors before sending to the carrier
|
|
294
|
+
*/
|
|
295
|
+
export function validateFoxpostParcel(parcel) {
|
|
296
|
+
return FoxpostParcelSchema.parse(parcel);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Safely validate a Foxpost parcel payload without throwing
|
|
300
|
+
* Returns { success: true, data } or { success: false, error }
|
|
301
|
+
*/
|
|
302
|
+
export function safeValidateFoxpostParcel(parcel) {
|
|
303
|
+
return FoxpostParcelSchema.safeParse(parcel);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* ============================================================================
|
|
307
|
+
* Foxpost Tracking Schemas (from OpenAPI /api/tracking/{barcode})
|
|
308
|
+
* ============================================================================
|
|
309
|
+
*/
|
|
310
|
+
/**
|
|
311
|
+
* Trace enum from OpenAPI Trace.status
|
|
312
|
+
*/
|
|
313
|
+
const TraceStatusEnum = z.enum([
|
|
314
|
+
'CREATE',
|
|
315
|
+
'OPERIN',
|
|
316
|
+
'OPEROUT',
|
|
317
|
+
'RECEIVE',
|
|
318
|
+
'RETURN',
|
|
319
|
+
'REDIRECT',
|
|
320
|
+
'OVERTIMEOUT',
|
|
321
|
+
'SORTIN',
|
|
322
|
+
'SORTOUT',
|
|
323
|
+
'SLOTCHANGE',
|
|
324
|
+
'OVERTIMED',
|
|
325
|
+
'MPSIN',
|
|
326
|
+
'C2CIN',
|
|
327
|
+
'HDSENT',
|
|
328
|
+
'HDDEPO',
|
|
329
|
+
'HDINTRANSIT',
|
|
330
|
+
'HDRETURN',
|
|
331
|
+
'HDRECEIVE',
|
|
332
|
+
'WBXREDIRECT',
|
|
333
|
+
'BACKTOSENDER',
|
|
334
|
+
'HDHUBIN',
|
|
335
|
+
'HDHUBOUT',
|
|
336
|
+
'HDCOURIER',
|
|
337
|
+
'HDUNDELIVERABLE',
|
|
338
|
+
'PREPAREDFORPD',
|
|
339
|
+
'INWAREHOUSE',
|
|
340
|
+
'COLLECTSENT',
|
|
341
|
+
'C2BIN',
|
|
342
|
+
'RETURNED',
|
|
343
|
+
'COLLECTED',
|
|
344
|
+
'BACKLOGINFULL',
|
|
345
|
+
'BACKLOGINFAIL',
|
|
346
|
+
'MISSORT',
|
|
347
|
+
'EMPTYSLOT',
|
|
348
|
+
'RESENT',
|
|
349
|
+
'PREREDIRECT',
|
|
350
|
+
]);
|
|
351
|
+
/**
|
|
352
|
+
* Trace schema (from OpenAPI components/schemas/Trace)
|
|
353
|
+
*
|
|
354
|
+
* Uses lenient validation with coercion to handle real API responses:
|
|
355
|
+
* - statusDate: coerces various date string formats to Date objects
|
|
356
|
+
* - statusStationId: coerces numbers to strings (API inconsistency)
|
|
357
|
+
* - Other fields: optional, pass through extra fields
|
|
358
|
+
*/
|
|
359
|
+
const TraceSchema = z.object({
|
|
360
|
+
statusDate: z.string()
|
|
361
|
+
.refine((s) => {
|
|
362
|
+
// Accept any string that looks like a date
|
|
363
|
+
// Covers: ISO with timezone, ISO without timezone, other formats
|
|
364
|
+
const date = new Date(s);
|
|
365
|
+
return !isNaN(date.getTime());
|
|
366
|
+
}, "Invalid date format")
|
|
367
|
+
.transform(s => new Date(s)),
|
|
368
|
+
statusStationId: z.union([
|
|
369
|
+
z.string(),
|
|
370
|
+
z.number().transform(n => String(n)),
|
|
371
|
+
]).optional(),
|
|
372
|
+
shortName: z.string().optional(),
|
|
373
|
+
longName: z.string().optional(),
|
|
374
|
+
status: TraceStatusEnum.optional(),
|
|
375
|
+
}).loose(); // Allow extra fields from API
|
|
376
|
+
/**
|
|
377
|
+
* TrackDTO schema (from OpenAPI components/schemas/TrackDTO)
|
|
378
|
+
* Note: TrackDTO mirrors Trace but with trackId and slightly different names
|
|
379
|
+
*/
|
|
380
|
+
const TrackDTOSchema = z.object({
|
|
381
|
+
trackId: z.number().int().optional(),
|
|
382
|
+
status: z.string().optional(),
|
|
383
|
+
statusDate: z.string().datetime().or(z.string()).transform(s => new Date(s)),
|
|
384
|
+
}).strict();
|
|
385
|
+
/**
|
|
386
|
+
* Parcel type enum from OpenAPI Tracking.parcelType
|
|
387
|
+
*/
|
|
388
|
+
const FoxpostParcelTypeEnum = z.enum(['NORMAL', 'RE', 'XRE', 'IRE', 'C2B']);
|
|
389
|
+
/**
|
|
390
|
+
* Send type enum from OpenAPI Tracking.sendType
|
|
391
|
+
*/
|
|
392
|
+
const FoxpostSendTypeEnum = z.enum(['APM', 'HD', 'COLLECT']);
|
|
393
|
+
/**
|
|
394
|
+
* Tracking schema (from OpenAPI components/schemas/Tracking)
|
|
395
|
+
* This is the response from GET /api/tracking/{barcode}
|
|
396
|
+
*
|
|
397
|
+
* Uses lenient validation to handle API quirks:
|
|
398
|
+
* - All fields optional
|
|
399
|
+
* - estimatedDelivery can be null or string
|
|
400
|
+
* - Passes through extra fields from API
|
|
401
|
+
*/
|
|
402
|
+
const FoxpostTrackingSchema = z.object({
|
|
403
|
+
clFox: z.string().optional(),
|
|
404
|
+
parcelType: FoxpostParcelTypeEnum.optional(),
|
|
405
|
+
sendType: FoxpostSendTypeEnum.optional(),
|
|
406
|
+
traces: z.array(TraceSchema).optional(),
|
|
407
|
+
relatedParcel: z.string().nullable().optional(),
|
|
408
|
+
estimatedDelivery: z.string().nullable().optional(),
|
|
409
|
+
}).loose(); // Allow extra fields from API
|
|
410
|
+
/**
|
|
411
|
+
* Helper to validate a Foxpost tracking response
|
|
412
|
+
* Throws ZodError if validation fails
|
|
413
|
+
*/
|
|
414
|
+
export function validateFoxpostTracking(res) {
|
|
415
|
+
return FoxpostTrackingSchema.parse(res);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Helper to safely validate a Foxpost tracking response without throwing
|
|
419
|
+
* Returns { success: true, data } or { success: false, error }
|
|
420
|
+
*/
|
|
421
|
+
export function safeValidateFoxpostTracking(res) {
|
|
422
|
+
return FoxpostTrackingSchema.safeParse(res);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* ============================================================================
|
|
426
|
+
* Foxpost Parcel Creation Request/Response Schemas (from OpenAPI /api/parcel)
|
|
427
|
+
* ============================================================================
|
|
428
|
+
*
|
|
429
|
+
* Schemas for request body and response shapes, modeled after Foxpost OpenAPI
|
|
430
|
+
* but kept lenient to handle real-world API quirks and future changes.
|
|
431
|
+
*/
|
|
432
|
+
/**
|
|
433
|
+
* FieldError schema (from OpenAPI components/schemas/FieldError)
|
|
434
|
+
* Represents a validation error on a specific field
|
|
435
|
+
* Uses lenient validation to accept any field name or error code
|
|
436
|
+
*/
|
|
437
|
+
const FieldErrorSchema = z.object({
|
|
438
|
+
field: z.string().optional(),
|
|
439
|
+
message: z.string().optional(),
|
|
440
|
+
}).loose(); // Allow extra fields (e.g., error codes)
|
|
441
|
+
/**
|
|
442
|
+
* Package schema (from OpenAPI components/schemas/Package)
|
|
443
|
+
* Represents a single parcel result in the CreateResponse
|
|
444
|
+
*
|
|
445
|
+
* Uses lenient validation:
|
|
446
|
+
* - All fields optional to handle partial responses
|
|
447
|
+
* - Passes through extra fields from carrier
|
|
448
|
+
* - Lenient with field types (clFoxId may be string or number fallback)
|
|
449
|
+
*/
|
|
450
|
+
const PackageSchema = z.object({
|
|
451
|
+
clFoxId: z.union([
|
|
452
|
+
z.string(),
|
|
453
|
+
z.number().transform(n => String(n)),
|
|
454
|
+
]).optional(),
|
|
455
|
+
barcode: z.string().nullable().optional(), // Fallback name for barcode
|
|
456
|
+
newBarcode: z.string().nullable().optional(), // Alternative barcode field
|
|
457
|
+
refCode: z.string().nullable().optional(),
|
|
458
|
+
uniqueBarcode: z.string().nullable().optional(),
|
|
459
|
+
errors: z.array(FieldErrorSchema).nullable().optional(),
|
|
460
|
+
}).loose(); // Allow extra fields from API
|
|
461
|
+
/**
|
|
462
|
+
* CreateResponse schema (from OpenAPI components/schemas/CreateResponse)
|
|
463
|
+
* Response from POST /api/parcel
|
|
464
|
+
*
|
|
465
|
+
* Uses lenient validation:
|
|
466
|
+
* - valid: boolean indicating if all parcels succeeded or not
|
|
467
|
+
* - parcels: array of Package results
|
|
468
|
+
* - errors: top-level validation errors (optional)
|
|
469
|
+
* - Passes through extra fields for extensibility
|
|
470
|
+
*/
|
|
471
|
+
const CreateResponseSchema = z.object({
|
|
472
|
+
valid: z.boolean().optional(),
|
|
473
|
+
parcels: z.array(PackageSchema).optional(),
|
|
474
|
+
errors: z.array(FieldErrorSchema).optional(),
|
|
475
|
+
}).loose(); // Allow extra fields from API
|
|
476
|
+
/**
|
|
477
|
+
* CreateParcelRequest schema (from OpenAPI components/schemas/CreateParcelRequest)
|
|
478
|
+
* Request body for POST /api/parcel (single or batch)
|
|
479
|
+
*
|
|
480
|
+
* Uses lenient validation:
|
|
481
|
+
* - Required: recipientName, recipientEmail, recipientPhone (for all parcels)
|
|
482
|
+
* - HD parcels: also require recipientCity, recipientZip, recipientAddress
|
|
483
|
+
* - APM parcels: also require destination
|
|
484
|
+
* - Optional fields accept any string/value, not strict enums
|
|
485
|
+
* - size defaults to 's' if missing
|
|
486
|
+
* - cod: coerced to number, defaults to 0
|
|
487
|
+
* - Passes through extra fields for future extensibility
|
|
488
|
+
*/
|
|
489
|
+
const CreateParcelRequestItemSchema = z.object({
|
|
490
|
+
// Required recipient contact fields
|
|
491
|
+
recipientName: z.string().max(150).optional(),
|
|
492
|
+
recipientEmail: z.string().optional(),
|
|
493
|
+
recipientPhone: z.string().optional(),
|
|
494
|
+
// Optional contact fields
|
|
495
|
+
recipientCountry: z.string().max(3).optional(),
|
|
496
|
+
// HD (Home Delivery) address fields - optional at schema level
|
|
497
|
+
// (validation logic in parcels.ts checks that either all or none are present)
|
|
498
|
+
recipientCity: z.string().max(50).optional(),
|
|
499
|
+
recipientZip: z.string().optional(),
|
|
500
|
+
recipientAddress: z.string().max(150).optional(),
|
|
501
|
+
// APM delivery field
|
|
502
|
+
destination: z.string().optional(),
|
|
503
|
+
// Parcel details
|
|
504
|
+
size: z.union([
|
|
505
|
+
z.enum(['XS', 'S', 'M', 'L', 'XL']),
|
|
506
|
+
z.enum(['xs', 's', 'm', 'l', 'xl']).transform(s => s.toUpperCase()),
|
|
507
|
+
z.string(), // Lenient: accept any string size
|
|
508
|
+
]).optional().default('S'),
|
|
509
|
+
cod: z.union([
|
|
510
|
+
z.number().int().min(0).max(1000000),
|
|
511
|
+
z.string().transform(s => parseInt(s, 10)).refine(n => !Number.isNaN(n) && n >= 0 && n <= 1000000),
|
|
512
|
+
]).optional().default(0),
|
|
513
|
+
// Optional fields - lenient, any string accepted
|
|
514
|
+
comment: z.string().max(50).optional(),
|
|
515
|
+
deliveryNote: z.string().optional(),
|
|
516
|
+
label: z.boolean().optional(),
|
|
517
|
+
fragile: z.boolean().optional(),
|
|
518
|
+
refCode: z.string().max(30).optional(),
|
|
519
|
+
uniqueBarcode: z.string().max(20).optional(),
|
|
520
|
+
// Extra fields for future extensibility
|
|
521
|
+
}).loose();
|
|
522
|
+
/**
|
|
523
|
+
* Helper to validate a Foxpost CreateResponse
|
|
524
|
+
* Throws ZodError if validation fails
|
|
525
|
+
*/
|
|
526
|
+
export function validateFoxpostCreateResponse(res) {
|
|
527
|
+
return CreateResponseSchema.parse(res);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Helper to safely validate a Foxpost CreateResponse without throwing
|
|
531
|
+
* Returns { success: true, data } or { success: false, error }
|
|
532
|
+
*/
|
|
533
|
+
export function safeValidateFoxpostCreateResponse(res) {
|
|
534
|
+
return CreateResponseSchema.safeParse(res);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Helper to validate a Foxpost Package entry
|
|
538
|
+
* Throws ZodError if validation fails
|
|
539
|
+
*/
|
|
540
|
+
export function validateFoxpostPackage(pkg) {
|
|
541
|
+
return PackageSchema.parse(pkg);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Helper to safely validate a Foxpost Package entry without throwing
|
|
545
|
+
* Returns { success: true, data } or { success: false, error }
|
|
546
|
+
*/
|
|
547
|
+
export function safeValidateFoxpostPackage(pkg) {
|
|
548
|
+
return PackageSchema.safeParse(pkg);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Helper to validate a single Foxpost CreateParcelRequest item
|
|
552
|
+
* Throws ZodError if validation fails
|
|
553
|
+
*/
|
|
554
|
+
export function validateFoxpostCreateParcelRequestItem(item) {
|
|
555
|
+
return CreateParcelRequestItemSchema.parse(item);
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Helper to safely validate a Foxpost CreateParcelRequest item without throwing
|
|
559
|
+
* Returns { success: true, data } or { success: false, error }
|
|
560
|
+
*/
|
|
561
|
+
export function safeValidateFoxpostCreateParcelRequestItem(item) {
|
|
562
|
+
return CreateParcelRequestItemSchema.safeParse(item);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* ============================================================================
|
|
566
|
+
* Foxpost Label Generation Schemas (from OpenAPI /api/label/{pageSize})
|
|
567
|
+
* ============================================================================
|
|
568
|
+
*
|
|
569
|
+
* Schemas for label generation request parameters, response metadata,
|
|
570
|
+
* error handling, and label info (from /api/label/info/{barcode})
|
|
571
|
+
*/
|
|
572
|
+
/**
|
|
573
|
+
* ApiError schema (from OpenAPI components/schemas/ApiError)
|
|
574
|
+
* Carrier returns this on error responses (400, 401, etc.)
|
|
575
|
+
* Uses lenient validation to accept any structure
|
|
576
|
+
*/
|
|
577
|
+
const ApiErrorSchema = z.object({
|
|
578
|
+
timestamp: z.string().optional(),
|
|
579
|
+
error: z.string().optional(),
|
|
580
|
+
status: z.number().int().optional(),
|
|
581
|
+
}).loose(); // Allow extra fields from API
|
|
582
|
+
/**
|
|
583
|
+
* Helper to safely validate a Foxpost API error response without throwing
|
|
584
|
+
* Returns { success: true, data } or { success: false, error }
|
|
585
|
+
*/
|
|
586
|
+
export function safeValidateFoxpostApiError(res) {
|
|
587
|
+
return ApiErrorSchema.safeParse(res);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* PDF binary raw response validation
|
|
591
|
+
* Ensures the PDF buffer is non-empty
|
|
592
|
+
* Accepts Node Buffer or any object with numeric byteLength > 0
|
|
593
|
+
*/
|
|
594
|
+
const FoxpostLabelPdfRawSchema = z.any().refine((v) => {
|
|
595
|
+
return v != null && typeof v.byteLength === 'number' && v.byteLength > 0;
|
|
596
|
+
}, { message: 'Expected non-empty PDF buffer' });
|
|
597
|
+
/**
|
|
598
|
+
* Helper to safely validate PDF raw response without throwing
|
|
599
|
+
* Returns { success: true, data } or { success: false, error }
|
|
600
|
+
*/
|
|
601
|
+
export function safeValidateFoxpostLabelPdfRaw(raw) {
|
|
602
|
+
return FoxpostLabelPdfRawSchema.safeParse(raw);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* PDF metadata schema for label generation
|
|
606
|
+
* Describes the PDF file properties and generation options
|
|
607
|
+
*/
|
|
608
|
+
const FoxpostLabelPdfMetadataSchema = z.object({
|
|
609
|
+
size: z.enum(['A6', 'A7', '_85X85']).optional(),
|
|
610
|
+
barcodesCount: z.number().int().min(1).optional(),
|
|
611
|
+
startPos: z.number().int().min(0).max(7).optional(),
|
|
612
|
+
isPortrait: z.boolean().optional(),
|
|
613
|
+
}).loose();
|
|
614
|
+
/**
|
|
615
|
+
* Helper to validate PDF metadata
|
|
616
|
+
* Throws ZodError if validation fails
|
|
617
|
+
*/
|
|
618
|
+
export function validateFoxpostLabelPdfMetadata(metadata) {
|
|
619
|
+
return FoxpostLabelPdfMetadataSchema.parse(metadata);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Helper to safely validate PDF metadata without throwing
|
|
623
|
+
* Returns { success: true, data } or { success: false, error }
|
|
624
|
+
*/
|
|
625
|
+
export function safeValidateFoxpostLabelPdfMetadata(metadata) {
|
|
626
|
+
return FoxpostLabelPdfMetadataSchema.safeParse(metadata);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* LabelInfo schema (from OpenAPI components/schemas/LabelInfo)
|
|
630
|
+
* Response from GET /api/label/info/{barcode}
|
|
631
|
+
* Contains pre-label metadata about the parcel
|
|
632
|
+
*/
|
|
633
|
+
const LabelInfoSchema = z.object({
|
|
634
|
+
senderName: z.string().optional(),
|
|
635
|
+
senderZip: z.string().optional(),
|
|
636
|
+
senderCity: z.string().optional(),
|
|
637
|
+
senderAddress: z.string().optional(),
|
|
638
|
+
recipientName: z.string().optional(),
|
|
639
|
+
recipientEmail: z.string().optional(),
|
|
640
|
+
recipientPhone: z.string().optional(),
|
|
641
|
+
recipientZip: z.string().optional(),
|
|
642
|
+
recipientCity: z.string().optional(),
|
|
643
|
+
recipientAddress: z.string().optional(),
|
|
644
|
+
apm: z.string().optional(),
|
|
645
|
+
cod: z.number().int().optional(),
|
|
646
|
+
isFragile: z.boolean().optional(),
|
|
647
|
+
barcode: z.string().optional(),
|
|
648
|
+
refCode: z.string().optional(),
|
|
649
|
+
depoCode: z.string().optional(),
|
|
650
|
+
courierCode: z.string().optional(),
|
|
651
|
+
sendType: z.enum(['APM', 'HD', 'COLLECT']).optional(),
|
|
652
|
+
}).loose(); // Allow extra fields from API
|
|
653
|
+
/**
|
|
654
|
+
* Helper to validate a Foxpost LabelInfo response
|
|
655
|
+
* Throws ZodError if validation fails
|
|
656
|
+
*/
|
|
657
|
+
export function validateFoxpostLabelInfo(res) {
|
|
658
|
+
return LabelInfoSchema.parse(res);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Helper to safely validate a Foxpost LabelInfo response without throwing
|
|
662
|
+
* Returns { success: true, data } or { success: false, error }
|
|
663
|
+
*/
|
|
664
|
+
export function safeValidateFoxpostLabelInfo(res) {
|
|
665
|
+
return LabelInfoSchema.safeParse(res);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* ============================================================================
|
|
669
|
+
* Foxpost APM/Pickup-Points Schemas (from foxplus.json feed)
|
|
670
|
+
* ============================================================================
|
|
671
|
+
*/
|
|
672
|
+
/**
|
|
673
|
+
* Opening hours schema - Hungarian day names with optional string values
|
|
674
|
+
* Uses passthrough to allow extra fields and lenient validation
|
|
675
|
+
*/
|
|
676
|
+
const OpeningHoursSchema = z.object({
|
|
677
|
+
hetfo: z.string().optional().nullable(),
|
|
678
|
+
kedd: z.string().optional().nullable(),
|
|
679
|
+
szerda: z.string().optional().nullable(),
|
|
680
|
+
csutortok: z.string().optional().nullable(),
|
|
681
|
+
pentek: z.string().optional().nullable(),
|
|
682
|
+
szombat: z.string().optional().nullable(),
|
|
683
|
+
vasarnap: z.string().optional().nullable(),
|
|
684
|
+
}).loose(); // Allow extra fields
|
|
685
|
+
/**
|
|
686
|
+
* Fill/empty schedule entry
|
|
687
|
+
*/
|
|
688
|
+
const FillEmptyEntrySchema = z.object({
|
|
689
|
+
emptying: z.string().optional(),
|
|
690
|
+
filling: z.string().optional(),
|
|
691
|
+
}).loose();
|
|
692
|
+
/**
|
|
693
|
+
* Foxpost APM substitute entry
|
|
694
|
+
* When a pickup point is closed or unavailable, these are alternative locations
|
|
695
|
+
* where the customer can pick up/drop off their parcel.
|
|
696
|
+
*/
|
|
697
|
+
const FoxpostSubstituteSchema = z.object({
|
|
698
|
+
place_id: z.union([
|
|
699
|
+
z.string(),
|
|
700
|
+
z.number().transform(n => String(n)),
|
|
701
|
+
]),
|
|
702
|
+
operator_id: z.string().optional().nullable(),
|
|
703
|
+
}).loose(); // Allow extra fields for future extensibility
|
|
704
|
+
/**
|
|
705
|
+
* Foxpost APM metadata schema - carrier-specific fields
|
|
706
|
+
* Uses lenient validation with passthrough for future extensibility
|
|
707
|
+
*/
|
|
708
|
+
const FoxpostApmMetadataSchema = z.object({
|
|
709
|
+
depot: z.string().optional(),
|
|
710
|
+
load: z.string().optional(), // Lenient: accept any string, not just predefined enum values
|
|
711
|
+
apmType: z.string().optional(), // Lenient: accept any string (Cleveron, Keba, Rollkon, Rotte, Z-BOX, Z-Pont, etc.)
|
|
712
|
+
substitutes: z.array(FoxpostSubstituteSchema).optional(), // Array of substitute APM objects
|
|
713
|
+
variant: z.string().optional(), // Lenient: accept any string
|
|
714
|
+
fillEmptyList: z.array(FillEmptyEntrySchema).optional(),
|
|
715
|
+
ssapt: z.string().optional(),
|
|
716
|
+
sdapt: z.string().optional(),
|
|
717
|
+
}).loose();
|
|
718
|
+
const FoxpostApmEntrySchema = z.object({
|
|
719
|
+
place_id: z.union([
|
|
720
|
+
z.string(),
|
|
721
|
+
z.number().transform(n => String(n)),
|
|
722
|
+
]),
|
|
723
|
+
operator_id: z.string().optional().nullable(),
|
|
724
|
+
name: z.string().optional(),
|
|
725
|
+
ssapt: z.string().optional(),
|
|
726
|
+
sdapt: z.string().optional(),
|
|
727
|
+
country: z.string().optional(),
|
|
728
|
+
address: z.string().optional(),
|
|
729
|
+
zip: z.string().optional(),
|
|
730
|
+
city: z.string().optional(),
|
|
731
|
+
street: z.string().optional(),
|
|
732
|
+
findme: z.string().optional(),
|
|
733
|
+
geolat: z.union([
|
|
734
|
+
z.number(),
|
|
735
|
+
z.string().transform(s => parseFloat(s)),
|
|
736
|
+
]).optional().refine(n => n === undefined || !Number.isNaN(n), 'Latitude must be a valid number'),
|
|
737
|
+
geolng: z.union([
|
|
738
|
+
z.number(),
|
|
739
|
+
z.string().transform(s => parseFloat(s)),
|
|
740
|
+
]).optional().refine(n => n === undefined || !Number.isNaN(n), 'Longitude must be a valid number'),
|
|
741
|
+
allowed2: z.enum(['ALL', 'C2C', 'B2C']).optional(),
|
|
742
|
+
depot: z.string().optional(),
|
|
743
|
+
load: z.string().optional(), // Lenient: accept any string (e.g., "normal loaded", "custom value")
|
|
744
|
+
isOutdoor: z.boolean().optional(),
|
|
745
|
+
apmType: z.string().optional(), // Lenient: accept any string (Foxpost types: Cleveron/Keba/Rollkon/Rotte, Packeta types: Z-BOX/Z-Pont, etc.)
|
|
746
|
+
substitutes: z.array(FoxpostSubstituteSchema).optional(), // Array of substitute APM objects
|
|
747
|
+
open: OpeningHoursSchema.optional(),
|
|
748
|
+
fillEmptyList: z.array(FillEmptyEntrySchema).optional(),
|
|
749
|
+
cardPayment: z.boolean().optional(),
|
|
750
|
+
cashPayment: z.boolean().optional(),
|
|
751
|
+
iconUrl: z.string().optional(),
|
|
752
|
+
variant: z.string().optional(), // Lenient: accept any string (e.g., FOXPOST A-BOX, Packeta Z-BOX, custom types)
|
|
753
|
+
paymentOptions: z.array(z.string()).optional(), // Lenient: accept any payment method string
|
|
754
|
+
paymentOptionsString: z.string().optional(),
|
|
755
|
+
service: z.array(z.string()).optional(), // Lenient: accept any service string (pickup, dispatch, or others)
|
|
756
|
+
serviceString: z.string().optional(),
|
|
757
|
+
}).loose(); // Allow extra fields from API
|
|
758
|
+
/**
|
|
759
|
+
* Foxpost-specific options for fetchPickupPoints.
|
|
760
|
+
*
|
|
761
|
+
* Carrier-specific filters are namespaced under `options.foxpost`.
|
|
762
|
+
*/
|
|
763
|
+
const FoxpostFetchPickupPointsCarrierOptionsSchema = z.object({
|
|
764
|
+
country: z.string().length(2).optional(),
|
|
765
|
+
bbox: z.object({
|
|
766
|
+
north: z.number(),
|
|
767
|
+
south: z.number(),
|
|
768
|
+
east: z.number(),
|
|
769
|
+
west: z.number(),
|
|
770
|
+
}).optional(),
|
|
771
|
+
updatedSince: z.union([z.string(), z.date()]).optional(),
|
|
772
|
+
}).catchall(z.unknown());
|
|
773
|
+
const FoxpostFetchPickupPointsOptionsSchema = z.object({
|
|
774
|
+
foxpost: FoxpostFetchPickupPointsCarrierOptionsSchema.optional(),
|
|
775
|
+
}).catchall(z.unknown());
|
|
776
|
+
const FoxpostFetchPickupPointsRequestSchema = z.object({
|
|
777
|
+
credentials: z.record(z.string(), z.unknown()).optional(),
|
|
778
|
+
options: FoxpostFetchPickupPointsOptionsSchema.optional(),
|
|
779
|
+
});
|
|
780
|
+
/**
|
|
781
|
+
* Helper to safely validate a Foxpost APM feed (array of entries)
|
|
782
|
+
* Returns { success: true, data } or { success: false, error }
|
|
783
|
+
*/
|
|
784
|
+
export function safeValidateFoxpostApmFeed(feed) {
|
|
785
|
+
return z.array(FoxpostApmEntrySchema).safeParse(feed);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Helper to safely validate a single Foxpost APM entry
|
|
789
|
+
* Returns { success: true, data } or { success: false, error }
|
|
790
|
+
*/
|
|
791
|
+
export function safeValidateFoxpostApmEntry(entry) {
|
|
792
|
+
return FoxpostApmEntrySchema.safeParse(entry);
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Helper to validate Foxpost fetchPickupPoints request envelope.
|
|
796
|
+
*/
|
|
797
|
+
export function safeValidateFetchPickupPointsRequest(input) {
|
|
798
|
+
return FoxpostFetchPickupPointsRequestSchema.safeParse(input);
|
|
799
|
+
}
|