@theelena/sdk 0.1.0 → 0.1.2
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 +6 -8
- package/dist/agent.d.ts +3048 -0
- package/dist/agent.js +1786 -0
- package/dist/agent.js.map +1 -0
- package/dist/index.d.ts +901 -0
- package/dist/index.js +1766 -0
- package/dist/index.js.map +1 -0
- package/dist/key-transport.d-Bopgxw9r.d.ts +1487 -0
- package/guides/external-agents.md +1 -1
- package/package.json +1 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,1766 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var ElenaApiError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
details;
|
|
6
|
+
retryAfter;
|
|
7
|
+
constructor(message, details) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "ElenaApiError";
|
|
10
|
+
this.status = details.status;
|
|
11
|
+
this.code = details.code;
|
|
12
|
+
this.details = details.details;
|
|
13
|
+
this.retryAfter = details.retryAfter;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var ElenaNetworkError = class extends Error {
|
|
17
|
+
constructor(message = "Error de red desconocido") {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "ElenaNetworkError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var ElenaTimeoutError = class extends Error {
|
|
23
|
+
timeoutMs;
|
|
24
|
+
constructor(timeoutMs) {
|
|
25
|
+
super(`Elena no respondi\xF3 en ${timeoutMs} ms.`);
|
|
26
|
+
this.name = "ElenaTimeoutError";
|
|
27
|
+
this.timeoutMs = timeoutMs;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var ElenaRequestAbortedError = class extends Error {
|
|
31
|
+
constructor(message = "La solicitud a Elena fue cancelada.") {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "ElenaRequestAbortedError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var ElenaResponseError = class extends Error {
|
|
37
|
+
status;
|
|
38
|
+
bodyText;
|
|
39
|
+
constructor(message, details) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "ElenaResponseError";
|
|
42
|
+
this.status = details.status;
|
|
43
|
+
this.bodyText = details.bodyText;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var ElenaCheckoutValidationError = class extends Error {
|
|
47
|
+
code;
|
|
48
|
+
/** Selection path, e.g. selections[0].quantity or selections[0].selectedOptions. */
|
|
49
|
+
field;
|
|
50
|
+
constructor(message, details) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "ElenaCheckoutValidationError";
|
|
53
|
+
this.code = details.code;
|
|
54
|
+
this.field = details.field;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/resources/orders.ts
|
|
59
|
+
var AccessResource = class {
|
|
60
|
+
constructor(http, businessSlug) {
|
|
61
|
+
this.http = http;
|
|
62
|
+
this.businessSlug = businessSlug;
|
|
63
|
+
}
|
|
64
|
+
http;
|
|
65
|
+
businessSlug;
|
|
66
|
+
resolve(publicKey) {
|
|
67
|
+
return this.http.request(
|
|
68
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/access/${encodeURIComponent(publicKey)}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
createOrder(publicKey, input, options = {}) {
|
|
72
|
+
const body = options.idempotencyKey && !input.idempotencyKey ? { ...input, idempotencyKey: options.idempotencyKey } : input;
|
|
73
|
+
return this.http.request(
|
|
74
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/access/${encodeURIComponent(publicKey)}/orders`,
|
|
75
|
+
{ method: "POST", body, signal: options.signal }
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var OrdersResource = class {
|
|
80
|
+
constructor(http, businessSlug) {
|
|
81
|
+
this.http = http;
|
|
82
|
+
this.businessSlug = businessSlug;
|
|
83
|
+
}
|
|
84
|
+
http;
|
|
85
|
+
businessSlug;
|
|
86
|
+
create(input, options = {}) {
|
|
87
|
+
const body = options.idempotencyKey && !input.idempotencyKey ? { ...input, idempotencyKey: options.idempotencyKey } : input;
|
|
88
|
+
return this.http.request(
|
|
89
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/orders`,
|
|
90
|
+
{ method: "POST", body, signal: options.signal }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
async getById(orderId) {
|
|
94
|
+
if (!this.http.visitorId) {
|
|
95
|
+
throw new ElenaApiError("Consultar un pedido requiere visitorId.", { status: 0 });
|
|
96
|
+
}
|
|
97
|
+
return this.http.request(
|
|
98
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/orders/${encodeURIComponent(orderId)}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
getTracking(trackingToken) {
|
|
102
|
+
return this.http.request(
|
|
103
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/tracking/${encodeURIComponent(trackingToken)}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
submitPaymentClaim(trackingToken, input) {
|
|
107
|
+
const form = new FormData();
|
|
108
|
+
form.set("referenceNumber", input.referenceNumber);
|
|
109
|
+
if (input.proofImage) form.set("proofImage", input.proofImage);
|
|
110
|
+
return this.http.request(
|
|
111
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/tracking/${encodeURIComponent(trackingToken)}/payment-claims`,
|
|
112
|
+
{ method: "POST", body: form }
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/resources/catalog.ts
|
|
118
|
+
var CatalogResource = class {
|
|
119
|
+
constructor(http, businessSlug) {
|
|
120
|
+
this.http = http;
|
|
121
|
+
this.businessSlug = businessSlug;
|
|
122
|
+
}
|
|
123
|
+
http;
|
|
124
|
+
businessSlug;
|
|
125
|
+
getInfo() {
|
|
126
|
+
return this.http.request(
|
|
127
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
getMenu(query) {
|
|
131
|
+
return this.http.request(
|
|
132
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/menu`,
|
|
133
|
+
{ query: { search: query?.search } }
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
listHeroSlides() {
|
|
137
|
+
return this.http.request(
|
|
138
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/hero-slides`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var TablesResource = class {
|
|
143
|
+
constructor(http, businessSlug) {
|
|
144
|
+
this.http = http;
|
|
145
|
+
this.businessSlug = businessSlug;
|
|
146
|
+
}
|
|
147
|
+
http;
|
|
148
|
+
businessSlug;
|
|
149
|
+
listAvailable() {
|
|
150
|
+
return this.http.request(
|
|
151
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/tables/available`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
get(tableId) {
|
|
155
|
+
return this.http.request(
|
|
156
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/tables/${encodeURIComponent(tableId)}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/resources/realtime.ts
|
|
162
|
+
var RealtimeResource = class {
|
|
163
|
+
constructor(http, businessSlug) {
|
|
164
|
+
this.http = http;
|
|
165
|
+
this.businessSlug = businessSlug;
|
|
166
|
+
}
|
|
167
|
+
http;
|
|
168
|
+
businessSlug;
|
|
169
|
+
/** SSE stream URL. EventSource-compatible: identity travels in query, not headers. */
|
|
170
|
+
getConnection(visitorId) {
|
|
171
|
+
const resolvedVisitorId = visitorId ?? this.http.visitorId;
|
|
172
|
+
if (!resolvedVisitorId) {
|
|
173
|
+
throw new ElenaApiError("El stream de eventos requiere visitorId.", { status: 0 });
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
url: this.http.url("/api/v1/public/events/stream", {
|
|
177
|
+
visitorId: resolvedVisitorId,
|
|
178
|
+
restaurantSlug: this.businessSlug
|
|
179
|
+
})
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var ChatResource = class {
|
|
184
|
+
constructor(http, businessSlug) {
|
|
185
|
+
this.http = http;
|
|
186
|
+
this.businessSlug = businessSlug;
|
|
187
|
+
}
|
|
188
|
+
http;
|
|
189
|
+
businessSlug;
|
|
190
|
+
/** Connection descriptor for the existing Tari chat runtime. No HTTP call. */
|
|
191
|
+
getConnection() {
|
|
192
|
+
return {
|
|
193
|
+
apiUrl: this.http.url("/api/v1"),
|
|
194
|
+
businessSlug: this.businessSlug,
|
|
195
|
+
...this.http.visitorId ? { visitorId: this.http.visitorId } : {}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
var ContinuationsResource = class {
|
|
200
|
+
constructor(http, businessSlug) {
|
|
201
|
+
this.http = http;
|
|
202
|
+
this.businessSlug = businessSlug;
|
|
203
|
+
}
|
|
204
|
+
http;
|
|
205
|
+
businessSlug;
|
|
206
|
+
exchange(token) {
|
|
207
|
+
return this.http.request(
|
|
208
|
+
`/api/v1/public/journeys/${encodeURIComponent(this.businessSlug)}/exchange`,
|
|
209
|
+
{ method: "POST", body: { token } }
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var JourneyConflictError = class extends ElenaApiError {
|
|
214
|
+
journey;
|
|
215
|
+
constructor(journey) {
|
|
216
|
+
super("La journey cambi\xF3 en el servidor; recarga el estado.", {
|
|
217
|
+
status: 409,
|
|
218
|
+
code: "journey_conflict",
|
|
219
|
+
details: { journey }
|
|
220
|
+
});
|
|
221
|
+
this.name = "JourneyConflictError";
|
|
222
|
+
this.journey = journey;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
var JourneyResource = class _JourneyResource {
|
|
226
|
+
constructor(http, businessSlug) {
|
|
227
|
+
this.http = http;
|
|
228
|
+
this.businessSlug = businessSlug;
|
|
229
|
+
}
|
|
230
|
+
http;
|
|
231
|
+
businessSlug;
|
|
232
|
+
getState() {
|
|
233
|
+
return this.http.request(
|
|
234
|
+
`/api/v1/public/journeys/${encodeURIComponent(this.businessSlug)}/state`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
async dispatch(input) {
|
|
238
|
+
try {
|
|
239
|
+
return await this.http.request(
|
|
240
|
+
`/api/v1/public/journeys/${encodeURIComponent(this.businessSlug)}/events`,
|
|
241
|
+
{
|
|
242
|
+
method: "POST",
|
|
243
|
+
body: {
|
|
244
|
+
eventId: input.eventId,
|
|
245
|
+
expectedVersion: input.expectedVersion,
|
|
246
|
+
event: input.event
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
throw _JourneyResource.withConflict(error);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async confirm(input) {
|
|
255
|
+
try {
|
|
256
|
+
return await this.http.request(`/api/v1/public/journeys/${encodeURIComponent(this.businessSlug)}/confirm`, {
|
|
257
|
+
method: "POST",
|
|
258
|
+
body: {
|
|
259
|
+
eventId: input.eventId,
|
|
260
|
+
expectedVersion: input.expectedVersion,
|
|
261
|
+
fingerprint: input.fingerprint
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
} catch (error) {
|
|
265
|
+
throw _JourneyResource.withConflict(error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
static withConflict(error) {
|
|
269
|
+
if (error instanceof ElenaApiError && error.status === 409 && typeof error.details === "object" && error.details !== null && "journey" in error.details) {
|
|
270
|
+
const journey = error.details.journey;
|
|
271
|
+
return new JourneyConflictError(journey);
|
|
272
|
+
}
|
|
273
|
+
return error;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/resources/receipts.ts
|
|
278
|
+
var ReceiptsResource = class {
|
|
279
|
+
constructor(http, businessSlug) {
|
|
280
|
+
this.http = http;
|
|
281
|
+
this.businessSlug = businessSlug;
|
|
282
|
+
}
|
|
283
|
+
http;
|
|
284
|
+
businessSlug;
|
|
285
|
+
async get(receiptToken) {
|
|
286
|
+
const receipt = await this.http.request(
|
|
287
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/sales/${encodeURIComponent(receiptToken)}`
|
|
288
|
+
);
|
|
289
|
+
const url = receipt.receipt?.url;
|
|
290
|
+
if (!url || !url.startsWith("/")) return receipt;
|
|
291
|
+
if (!receipt.receipt) return receipt;
|
|
292
|
+
return { ...receipt, receipt: { ...receipt.receipt, url: this.http.url(url) } };
|
|
293
|
+
}
|
|
294
|
+
/** PDF endpoint answers 302; callers open/download the URL, never JSON-fetch it. */
|
|
295
|
+
getPdfUrl(receiptToken) {
|
|
296
|
+
return this.http.url(
|
|
297
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/sales/${encodeURIComponent(receiptToken)}/pdf`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
var PresenceResource = class {
|
|
302
|
+
constructor(http, businessSlug) {
|
|
303
|
+
this.http = http;
|
|
304
|
+
this.businessSlug = businessSlug;
|
|
305
|
+
}
|
|
306
|
+
http;
|
|
307
|
+
businessSlug;
|
|
308
|
+
report(input, options = {}) {
|
|
309
|
+
return this.http.request(
|
|
310
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/live-session`,
|
|
311
|
+
{ method: "POST", body: input, signal: options.signal }
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
/** Beacon payload for pagehide/unload, where fetch cannot be awaited. */
|
|
315
|
+
getBeaconRequest(input) {
|
|
316
|
+
return {
|
|
317
|
+
url: this.http.url(
|
|
318
|
+
`/api/v1/public/catalog/${encodeURIComponent(this.businessSlug)}/live-session`
|
|
319
|
+
),
|
|
320
|
+
body: new Blob([JSON.stringify(input)], { type: "application/json" })
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// src/resources/discovery.ts
|
|
326
|
+
var EventsResource = class {
|
|
327
|
+
constructor(http) {
|
|
328
|
+
this.http = http;
|
|
329
|
+
}
|
|
330
|
+
http;
|
|
331
|
+
get(businessSlug, eventSlug) {
|
|
332
|
+
return this.http.request("/api/v1/public/events/detail", {
|
|
333
|
+
query: { restaurantSlug: businessSlug, eventSlug }
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
var TicketsResource = class {
|
|
338
|
+
constructor(http) {
|
|
339
|
+
this.http = http;
|
|
340
|
+
}
|
|
341
|
+
http;
|
|
342
|
+
emit(input) {
|
|
343
|
+
return this.http.request("/api/v1/public/tickets/emissions", {
|
|
344
|
+
method: "POST",
|
|
345
|
+
body: input
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
getStatus(token) {
|
|
349
|
+
return this.http.request(`/api/v1/public/tickets/${encodeURIComponent(token)}`);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
var AssetsResource = class {
|
|
353
|
+
constructor(http) {
|
|
354
|
+
this.http = http;
|
|
355
|
+
}
|
|
356
|
+
http;
|
|
357
|
+
/** Render endpoint serves binary content; callers use the URL, never JSON-fetch it. */
|
|
358
|
+
getRenderUrl(assetId) {
|
|
359
|
+
return this.http.url(`/api/v1/public/assets/${encodeURIComponent(assetId)}/render`);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// src/business.ts
|
|
364
|
+
var BusinessScope = class {
|
|
365
|
+
constructor(http, slug) {
|
|
366
|
+
this.http = http;
|
|
367
|
+
const normalized = slug.trim();
|
|
368
|
+
if (!normalized) {
|
|
369
|
+
throw new TypeError("BusinessScope necesita un slug.");
|
|
370
|
+
}
|
|
371
|
+
this.slug = normalized;
|
|
372
|
+
this.catalog = new CatalogResource(http, normalized);
|
|
373
|
+
this.tables = new TablesResource(http, normalized);
|
|
374
|
+
this.access = new AccessResource(http, normalized);
|
|
375
|
+
this.orders = new OrdersResource(http, normalized);
|
|
376
|
+
this.receipts = new ReceiptsResource(http, normalized);
|
|
377
|
+
this.presence = new PresenceResource(http, normalized);
|
|
378
|
+
this.continuations = new ContinuationsResource(http, normalized);
|
|
379
|
+
this.realtime = new RealtimeResource(http, normalized);
|
|
380
|
+
this.chat = new ChatResource(http, normalized);
|
|
381
|
+
this.eventsResource = new EventsResource(http);
|
|
382
|
+
this.ticketsResource = new TicketsResource(http);
|
|
383
|
+
}
|
|
384
|
+
http;
|
|
385
|
+
slug;
|
|
386
|
+
catalog;
|
|
387
|
+
tables;
|
|
388
|
+
access;
|
|
389
|
+
orders;
|
|
390
|
+
receipts;
|
|
391
|
+
presence;
|
|
392
|
+
continuations;
|
|
393
|
+
realtime;
|
|
394
|
+
chat;
|
|
395
|
+
eventsResource;
|
|
396
|
+
ticketsResource;
|
|
397
|
+
getInfo() {
|
|
398
|
+
return this.catalog.getInfo();
|
|
399
|
+
}
|
|
400
|
+
getEvent(eventSlug) {
|
|
401
|
+
return this.eventsResource.get(this.slug, eventSlug);
|
|
402
|
+
}
|
|
403
|
+
emitTickets(input) {
|
|
404
|
+
return this.ticketsResource.emit({ ...input, restaurantSlug: this.slug });
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// src/http-client.ts
|
|
409
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
410
|
+
function isFormDataBody(body) {
|
|
411
|
+
return typeof FormData !== "undefined" && body instanceof FormData;
|
|
412
|
+
}
|
|
413
|
+
function isPlainJsonBody(body) {
|
|
414
|
+
if (body === null || body === void 0) return false;
|
|
415
|
+
if (typeof body !== "object") return false;
|
|
416
|
+
if (isFormDataBody(body)) return false;
|
|
417
|
+
if (body instanceof Blob) return false;
|
|
418
|
+
if (body instanceof ArrayBuffer) return false;
|
|
419
|
+
if (ArrayBuffer.isView(body)) return false;
|
|
420
|
+
if (body instanceof URLSearchParams) return false;
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
var ElenaHttpClient = class {
|
|
424
|
+
baseUrl;
|
|
425
|
+
visitorId;
|
|
426
|
+
fetchImpl;
|
|
427
|
+
timeoutMs;
|
|
428
|
+
accessToken;
|
|
429
|
+
constructor(options, accessToken) {
|
|
430
|
+
const normalizedBaseUrl = options.baseUrl.trim().replace(/\/+$/, "");
|
|
431
|
+
if (!normalizedBaseUrl) {
|
|
432
|
+
throw new TypeError("Elena SDK necesita un baseUrl.");
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
const base = new URL(normalizedBaseUrl);
|
|
436
|
+
if (!/^https?:\/\//i.test(normalizedBaseUrl) || /[?#\\\s]/.test(normalizedBaseUrl) || base.username || base.password) {
|
|
437
|
+
throw new TypeError();
|
|
438
|
+
}
|
|
439
|
+
} catch {
|
|
440
|
+
throw new TypeError(
|
|
441
|
+
"El baseUrl de Elena debe ser una URL HTTP(S) sin credenciales, query ni fragmento."
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
this.baseUrl = normalizedBaseUrl;
|
|
445
|
+
const visitorId = options.visitorId?.trim();
|
|
446
|
+
this.visitorId = visitorId ? visitorId : void 0;
|
|
447
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
448
|
+
if (typeof fetchImpl !== "function") {
|
|
449
|
+
throw new TypeError("Elena SDK necesita una implementaci\xF3n de fetch.");
|
|
450
|
+
}
|
|
451
|
+
this.fetchImpl = fetchImpl.bind(globalThis);
|
|
452
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
453
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
454
|
+
throw new TypeError("El timeout del SDK de Elena debe ser mayor que cero.");
|
|
455
|
+
}
|
|
456
|
+
this.timeoutMs = timeoutMs;
|
|
457
|
+
const token = accessToken?.trim();
|
|
458
|
+
this.accessToken = token ? token : void 0;
|
|
459
|
+
}
|
|
460
|
+
url(path, query) {
|
|
461
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(path) || path.startsWith("//") || // eslint-disable-next-line no-control-regex -- Intentional: reject control chars and whitespace in paths.
|
|
462
|
+
/[\\\s\u0000-\u001f\u007f]/.test(path) || path.split(/[/?#]/).some((segment) => /^(?:\.|%2e){1,2}$/i.test(segment))) {
|
|
463
|
+
throw new TypeError(
|
|
464
|
+
"Las rutas del SDK de Elena deben ser relativas al baseUrl y no escapar de \xE9l."
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
468
|
+
const url = `${this.baseUrl}${normalizedPath}`;
|
|
469
|
+
if (!query) return url;
|
|
470
|
+
const params = new URLSearchParams();
|
|
471
|
+
for (const [key, value] of Object.entries(query)) {
|
|
472
|
+
if (value === void 0) continue;
|
|
473
|
+
params.set(key, String(value));
|
|
474
|
+
}
|
|
475
|
+
const qs = params.toString();
|
|
476
|
+
return qs ? `${url}?${qs}` : url;
|
|
477
|
+
}
|
|
478
|
+
async request(path, options = {}) {
|
|
479
|
+
if (options.signal?.aborted) {
|
|
480
|
+
throw new ElenaRequestAbortedError();
|
|
481
|
+
}
|
|
482
|
+
const headers = new Headers();
|
|
483
|
+
headers.set("Accept", "application/json");
|
|
484
|
+
let body;
|
|
485
|
+
if (options.body !== void 0) {
|
|
486
|
+
if (isPlainJsonBody(options.body)) {
|
|
487
|
+
headers.set("Content-Type", "application/json");
|
|
488
|
+
body = JSON.stringify(options.body);
|
|
489
|
+
} else {
|
|
490
|
+
body = options.body;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (this.visitorId) headers.set("x-visitor-id", this.visitorId);
|
|
494
|
+
if (options.headers) {
|
|
495
|
+
new Headers(options.headers).forEach((value, key) => {
|
|
496
|
+
headers.set(key, value);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
if (this.accessToken) headers.set("Authorization", `Bearer ${this.accessToken}`);
|
|
500
|
+
const controller = new AbortController();
|
|
501
|
+
let timedOut = false;
|
|
502
|
+
const timeout = setTimeout(() => {
|
|
503
|
+
timedOut = true;
|
|
504
|
+
controller.abort();
|
|
505
|
+
}, this.timeoutMs);
|
|
506
|
+
const onCallerAbort = () => {
|
|
507
|
+
controller.abort();
|
|
508
|
+
};
|
|
509
|
+
options.signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
510
|
+
if (options.signal?.aborted) onCallerAbort();
|
|
511
|
+
const bounded = (promise) => new Promise((resolve, reject) => {
|
|
512
|
+
const onAbort = () => {
|
|
513
|
+
cleanup();
|
|
514
|
+
reject(timedOut ? new ElenaTimeoutError(this.timeoutMs) : new ElenaRequestAbortedError());
|
|
515
|
+
};
|
|
516
|
+
const cleanup = () => controller.signal.removeEventListener("abort", onAbort);
|
|
517
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
518
|
+
promise.then(
|
|
519
|
+
(value) => {
|
|
520
|
+
cleanup();
|
|
521
|
+
resolve(value);
|
|
522
|
+
},
|
|
523
|
+
(error) => {
|
|
524
|
+
cleanup();
|
|
525
|
+
reject(error);
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
if (controller.signal.aborted) onAbort();
|
|
529
|
+
});
|
|
530
|
+
let response;
|
|
531
|
+
let text;
|
|
532
|
+
try {
|
|
533
|
+
response = await bounded(
|
|
534
|
+
Promise.resolve().then(() => {
|
|
535
|
+
if (controller.signal.aborted) throw new ElenaRequestAbortedError();
|
|
536
|
+
return this.fetchImpl(this.url(path, options.query), {
|
|
537
|
+
method: options.method ?? "GET",
|
|
538
|
+
headers,
|
|
539
|
+
body,
|
|
540
|
+
credentials: "omit",
|
|
541
|
+
redirect: "error",
|
|
542
|
+
referrerPolicy: "no-referrer",
|
|
543
|
+
signal: controller.signal,
|
|
544
|
+
...options.keepalive ? { keepalive: true } : {}
|
|
545
|
+
});
|
|
546
|
+
})
|
|
547
|
+
);
|
|
548
|
+
if (response.redirected || response.status >= 300 && response.status < 400 || response.type === "opaqueredirect" || response.url && new URL(response.url).origin !== new URL(this.baseUrl).origin) {
|
|
549
|
+
throw new ElenaResponseError("El SDK de Elena no sigue redirecciones de la API.", {
|
|
550
|
+
status: response.status
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
text = await bounded(Promise.resolve().then(() => response.text()));
|
|
554
|
+
} catch (error) {
|
|
555
|
+
if (timedOut) throw new ElenaTimeoutError(this.timeoutMs);
|
|
556
|
+
if (options.signal?.aborted) throw new ElenaRequestAbortedError();
|
|
557
|
+
if (error instanceof ElenaResponseError) throw error;
|
|
558
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
559
|
+
throw new ElenaRequestAbortedError();
|
|
560
|
+
}
|
|
561
|
+
throw new ElenaNetworkError(error instanceof Error ? error.message : void 0);
|
|
562
|
+
} finally {
|
|
563
|
+
clearTimeout(timeout);
|
|
564
|
+
options.signal?.removeEventListener("abort", onCallerAbort);
|
|
565
|
+
}
|
|
566
|
+
const retryAfter = response.headers.get("retry-after") ?? void 0;
|
|
567
|
+
let payload = null;
|
|
568
|
+
if (text) {
|
|
569
|
+
try {
|
|
570
|
+
payload = JSON.parse(text);
|
|
571
|
+
} catch {
|
|
572
|
+
if (!response.ok) {
|
|
573
|
+
throw new ElenaApiError(`Elena respondi\xF3 ${response.status}: ${response.statusText}`, {
|
|
574
|
+
status: response.status,
|
|
575
|
+
details: text,
|
|
576
|
+
retryAfter
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
throw new ElenaResponseError("Respuesta inv\xE1lida del servidor de Elena.", {
|
|
580
|
+
status: response.status,
|
|
581
|
+
bodyText: text
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (!response.ok) {
|
|
586
|
+
let code;
|
|
587
|
+
let message;
|
|
588
|
+
if (typeof payload === "object" && payload !== null) {
|
|
589
|
+
if ("code" in payload && typeof payload.code === "string" && payload.code) {
|
|
590
|
+
code = payload.code;
|
|
591
|
+
} else if ("error" in payload && typeof payload.error === "string" && payload.error) {
|
|
592
|
+
code = payload.error;
|
|
593
|
+
}
|
|
594
|
+
if (!code && "details" in payload && typeof payload.details === "object" && payload.details !== null && "code" in payload.details && typeof payload.details.code === "string" && payload.details.code) {
|
|
595
|
+
code = payload.details.code;
|
|
596
|
+
}
|
|
597
|
+
if ("message" in payload && typeof payload.message === "string" && payload.message) {
|
|
598
|
+
message = payload.message;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
throw new ElenaApiError(
|
|
602
|
+
message ?? `Elena respondi\xF3 ${response.status}: ${response.statusText}`,
|
|
603
|
+
{ status: response.status, code, details: payload, retryAfter }
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
return payload;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// src/client.ts
|
|
611
|
+
function createElenaPublicClient(options) {
|
|
612
|
+
const http = new ElenaHttpClient(options);
|
|
613
|
+
return {
|
|
614
|
+
business: (slug) => new BusinessScope(http, slug),
|
|
615
|
+
tickets: new TicketsResource(http),
|
|
616
|
+
assets: new AssetsResource(http)
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function createElenaJourneyClient(options) {
|
|
620
|
+
if (!options.accessToken?.trim()) {
|
|
621
|
+
throw new TypeError("El cliente journey de Elena necesita un accessToken.");
|
|
622
|
+
}
|
|
623
|
+
const businessSlug = options.businessSlug.trim();
|
|
624
|
+
if (!businessSlug) {
|
|
625
|
+
throw new TypeError("El cliente journey de Elena necesita un businessSlug.");
|
|
626
|
+
}
|
|
627
|
+
const http = new ElenaHttpClient(
|
|
628
|
+
{
|
|
629
|
+
baseUrl: options.baseUrl,
|
|
630
|
+
fetch: options.fetch,
|
|
631
|
+
timeoutMs: options.timeoutMs
|
|
632
|
+
},
|
|
633
|
+
options.accessToken
|
|
634
|
+
);
|
|
635
|
+
return { businessSlug, journeys: new JourneyResource(http, businessSlug) };
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/checkout.ts
|
|
639
|
+
var catalogName = (name) => name.trim().toLocaleLowerCase();
|
|
640
|
+
function invalid(code, field, message) {
|
|
641
|
+
throw new ElenaCheckoutValidationError(message, { code, field });
|
|
642
|
+
}
|
|
643
|
+
function buildOrderItems(menu, selections) {
|
|
644
|
+
return selections.map((selection, itemIndex) => {
|
|
645
|
+
const field = `selections[${itemIndex}]`;
|
|
646
|
+
const products = menu.products.filter((product2) => product2.id === selection.productId);
|
|
647
|
+
const product = products[0];
|
|
648
|
+
if (!product) {
|
|
649
|
+
return invalid(
|
|
650
|
+
"PRODUCT_NOT_FOUND",
|
|
651
|
+
`${field}.productId`,
|
|
652
|
+
"El producto no existe en el cat\xE1logo."
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
if (products.length !== 1) {
|
|
656
|
+
return invalid("AMBIGUOUS_SELECTION", `${field}.productId`, "El ID del producto es ambiguo.");
|
|
657
|
+
}
|
|
658
|
+
if (!product.available) {
|
|
659
|
+
return invalid(
|
|
660
|
+
"PRODUCT_UNAVAILABLE",
|
|
661
|
+
`${field}.productId`,
|
|
662
|
+
"El producto no est\xE1 disponible."
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
if (!Number.isInteger(selection.quantity) || selection.quantity < 1 || selection.quantity > 100) {
|
|
666
|
+
return invalid(
|
|
667
|
+
"INVALID_QUANTITY",
|
|
668
|
+
`${field}.quantity`,
|
|
669
|
+
"La cantidad debe ser un entero entre 1 y 100."
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const counts = /* @__PURE__ */ new Map();
|
|
673
|
+
const selected = /* @__PURE__ */ new Map();
|
|
674
|
+
const extras = (selection.selectedOptions ?? []).map((selectionOption, optionIndex) => {
|
|
675
|
+
const optionField = `${field}.selectedOptions[${optionIndex}]`;
|
|
676
|
+
const groups = product.customizationGroups.filter(
|
|
677
|
+
(group2) => group2.id === selectionOption.groupId
|
|
678
|
+
);
|
|
679
|
+
const group = groups[0];
|
|
680
|
+
if (!group) {
|
|
681
|
+
return invalid(
|
|
682
|
+
"UNKNOWN_GROUP",
|
|
683
|
+
`${optionField}.groupId`,
|
|
684
|
+
"El grupo no pertenece al producto."
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
if (groups.length !== 1 || !catalogName(group.name) || product.customizationGroups.filter(
|
|
688
|
+
(candidate) => catalogName(candidate.name) === catalogName(group.name)
|
|
689
|
+
).length !== 1) {
|
|
690
|
+
return invalid(
|
|
691
|
+
"AMBIGUOUS_SELECTION",
|
|
692
|
+
`${optionField}.groupId`,
|
|
693
|
+
"El nombre o ID del grupo es ambiguo."
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
const options = group.options.filter((option2) => option2.id === selectionOption.optionId);
|
|
697
|
+
const option = options[0];
|
|
698
|
+
if (!option) {
|
|
699
|
+
return invalid(
|
|
700
|
+
"UNKNOWN_OPTION",
|
|
701
|
+
`${optionField}.optionId`,
|
|
702
|
+
"La opci\xF3n no pertenece al grupo."
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
if (options.length !== 1 || !catalogName(option.name) || group.options.filter(
|
|
706
|
+
(candidate) => catalogName(candidate.name) === catalogName(option.name)
|
|
707
|
+
).length !== 1) {
|
|
708
|
+
return invalid(
|
|
709
|
+
"AMBIGUOUS_SELECTION",
|
|
710
|
+
`${optionField}.optionId`,
|
|
711
|
+
"El nombre o ID de la opci\xF3n es ambiguo."
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
const groupSelections = selected.get(group.id) ?? /* @__PURE__ */ new Set();
|
|
715
|
+
if (groupSelections.has(option.id)) {
|
|
716
|
+
return invalid(
|
|
717
|
+
"DUPLICATE_OPTION",
|
|
718
|
+
`${optionField}.optionId`,
|
|
719
|
+
"Una opci\xF3n no puede seleccionarse m\xE1s de una vez por art\xEDculo."
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
groupSelections.add(option.id);
|
|
723
|
+
selected.set(group.id, groupSelections);
|
|
724
|
+
counts.set(group.id, (counts.get(group.id) ?? 0) + 1);
|
|
725
|
+
return {
|
|
726
|
+
groupName: group.name,
|
|
727
|
+
optionName: option.name,
|
|
728
|
+
additionalPrice: option.additionalPrice
|
|
729
|
+
};
|
|
730
|
+
});
|
|
731
|
+
for (const group of product.customizationGroups) {
|
|
732
|
+
const count = counts.get(group.id) ?? 0;
|
|
733
|
+
if (group.required && count === 0) {
|
|
734
|
+
return invalid(
|
|
735
|
+
"REQUIRED_OPTIONS",
|
|
736
|
+
`${field}.selectedOptions`,
|
|
737
|
+
`El grupo "${group.name}" requiere una opci\xF3n.`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
if (count < group.minSelections) {
|
|
741
|
+
return invalid(
|
|
742
|
+
"MIN_SELECTIONS",
|
|
743
|
+
`${field}.selectedOptions`,
|
|
744
|
+
`El grupo "${group.name}" requiere al menos ${group.minSelections} opci\xF3n(es).`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
if (count > group.maxSelections) {
|
|
748
|
+
return invalid(
|
|
749
|
+
"MAX_SELECTIONS",
|
|
750
|
+
`${field}.selectedOptions`,
|
|
751
|
+
`El grupo "${group.name}" permite como m\xE1ximo ${group.maxSelections} opci\xF3n(es).`
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
productId: product.id,
|
|
757
|
+
cantidad: selection.quantity,
|
|
758
|
+
unitPrice: product.basePrice,
|
|
759
|
+
...selection.notes !== void 0 ? { specialNotes: selection.notes } : {},
|
|
760
|
+
...extras.length ? { extras } : {}
|
|
761
|
+
};
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// ../shared/src/agent-contracts/access.ts
|
|
766
|
+
import { z as z2 } from "zod";
|
|
767
|
+
|
|
768
|
+
// ../shared/src/agent-contracts/common.ts
|
|
769
|
+
import { z } from "zod";
|
|
770
|
+
var ELENA_AGENT_CONTRACT_VERSION = "1";
|
|
771
|
+
var elenaAgentVersionSchema = z.literal(ELENA_AGENT_CONTRACT_VERSION);
|
|
772
|
+
var elenaAgentIdSchema = z.uuid();
|
|
773
|
+
var elenaAgentRevisionSchema = z.number().int().min(1).max(Number.MAX_SAFE_INTEGER);
|
|
774
|
+
var elenaAgentTimestampSchema = z.iso.datetime();
|
|
775
|
+
var elenaAgentHashSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
776
|
+
var elenaAgentRefSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,99}$/);
|
|
777
|
+
var elenaAgentNameSchema = z.string().trim().min(1).max(160);
|
|
778
|
+
var elenaAgentTextSchema = z.string().trim().min(1).max(2e3);
|
|
779
|
+
var elenaAgentCurrencySchema = z.literal("PEN");
|
|
780
|
+
var elenaAgentNonnegativeMoneySchema = z.string().regex(/^(0|[1-9]\d{0,7})\.\d{2}$/);
|
|
781
|
+
var elenaAgentMoneySchema = elenaAgentNonnegativeMoneySchema.refine(
|
|
782
|
+
(value) => value !== "0.00",
|
|
783
|
+
"Product price must be greater than zero"
|
|
784
|
+
);
|
|
785
|
+
var ELENA_AGENT_LIMITS = Object.freeze({
|
|
786
|
+
categoriesPerImport: 50,
|
|
787
|
+
productsPerImport: 200,
|
|
788
|
+
changesPerOperation: 200,
|
|
789
|
+
publicationProducts: 200,
|
|
790
|
+
galleryImages: 8,
|
|
791
|
+
extraGroups: 10,
|
|
792
|
+
optionsPerGroup: 30,
|
|
793
|
+
jsonBytes: 1024 * 1024,
|
|
794
|
+
mediaBytes: 10 * 1024 * 1024,
|
|
795
|
+
stagedBytesPerRestaurant: 100 * 1024 * 1024,
|
|
796
|
+
stagedFilesPerRestaurant: 20,
|
|
797
|
+
openOperationsPerRestaurant: 20,
|
|
798
|
+
openPreviewsPerRestaurant: 20,
|
|
799
|
+
activeKeysPerFamily: 10,
|
|
800
|
+
keyDefaultTtlSeconds: 30 * 24 * 60 * 60,
|
|
801
|
+
keyMaxTtlSeconds: 90 * 24 * 60 * 60,
|
|
802
|
+
pairingTtlSeconds: 600,
|
|
803
|
+
pairingPollSeconds: 5,
|
|
804
|
+
proposalTtlSeconds: 24 * 60 * 60,
|
|
805
|
+
previewTtlSeconds: 24 * 60 * 60,
|
|
806
|
+
stagingTtlSeconds: 48 * 60 * 60,
|
|
807
|
+
applyWindowSeconds: 60 * 60,
|
|
808
|
+
workerLeaseSeconds: 30,
|
|
809
|
+
workerHeartbeatSeconds: 10,
|
|
810
|
+
maxWorkerAttempts: 5,
|
|
811
|
+
resultRetentionDays: 90,
|
|
812
|
+
pageSize: 100
|
|
813
|
+
});
|
|
814
|
+
var elenaAgentErrorCodeSchema = z.enum([
|
|
815
|
+
"INVALID_REQUEST",
|
|
816
|
+
"UNSUPPORTED_VERSION",
|
|
817
|
+
"CREDENTIAL_REQUIRED",
|
|
818
|
+
"CREDENTIAL_INVALID",
|
|
819
|
+
"CREDENTIAL_EXPIRED",
|
|
820
|
+
"CREDENTIAL_REVOKED",
|
|
821
|
+
"CREDENTIAL_MIXED",
|
|
822
|
+
"CREDENTIAL_WRONG_FAMILY",
|
|
823
|
+
"OWNER_REQUIRED",
|
|
824
|
+
"ORIGIN_REJECTED",
|
|
825
|
+
"APPROVAL_REQUIRED",
|
|
826
|
+
"APPROVAL_REJECTED",
|
|
827
|
+
"APPROVAL_EXPIRED",
|
|
828
|
+
"APPROVAL_MISMATCH",
|
|
829
|
+
"SCOPE_REQUIRED",
|
|
830
|
+
"MODE_READ_ONLY",
|
|
831
|
+
"API_ACCESS_DISABLED",
|
|
832
|
+
"RESTAURANT_NOT_APPROVED",
|
|
833
|
+
"NOT_FOUND",
|
|
834
|
+
"VERSION_CONFLICT",
|
|
835
|
+
"IDEMPOTENCY_CONFLICT",
|
|
836
|
+
"INVALID_PRICE",
|
|
837
|
+
"UNCONFIRMED_DATA",
|
|
838
|
+
"REFERENCE_CONFLICT",
|
|
839
|
+
"MEDIA_INVALID",
|
|
840
|
+
"MEDIA_EXPIRED",
|
|
841
|
+
"QUOTA_EXCEEDED",
|
|
842
|
+
"RATE_LIMITED",
|
|
843
|
+
"PAIRING_PENDING",
|
|
844
|
+
"PAIRING_DENIED",
|
|
845
|
+
"PAIRING_EXPIRED",
|
|
846
|
+
"PAIRING_CONSUMED",
|
|
847
|
+
"OPERATION_TERMINAL",
|
|
848
|
+
"APPLY_WINDOW_EXPIRED",
|
|
849
|
+
"DEPENDENCY_FAILED",
|
|
850
|
+
"TEMPORARY_UNAVAILABLE",
|
|
851
|
+
"INTERNAL_ERROR"
|
|
852
|
+
]);
|
|
853
|
+
var elenaAgentErrorSchema = z.strictObject({
|
|
854
|
+
version: elenaAgentVersionSchema,
|
|
855
|
+
error: z.strictObject({
|
|
856
|
+
code: elenaAgentErrorCodeSchema,
|
|
857
|
+
message: z.string().max(500),
|
|
858
|
+
retryable: z.boolean(),
|
|
859
|
+
requestId: elenaAgentRefSchema,
|
|
860
|
+
retryAfterSeconds: z.number().int().min(1).max(3600).optional(),
|
|
861
|
+
issues: z.array(
|
|
862
|
+
z.strictObject({
|
|
863
|
+
path: z.string().max(200),
|
|
864
|
+
code: elenaAgentErrorCodeSchema,
|
|
865
|
+
message: z.string().max(200)
|
|
866
|
+
})
|
|
867
|
+
).max(250).optional()
|
|
868
|
+
})
|
|
869
|
+
});
|
|
870
|
+
var elenaAgentPageQuerySchema = z.strictObject({
|
|
871
|
+
version: elenaAgentVersionSchema,
|
|
872
|
+
cursor: z.string().regex(/^[a-zA-Z0-9_-]{1,512}$/).optional(),
|
|
873
|
+
limit: z.number().int().min(1).max(ELENA_AGENT_LIMITS.pageSize).default(50)
|
|
874
|
+
});
|
|
875
|
+
var elenaAgentEmptyRequestSchema = z.strictObject({ version: elenaAgentVersionSchema });
|
|
876
|
+
var elenaAgentAckSchema = z.strictObject({
|
|
877
|
+
version: elenaAgentVersionSchema,
|
|
878
|
+
ok: z.literal(true)
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
// ../shared/src/agent-contracts/access.ts
|
|
882
|
+
var ELENA_AGENT_TRANSPORT = Object.freeze({
|
|
883
|
+
agentPrefix: "elena_sk_",
|
|
884
|
+
agentHeader: "x-elena-agent-key",
|
|
885
|
+
agentPath: "/api/v1/agent",
|
|
886
|
+
clientPrefix: "elena_pk_",
|
|
887
|
+
clientHeader: "x-elena-client-key",
|
|
888
|
+
clientPath: "/api/v1/public/sdk",
|
|
889
|
+
ownerPath: "/api/v1/agent-access"
|
|
890
|
+
});
|
|
891
|
+
var elenaAgentSecretSchema = z2.string().regex(/^elena_sk_[A-Za-z0-9_-]{43}$/);
|
|
892
|
+
var elenaManualClientKeySchema = z2.string().regex(/^elena_pk_[A-Za-z0-9_-]{43}$/);
|
|
893
|
+
var elenaManagedClientKeySchema = z2.string().max(160).regex(
|
|
894
|
+
/^elena_pk_m_[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.[1-9]\d{0,15}\.[A-Za-z0-9_-]{43}$/
|
|
895
|
+
);
|
|
896
|
+
var elenaClientKeySchema = z2.union([
|
|
897
|
+
elenaManualClientKeySchema,
|
|
898
|
+
elenaManagedClientKeySchema
|
|
899
|
+
]);
|
|
900
|
+
var elenaAgentDeviceCodeSchema = z2.string().regex(/^[A-Za-z0-9_-]{43}$/);
|
|
901
|
+
var elenaAgentUserCodeSchema = z2.string().regex(/^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/);
|
|
902
|
+
var elenaAgentModeSchema = z2.enum(["read", "mutate"]);
|
|
903
|
+
var elenaAgentScopeSchema = z2.enum([
|
|
904
|
+
"catalog:read",
|
|
905
|
+
"catalog:write",
|
|
906
|
+
"catalog:publish",
|
|
907
|
+
"assets:read",
|
|
908
|
+
"assets:write"
|
|
909
|
+
]);
|
|
910
|
+
var elenaAgentScopesSchema = z2.array(elenaAgentScopeSchema).min(1).max(5).refine((scopes) => new Set(scopes).size === scopes.length, "Duplicate scope").refine((scopes) => scopes.includes("catalog:read"), "catalog:read is mandatory");
|
|
911
|
+
var elenaAgentActionSchema = z2.enum([
|
|
912
|
+
"context.read",
|
|
913
|
+
"catalog.read",
|
|
914
|
+
"assets.read",
|
|
915
|
+
"assets.stage",
|
|
916
|
+
"operations.read",
|
|
917
|
+
"operations.propose",
|
|
918
|
+
"operations.apply",
|
|
919
|
+
"catalog.publish"
|
|
920
|
+
]);
|
|
921
|
+
var elenaAgentRestaurantSchema = z2.strictObject({
|
|
922
|
+
id: elenaAgentIdSchema,
|
|
923
|
+
name: elenaAgentNameSchema,
|
|
924
|
+
slug: z2.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(100),
|
|
925
|
+
currency: elenaAgentCurrencySchema
|
|
926
|
+
});
|
|
927
|
+
var keyTtl = z2.number().int().min(3600).max(ELENA_AGENT_LIMITS.keyMaxTtlSeconds).default(ELENA_AGENT_LIMITS.keyDefaultTtlSeconds);
|
|
928
|
+
var elenaAgentKeyCreateRequestSchema = z2.discriminatedUnion("kind", [
|
|
929
|
+
z2.strictObject({
|
|
930
|
+
version: elenaAgentVersionSchema,
|
|
931
|
+
kind: z2.literal("agent"),
|
|
932
|
+
label: elenaAgentNameSchema,
|
|
933
|
+
mode: elenaAgentModeSchema.default("read"),
|
|
934
|
+
scopes: elenaAgentScopesSchema.default(["catalog:read", "assets:read"]),
|
|
935
|
+
ttlSeconds: keyTtl
|
|
936
|
+
}),
|
|
937
|
+
z2.strictObject({
|
|
938
|
+
version: elenaAgentVersionSchema,
|
|
939
|
+
kind: z2.literal("client"),
|
|
940
|
+
label: elenaAgentNameSchema,
|
|
941
|
+
ttlSeconds: keyTtl
|
|
942
|
+
})
|
|
943
|
+
]);
|
|
944
|
+
var elenaAgentKeyPolicyRequestSchema = z2.strictObject({
|
|
945
|
+
version: elenaAgentVersionSchema,
|
|
946
|
+
expectedRevision: elenaAgentRevisionSchema,
|
|
947
|
+
mode: elenaAgentModeSchema,
|
|
948
|
+
scopes: elenaAgentScopesSchema
|
|
949
|
+
});
|
|
950
|
+
var elenaAgentKeyRevokeRequestSchema = z2.strictObject({
|
|
951
|
+
version: elenaAgentVersionSchema,
|
|
952
|
+
expectedRevision: elenaAgentRevisionSchema
|
|
953
|
+
});
|
|
954
|
+
var elenaAgentKeyReplaceRequestSchema = z2.strictObject({
|
|
955
|
+
version: elenaAgentVersionSchema,
|
|
956
|
+
expectedRevision: elenaAgentRevisionSchema,
|
|
957
|
+
ttlSeconds: keyTtl
|
|
958
|
+
});
|
|
959
|
+
var keyMetadata = {
|
|
960
|
+
id: elenaAgentIdSchema,
|
|
961
|
+
label: elenaAgentNameSchema,
|
|
962
|
+
displaySuffix: z2.string().regex(/^[A-Za-z0-9_-]{6}$/),
|
|
963
|
+
revision: elenaAgentRevisionSchema,
|
|
964
|
+
status: z2.enum(["active", "revoked", "expired"]),
|
|
965
|
+
createdAt: elenaAgentTimestampSchema,
|
|
966
|
+
expiresAt: elenaAgentTimestampSchema,
|
|
967
|
+
revokedAt: elenaAgentTimestampSchema.nullable(),
|
|
968
|
+
lastUsedAt: elenaAgentTimestampSchema.nullable()
|
|
969
|
+
};
|
|
970
|
+
var elenaAgentKeyMetadataSchema = z2.discriminatedUnion("kind", [
|
|
971
|
+
z2.strictObject({
|
|
972
|
+
...keyMetadata,
|
|
973
|
+
kind: z2.literal("agent"),
|
|
974
|
+
mode: elenaAgentModeSchema,
|
|
975
|
+
scopes: elenaAgentScopesSchema
|
|
976
|
+
}),
|
|
977
|
+
z2.strictObject({ ...keyMetadata, kind: z2.literal("client") })
|
|
978
|
+
]);
|
|
979
|
+
var elenaAgentKeyListSchema = z2.strictObject({
|
|
980
|
+
version: elenaAgentVersionSchema,
|
|
981
|
+
keys: z2.array(elenaAgentKeyMetadataSchema).max(20)
|
|
982
|
+
});
|
|
983
|
+
var elenaAgentKeyReceiptSchema = z2.discriminatedUnion("kind", [
|
|
984
|
+
z2.strictObject({
|
|
985
|
+
version: elenaAgentVersionSchema,
|
|
986
|
+
kind: z2.literal("agent"),
|
|
987
|
+
key: elenaAgentKeyMetadataSchema.options[0],
|
|
988
|
+
secret: elenaAgentSecretSchema
|
|
989
|
+
}),
|
|
990
|
+
z2.strictObject({
|
|
991
|
+
version: elenaAgentVersionSchema,
|
|
992
|
+
kind: z2.literal("client"),
|
|
993
|
+
key: elenaAgentKeyMetadataSchema.options[1],
|
|
994
|
+
clientKey: elenaManualClientKeySchema
|
|
995
|
+
})
|
|
996
|
+
]);
|
|
997
|
+
var elenaAgentKeyResponseSchema = z2.strictObject({
|
|
998
|
+
version: elenaAgentVersionSchema,
|
|
999
|
+
key: elenaAgentKeyMetadataSchema
|
|
1000
|
+
});
|
|
1001
|
+
var elenaAgentHttpsUrlSchema = z2.url({ protocol: /^https$/ }).max(2048).refine(
|
|
1002
|
+
(value) => /^https:\/\/[^/?#@\\\s]+(?:[/?#]|$)/.test(value),
|
|
1003
|
+
"URL must not contain userinfo"
|
|
1004
|
+
);
|
|
1005
|
+
var elenaAgentConnectionUrlSchema = z2.url({ protocol: /^https?$/ }).max(2048).refine(
|
|
1006
|
+
(value) => /^https?:\/\/[^/?#@\\\s]+(?:[/?#]|$)/.test(value),
|
|
1007
|
+
"URL must not contain userinfo"
|
|
1008
|
+
);
|
|
1009
|
+
var elenaAgentBaseUrlSchema = elenaAgentHttpsUrlSchema.refine(
|
|
1010
|
+
(value) => /^https:\/\/[^/?#@\\\s]+\/?$/.test(value),
|
|
1011
|
+
"Base URL must be an explicit origin"
|
|
1012
|
+
);
|
|
1013
|
+
var elenaAgentConnectionOriginSchema = elenaAgentConnectionUrlSchema.refine(
|
|
1014
|
+
(value) => /^https?:\/\/[^/?#@\\\s]+\/?$/.test(value),
|
|
1015
|
+
"Base URL must be an explicit origin"
|
|
1016
|
+
);
|
|
1017
|
+
var elenaAgentPairingStartRequestSchema = z2.strictObject({
|
|
1018
|
+
version: elenaAgentVersionSchema,
|
|
1019
|
+
label: elenaAgentNameSchema
|
|
1020
|
+
});
|
|
1021
|
+
var elenaAgentPairingStartResponseSchema = z2.strictObject({
|
|
1022
|
+
version: elenaAgentVersionSchema,
|
|
1023
|
+
pairingId: elenaAgentIdSchema,
|
|
1024
|
+
deviceCode: elenaAgentDeviceCodeSchema,
|
|
1025
|
+
userCode: elenaAgentUserCodeSchema,
|
|
1026
|
+
verificationUrl: elenaAgentConnectionUrlSchema,
|
|
1027
|
+
expiresAt: elenaAgentTimestampSchema,
|
|
1028
|
+
intervalSeconds: z2.literal(ELENA_AGENT_LIMITS.pairingPollSeconds)
|
|
1029
|
+
});
|
|
1030
|
+
var elenaAgentPairingPollRequestSchema = z2.strictObject({
|
|
1031
|
+
version: elenaAgentVersionSchema,
|
|
1032
|
+
deviceCode: elenaAgentDeviceCodeSchema
|
|
1033
|
+
});
|
|
1034
|
+
var elenaAgentPairingReviewSchema = z2.strictObject({
|
|
1035
|
+
version: elenaAgentVersionSchema,
|
|
1036
|
+
label: elenaAgentNameSchema,
|
|
1037
|
+
userCode: elenaAgentUserCodeSchema,
|
|
1038
|
+
state: z2.enum(["pending", "approved", "rejected", "expired", "consumed"]),
|
|
1039
|
+
expiresAt: elenaAgentTimestampSchema
|
|
1040
|
+
});
|
|
1041
|
+
var elenaAgentPairingDecisionRequestSchema = z2.discriminatedUnion("decision", [
|
|
1042
|
+
z2.strictObject({
|
|
1043
|
+
version: elenaAgentVersionSchema,
|
|
1044
|
+
userCode: elenaAgentUserCodeSchema,
|
|
1045
|
+
decision: z2.literal("approve"),
|
|
1046
|
+
mode: elenaAgentModeSchema.default("read"),
|
|
1047
|
+
scopes: elenaAgentScopesSchema.default(["catalog:read", "assets:read"]),
|
|
1048
|
+
ttlSeconds: keyTtl
|
|
1049
|
+
}),
|
|
1050
|
+
z2.strictObject({
|
|
1051
|
+
version: elenaAgentVersionSchema,
|
|
1052
|
+
userCode: elenaAgentUserCodeSchema,
|
|
1053
|
+
decision: z2.literal("reject")
|
|
1054
|
+
})
|
|
1055
|
+
]);
|
|
1056
|
+
var elenaAgentPairingPollResponseSchema = z2.discriminatedUnion("status", [
|
|
1057
|
+
z2.strictObject({
|
|
1058
|
+
version: elenaAgentVersionSchema,
|
|
1059
|
+
status: z2.literal("pending"),
|
|
1060
|
+
expiresAt: elenaAgentTimestampSchema,
|
|
1061
|
+
intervalSeconds: z2.literal(ELENA_AGENT_LIMITS.pairingPollSeconds)
|
|
1062
|
+
}),
|
|
1063
|
+
z2.strictObject({
|
|
1064
|
+
version: elenaAgentVersionSchema,
|
|
1065
|
+
status: z2.literal("delivered"),
|
|
1066
|
+
restaurant: elenaAgentRestaurantSchema,
|
|
1067
|
+
credential: elenaAgentKeyReceiptSchema.options[0]
|
|
1068
|
+
})
|
|
1069
|
+
]);
|
|
1070
|
+
var elenaAgentContextSchema = z2.strictObject({
|
|
1071
|
+
version: elenaAgentVersionSchema,
|
|
1072
|
+
restaurant: elenaAgentRestaurantSchema,
|
|
1073
|
+
principal: z2.strictObject({
|
|
1074
|
+
kind: z2.literal("agent"),
|
|
1075
|
+
credentialId: elenaAgentIdSchema,
|
|
1076
|
+
credentialRevision: elenaAgentRevisionSchema,
|
|
1077
|
+
mode: elenaAgentModeSchema,
|
|
1078
|
+
scopes: elenaAgentScopesSchema,
|
|
1079
|
+
expiresAt: elenaAgentTimestampSchema
|
|
1080
|
+
}),
|
|
1081
|
+
capabilities: z2.strictObject({
|
|
1082
|
+
apiAccess: z2.literal(true),
|
|
1083
|
+
menu: z2.literal(true),
|
|
1084
|
+
imports: z2.boolean(),
|
|
1085
|
+
media: z2.boolean(),
|
|
1086
|
+
publication: z2.boolean()
|
|
1087
|
+
}),
|
|
1088
|
+
actions: z2.array(elenaAgentActionSchema).min(1).max(8).refine((actions) => new Set(actions).size === actions.length, "Duplicate action")
|
|
1089
|
+
}).superRefine((value, ctx) => {
|
|
1090
|
+
const requiredScopes = {
|
|
1091
|
+
"catalog.read": "catalog:read",
|
|
1092
|
+
"assets.read": "assets:read",
|
|
1093
|
+
"assets.stage": "assets:write",
|
|
1094
|
+
"operations.propose": "catalog:write",
|
|
1095
|
+
"operations.apply": "catalog:write",
|
|
1096
|
+
"catalog.publish": "catalog:publish"
|
|
1097
|
+
};
|
|
1098
|
+
const writes = {
|
|
1099
|
+
"assets.stage": true,
|
|
1100
|
+
"operations.propose": true,
|
|
1101
|
+
"operations.apply": true,
|
|
1102
|
+
"catalog.publish": true
|
|
1103
|
+
};
|
|
1104
|
+
for (const action of value.actions) {
|
|
1105
|
+
if (value.principal.mode === "read" && writes[action]) {
|
|
1106
|
+
ctx.addIssue({
|
|
1107
|
+
code: "custom",
|
|
1108
|
+
path: ["actions"],
|
|
1109
|
+
message: "Read mode cannot expose mutation actions"
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
const scope = requiredScopes[action];
|
|
1113
|
+
if (scope && !value.principal.scopes.includes(scope)) {
|
|
1114
|
+
ctx.addIssue({
|
|
1115
|
+
code: "custom",
|
|
1116
|
+
path: ["actions"],
|
|
1117
|
+
message: "Action exceeds granted scopes"
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
if (value.actions.includes("catalog.publish") && !value.principal.scopes.includes("catalog:write")) {
|
|
1122
|
+
ctx.addIssue({
|
|
1123
|
+
code: "custom",
|
|
1124
|
+
path: ["actions"],
|
|
1125
|
+
message: "Publication also requires catalog:write"
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
const expectedCapabilities = {
|
|
1129
|
+
imports: value.actions.includes("operations.propose") && value.actions.includes("operations.apply"),
|
|
1130
|
+
media: value.actions.includes("assets.stage"),
|
|
1131
|
+
publication: value.actions.includes("catalog.publish")
|
|
1132
|
+
};
|
|
1133
|
+
for (const capability of ["imports", "media", "publication"]) {
|
|
1134
|
+
if (value.capabilities[capability] !== expectedCapabilities[capability]) {
|
|
1135
|
+
ctx.addIssue({
|
|
1136
|
+
code: "custom",
|
|
1137
|
+
path: ["capabilities", capability],
|
|
1138
|
+
message: "Capability must match effective actions"
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
var elenaClientContextSchema = z2.strictObject({
|
|
1144
|
+
version: elenaAgentVersionSchema,
|
|
1145
|
+
restaurant: elenaAgentRestaurantSchema,
|
|
1146
|
+
capabilities: z2.strictObject({ menu: z2.literal(true), assets: z2.literal(true) })
|
|
1147
|
+
});
|
|
1148
|
+
var elenaAgentCliAuthStatusSchema = z2.strictObject({
|
|
1149
|
+
version: elenaAgentVersionSchema,
|
|
1150
|
+
authenticated: z2.boolean(),
|
|
1151
|
+
source: z2.enum(["store", "environment", "none"]),
|
|
1152
|
+
baseUrl: elenaAgentBaseUrlSchema.nullable(),
|
|
1153
|
+
credentialId: elenaAgentIdSchema.nullable(),
|
|
1154
|
+
restaurantId: elenaAgentIdSchema.nullable(),
|
|
1155
|
+
expiresAt: elenaAgentTimestampSchema.nullable()
|
|
1156
|
+
});
|
|
1157
|
+
var elenaAgentCliLogoutSchema = z2.strictObject({
|
|
1158
|
+
version: elenaAgentVersionSchema,
|
|
1159
|
+
loggedOut: z2.literal(true),
|
|
1160
|
+
remoteRevoked: z2.literal(false),
|
|
1161
|
+
environmentCredentialPresent: z2.boolean()
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
// ../shared/src/agent-contracts/catalog.ts
|
|
1165
|
+
import { z as z3 } from "zod";
|
|
1166
|
+
var elenaAgentExistingTargetSchema = z3.strictObject({
|
|
1167
|
+
kind: z3.literal("id"),
|
|
1168
|
+
id: elenaAgentIdSchema,
|
|
1169
|
+
expectedVersion: elenaAgentRevisionSchema
|
|
1170
|
+
});
|
|
1171
|
+
var elenaAgentExternalTargetSchema = z3.strictObject({
|
|
1172
|
+
kind: z3.literal("external"),
|
|
1173
|
+
namespace: elenaAgentRefSchema,
|
|
1174
|
+
key: elenaAgentRefSchema,
|
|
1175
|
+
expectedVersion: elenaAgentRevisionSchema.nullable()
|
|
1176
|
+
});
|
|
1177
|
+
var elenaAgentTargetSchema = z3.discriminatedUnion("kind", [
|
|
1178
|
+
elenaAgentExistingTargetSchema,
|
|
1179
|
+
elenaAgentExternalTargetSchema
|
|
1180
|
+
]);
|
|
1181
|
+
var elenaAgentCategoryReferenceSchema = z3.discriminatedUnion("kind", [
|
|
1182
|
+
elenaAgentExistingTargetSchema,
|
|
1183
|
+
z3.strictObject({ kind: z3.literal("ref"), ref: elenaAgentRefSchema })
|
|
1184
|
+
]);
|
|
1185
|
+
var elenaAgentDescriptionSchema = z3.discriminatedUnion("status", [
|
|
1186
|
+
z3.strictObject({ status: z3.literal("transcribed"), text: elenaAgentTextSchema }),
|
|
1187
|
+
z3.strictObject({ status: z3.literal("proposed"), text: elenaAgentTextSchema })
|
|
1188
|
+
]);
|
|
1189
|
+
var elenaAgentConfirmedPriceSchema = z3.strictObject({
|
|
1190
|
+
status: z3.literal("confirmed"),
|
|
1191
|
+
amount: elenaAgentMoneySchema,
|
|
1192
|
+
basis: z3.literal("operator")
|
|
1193
|
+
});
|
|
1194
|
+
var elenaAgentDraftPriceSchema = z3.discriminatedUnion("status", [
|
|
1195
|
+
elenaAgentConfirmedPriceSchema,
|
|
1196
|
+
z3.strictObject({
|
|
1197
|
+
status: z3.literal("unconfirmed"),
|
|
1198
|
+
reason: z3.enum(["missing", "illegible"]),
|
|
1199
|
+
rawText: z3.string().max(100).nullable()
|
|
1200
|
+
})
|
|
1201
|
+
]);
|
|
1202
|
+
var elenaAgentMediaReferenceSchema = z3.discriminatedUnion("kind", [
|
|
1203
|
+
z3.strictObject({
|
|
1204
|
+
kind: z3.literal("existing"),
|
|
1205
|
+
assetId: elenaAgentIdSchema,
|
|
1206
|
+
sha256: elenaAgentHashSchema
|
|
1207
|
+
}),
|
|
1208
|
+
z3.strictObject({
|
|
1209
|
+
kind: z3.literal("staged"),
|
|
1210
|
+
stagingId: elenaAgentIdSchema,
|
|
1211
|
+
sha256: elenaAgentHashSchema
|
|
1212
|
+
})
|
|
1213
|
+
]);
|
|
1214
|
+
var elenaAgentGallerySchema = z3.array(elenaAgentMediaReferenceSchema).max(ELENA_AGENT_LIMITS.galleryImages).refine(
|
|
1215
|
+
(images) => new Set(images.map((image) => image.kind === "existing" ? image.assetId : image.stagingId)).size === images.length,
|
|
1216
|
+
"Duplicate media reference"
|
|
1217
|
+
);
|
|
1218
|
+
var elenaAgentExtraOptionSchema = z3.strictObject({
|
|
1219
|
+
name: elenaAgentNameSchema,
|
|
1220
|
+
additionalPrice: elenaAgentNonnegativeMoneySchema,
|
|
1221
|
+
order: z3.number().int().min(0).max(1e3)
|
|
1222
|
+
});
|
|
1223
|
+
var elenaAgentExtraGroupSchema = z3.strictObject({
|
|
1224
|
+
name: elenaAgentNameSchema,
|
|
1225
|
+
required: z3.boolean(),
|
|
1226
|
+
minSelections: z3.number().int().min(0).max(ELENA_AGENT_LIMITS.optionsPerGroup),
|
|
1227
|
+
maxSelections: z3.number().int().min(1).max(ELENA_AGENT_LIMITS.optionsPerGroup),
|
|
1228
|
+
order: z3.number().int().min(0).max(1e3),
|
|
1229
|
+
options: z3.array(elenaAgentExtraOptionSchema).min(1).max(ELENA_AGENT_LIMITS.optionsPerGroup)
|
|
1230
|
+
}).superRefine((group, ctx) => {
|
|
1231
|
+
if (group.minSelections > group.maxSelections || group.maxSelections > group.options.length) {
|
|
1232
|
+
ctx.addIssue({
|
|
1233
|
+
code: "custom",
|
|
1234
|
+
path: ["maxSelections"],
|
|
1235
|
+
message: "Invalid selection bounds"
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
if (group.required && group.minSelections < 1 || !group.required && group.minSelections !== 0) {
|
|
1239
|
+
ctx.addIssue({
|
|
1240
|
+
code: "custom",
|
|
1241
|
+
path: ["minSelections"],
|
|
1242
|
+
message: "Required must match minimum selection count"
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
if (new Set(
|
|
1246
|
+
group.options.map((option) => option.name.normalize("NFKC").trim().toLocaleLowerCase("es"))
|
|
1247
|
+
).size !== group.options.length) {
|
|
1248
|
+
ctx.addIssue({ code: "custom", path: ["options"], message: "Duplicate option name" });
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
var elenaAgentExtrasSchema = z3.strictObject({
|
|
1252
|
+
confirmed: z3.literal(true),
|
|
1253
|
+
groups: z3.array(elenaAgentExtraGroupSchema).max(ELENA_AGENT_LIMITS.extraGroups)
|
|
1254
|
+
}).refine(
|
|
1255
|
+
(extras) => new Set(
|
|
1256
|
+
extras.groups.map((group) => group.name.normalize("NFKC").trim().toLocaleLowerCase("es"))
|
|
1257
|
+
).size === extras.groups.length,
|
|
1258
|
+
"Duplicate group name"
|
|
1259
|
+
);
|
|
1260
|
+
var elenaAgentCategoryDataSchema = z3.strictObject({
|
|
1261
|
+
name: elenaAgentNameSchema,
|
|
1262
|
+
description: elenaAgentDescriptionSchema.nullable(),
|
|
1263
|
+
order: z3.number().int().min(0).max(1e4)
|
|
1264
|
+
});
|
|
1265
|
+
var productFields = {
|
|
1266
|
+
type: z3.literal("menu"),
|
|
1267
|
+
name: elenaAgentNameSchema,
|
|
1268
|
+
description: elenaAgentDescriptionSchema.nullable(),
|
|
1269
|
+
category: elenaAgentCategoryReferenceSchema,
|
|
1270
|
+
available: z3.boolean(),
|
|
1271
|
+
images: elenaAgentGallerySchema,
|
|
1272
|
+
extras: elenaAgentExtrasSchema
|
|
1273
|
+
};
|
|
1274
|
+
var elenaAgentProductDataSchema = z3.strictObject({
|
|
1275
|
+
...productFields,
|
|
1276
|
+
price: elenaAgentConfirmedPriceSchema
|
|
1277
|
+
});
|
|
1278
|
+
var elenaAgentDraftProductDataSchema = z3.strictObject({
|
|
1279
|
+
...productFields,
|
|
1280
|
+
price: elenaAgentDraftPriceSchema
|
|
1281
|
+
});
|
|
1282
|
+
var elenaAgentCategoryChangeSchema = z3.strictObject({
|
|
1283
|
+
kind: z3.literal("category.upsert"),
|
|
1284
|
+
ref: elenaAgentRefSchema,
|
|
1285
|
+
target: elenaAgentTargetSchema,
|
|
1286
|
+
data: elenaAgentCategoryDataSchema
|
|
1287
|
+
});
|
|
1288
|
+
var elenaAgentProductChangeSchema = z3.strictObject({
|
|
1289
|
+
kind: z3.literal("product.upsert"),
|
|
1290
|
+
ref: elenaAgentRefSchema,
|
|
1291
|
+
target: elenaAgentTargetSchema,
|
|
1292
|
+
data: elenaAgentProductDataSchema
|
|
1293
|
+
});
|
|
1294
|
+
var elenaAgentChangeSchema = z3.discriminatedUnion("kind", [
|
|
1295
|
+
elenaAgentCategoryChangeSchema,
|
|
1296
|
+
elenaAgentProductChangeSchema
|
|
1297
|
+
]);
|
|
1298
|
+
var elenaAgentCategorySchema = z3.strictObject({
|
|
1299
|
+
id: elenaAgentIdSchema,
|
|
1300
|
+
revision: elenaAgentRevisionSchema,
|
|
1301
|
+
name: elenaAgentNameSchema,
|
|
1302
|
+
description: elenaAgentTextSchema.nullable(),
|
|
1303
|
+
order: z3.number().int().min(0).max(1e4)
|
|
1304
|
+
});
|
|
1305
|
+
var elenaAgentProductSchema = z3.strictObject({
|
|
1306
|
+
id: elenaAgentIdSchema,
|
|
1307
|
+
revision: elenaAgentRevisionSchema,
|
|
1308
|
+
type: z3.literal("menu"),
|
|
1309
|
+
name: elenaAgentNameSchema,
|
|
1310
|
+
basePrice: elenaAgentMoneySchema,
|
|
1311
|
+
description: elenaAgentTextSchema.nullable(),
|
|
1312
|
+
categoryId: elenaAgentIdSchema,
|
|
1313
|
+
available: z3.boolean(),
|
|
1314
|
+
publishedToMenu: z3.boolean(),
|
|
1315
|
+
isDraft: z3.boolean(),
|
|
1316
|
+
images: z3.array(z3.strictObject({ assetId: elenaAgentIdSchema, sha256: elenaAgentHashSchema })).max(ELENA_AGENT_LIMITS.galleryImages),
|
|
1317
|
+
/** Present on current native reads; optional only for retained v1 snapshots. */
|
|
1318
|
+
primaryImageId: elenaAgentIdSchema.nullable().optional(),
|
|
1319
|
+
extraGroups: z3.array(elenaAgentExtraGroupSchema).max(ELENA_AGENT_LIMITS.extraGroups)
|
|
1320
|
+
});
|
|
1321
|
+
var elenaAgentCategoryListSchema = z3.strictObject({
|
|
1322
|
+
version: elenaAgentVersionSchema,
|
|
1323
|
+
categories: z3.array(elenaAgentCategorySchema).max(ELENA_AGENT_LIMITS.pageSize),
|
|
1324
|
+
nextCursor: z3.string().regex(/^[a-zA-Z0-9_-]{1,512}$/).nullable()
|
|
1325
|
+
});
|
|
1326
|
+
var elenaAgentProductListSchema = z3.strictObject({
|
|
1327
|
+
version: elenaAgentVersionSchema,
|
|
1328
|
+
products: z3.array(elenaAgentProductSchema).max(ELENA_AGENT_LIMITS.pageSize),
|
|
1329
|
+
nextCursor: z3.string().regex(/^[a-zA-Z0-9_-]{1,512}$/).nullable()
|
|
1330
|
+
});
|
|
1331
|
+
var elenaAgentProductResponseSchema = z3.strictObject({
|
|
1332
|
+
version: elenaAgentVersionSchema,
|
|
1333
|
+
product: elenaAgentProductSchema,
|
|
1334
|
+
/** Authoritative editing metadata; never infer primary image from gallery order. */
|
|
1335
|
+
editable: z3.strictObject({
|
|
1336
|
+
restaurantId: elenaAgentIdSchema,
|
|
1337
|
+
productId: elenaAgentIdSchema,
|
|
1338
|
+
imageId: elenaAgentIdSchema.nullable(),
|
|
1339
|
+
categoryRevision: elenaAgentRevisionSchema,
|
|
1340
|
+
primaryImage: z3.strictObject({ assetId: elenaAgentIdSchema, sha256: elenaAgentHashSchema }).nullable().optional(),
|
|
1341
|
+
imageEditorUrl: elenaAgentHttpsUrlSchema.optional()
|
|
1342
|
+
}).optional()
|
|
1343
|
+
});
|
|
1344
|
+
var elenaAgentCategoryResponseSchema = z3.strictObject({
|
|
1345
|
+
version: elenaAgentVersionSchema,
|
|
1346
|
+
category: elenaAgentCategorySchema
|
|
1347
|
+
});
|
|
1348
|
+
var elenaAgentMediaStageRequestSchema = z3.strictObject({
|
|
1349
|
+
version: elenaAgentVersionSchema,
|
|
1350
|
+
idempotencyKey: elenaAgentRefSchema,
|
|
1351
|
+
fileName: z3.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,119}\.(?:jpg|jpeg|png|webp)$/),
|
|
1352
|
+
mimeType: z3.enum(["image/jpeg", "image/png", "image/webp"]),
|
|
1353
|
+
bytes: z3.number().int().min(1).max(ELENA_AGENT_LIMITS.mediaBytes),
|
|
1354
|
+
sha256: elenaAgentHashSchema
|
|
1355
|
+
});
|
|
1356
|
+
var elenaAgentMediaStagedSchema = z3.strictObject({
|
|
1357
|
+
version: elenaAgentVersionSchema,
|
|
1358
|
+
stagingId: elenaAgentIdSchema,
|
|
1359
|
+
sha256: elenaAgentHashSchema,
|
|
1360
|
+
mimeType: z3.enum(["image/jpeg", "image/png", "image/webp"]),
|
|
1361
|
+
bytes: z3.number().int().min(1).max(ELENA_AGENT_LIMITS.mediaBytes),
|
|
1362
|
+
expiresAt: elenaAgentTimestampSchema
|
|
1363
|
+
});
|
|
1364
|
+
var elenaAgentAssetSchema = z3.strictObject({
|
|
1365
|
+
version: elenaAgentVersionSchema,
|
|
1366
|
+
assetId: elenaAgentIdSchema,
|
|
1367
|
+
sha256: elenaAgentHashSchema,
|
|
1368
|
+
mimeType: z3.enum(["image/jpeg", "image/png", "image/webp"]),
|
|
1369
|
+
bytes: z3.number().int().min(1).max(ELENA_AGENT_LIMITS.mediaBytes)
|
|
1370
|
+
});
|
|
1371
|
+
var elenaClientAssetSchema = z3.strictObject({
|
|
1372
|
+
assetId: elenaAgentIdSchema,
|
|
1373
|
+
url: elenaAgentHttpsUrlSchema
|
|
1374
|
+
});
|
|
1375
|
+
var elenaClientCategorySchema = z3.strictObject({
|
|
1376
|
+
id: elenaAgentIdSchema,
|
|
1377
|
+
name: elenaAgentNameSchema,
|
|
1378
|
+
description: elenaAgentTextSchema.nullable(),
|
|
1379
|
+
order: z3.number().int().min(0).max(1e4)
|
|
1380
|
+
});
|
|
1381
|
+
var elenaClientProductSchema = z3.strictObject({
|
|
1382
|
+
id: elenaAgentIdSchema,
|
|
1383
|
+
type: z3.literal("menu"),
|
|
1384
|
+
name: elenaAgentNameSchema,
|
|
1385
|
+
basePrice: elenaAgentMoneySchema,
|
|
1386
|
+
description: elenaAgentTextSchema.nullable(),
|
|
1387
|
+
categoryId: elenaAgentIdSchema,
|
|
1388
|
+
available: z3.boolean(),
|
|
1389
|
+
images: z3.array(elenaClientAssetSchema).max(ELENA_AGENT_LIMITS.galleryImages),
|
|
1390
|
+
extraGroups: z3.array(elenaAgentExtraGroupSchema).max(ELENA_AGENT_LIMITS.extraGroups)
|
|
1391
|
+
});
|
|
1392
|
+
var elenaClientCatalogSchema = z3.strictObject({
|
|
1393
|
+
version: elenaAgentVersionSchema,
|
|
1394
|
+
categories: z3.array(elenaClientCategorySchema).max(ELENA_AGENT_LIMITS.pageSize),
|
|
1395
|
+
products: z3.array(elenaClientProductSchema).max(ELENA_AGENT_LIMITS.pageSize),
|
|
1396
|
+
nextCursor: z3.string().regex(/^[a-zA-Z0-9_-]{1,512}$/).nullable()
|
|
1397
|
+
});
|
|
1398
|
+
var elenaClientProductResponseSchema = z3.strictObject({
|
|
1399
|
+
version: elenaAgentVersionSchema,
|
|
1400
|
+
product: elenaClientProductSchema
|
|
1401
|
+
});
|
|
1402
|
+
var elenaClientAssetResponseSchema = z3.strictObject({
|
|
1403
|
+
version: elenaAgentVersionSchema,
|
|
1404
|
+
asset: elenaClientAssetSchema
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
// src/key-transport.ts
|
|
1408
|
+
var ElenaAgentTransportError = class extends Error {
|
|
1409
|
+
constructor(code, status = 0, retryable = false, requestId = null, retryAfterSeconds) {
|
|
1410
|
+
super(`Elena request failed (${code}).`);
|
|
1411
|
+
this.code = code;
|
|
1412
|
+
this.status = status;
|
|
1413
|
+
this.retryable = retryable;
|
|
1414
|
+
this.requestId = requestId;
|
|
1415
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
1416
|
+
}
|
|
1417
|
+
code;
|
|
1418
|
+
status;
|
|
1419
|
+
retryable;
|
|
1420
|
+
requestId;
|
|
1421
|
+
retryAfterSeconds;
|
|
1422
|
+
name = "ElenaAgentTransportError";
|
|
1423
|
+
};
|
|
1424
|
+
function parseWire(schema, value, response = false) {
|
|
1425
|
+
let unsupportedVersion = false;
|
|
1426
|
+
try {
|
|
1427
|
+
if (value !== null && typeof value === "object" && "version" in value) {
|
|
1428
|
+
unsupportedVersion = value.version !== void 0 && value.version !== "1";
|
|
1429
|
+
}
|
|
1430
|
+
const parsed = schema.safeParse(value);
|
|
1431
|
+
if (parsed.success) return parsed.data;
|
|
1432
|
+
} catch {
|
|
1433
|
+
}
|
|
1434
|
+
throw new ElenaAgentTransportError(
|
|
1435
|
+
unsupportedVersion ? "UNSUPPORTED_VERSION" : response ? "INVALID_RESPONSE" : "INVALID_REQUEST"
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
function assertOptionKeys(value, keys) {
|
|
1439
|
+
if (!value || typeof value !== "object" || Object.keys(value).some((key) => !keys.includes(key))) {
|
|
1440
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function explicitOrigin(value, allowTestHttp = false) {
|
|
1444
|
+
try {
|
|
1445
|
+
if (typeof value !== "string" || !/^https?:\/\/[^/?#@\\\s]+\/?$/.test(value)) throw 0;
|
|
1446
|
+
if (/elena_(?:sk|pk)_/i.test(value)) throw 0;
|
|
1447
|
+
const url = new URL(value);
|
|
1448
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
1449
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && (typeof process !== "undefined" && process.env.NODE_ENV === "development" || allowTestHttp && loopback)))
|
|
1450
|
+
throw 0;
|
|
1451
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== "/") throw 0;
|
|
1452
|
+
return url.origin;
|
|
1453
|
+
} catch {
|
|
1454
|
+
throw new ElenaAgentTransportError("ORIGIN_REJECTED");
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
function resourceId(value) {
|
|
1458
|
+
return parseWire(elenaAgentIdSchema, value);
|
|
1459
|
+
}
|
|
1460
|
+
function pageQuery(value = { version: "1" }) {
|
|
1461
|
+
const query = parseWire(elenaAgentPageQuerySchema, value);
|
|
1462
|
+
const params = new URLSearchParams({ version: query.version, limit: String(query.limit) });
|
|
1463
|
+
if (query.cursor !== void 0) params.set("cursor", query.cursor);
|
|
1464
|
+
return `?${params}`;
|
|
1465
|
+
}
|
|
1466
|
+
function timeoutValue(value) {
|
|
1467
|
+
if (!Number.isInteger(value) || value < 1 || value > 3e5) {
|
|
1468
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1469
|
+
}
|
|
1470
|
+
return value;
|
|
1471
|
+
}
|
|
1472
|
+
var privateCredential = /elena_sk_[A-Za-z0-9_-]{43}/;
|
|
1473
|
+
var retryCodes = {
|
|
1474
|
+
RATE_LIMITED: true,
|
|
1475
|
+
TEMPORARY_UNAVAILABLE: true
|
|
1476
|
+
};
|
|
1477
|
+
function createKeyTransport(options, header, credential) {
|
|
1478
|
+
if (options.testing !== void 0) {
|
|
1479
|
+
assertOptionKeys(options.testing, ["allowHttp"]);
|
|
1480
|
+
if (options.testing.allowHttp !== true || !options.fetch) {
|
|
1481
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
const baseUrl = explicitOrigin(options.baseUrl, options.testing?.allowHttp === true);
|
|
1485
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1486
|
+
if (typeof fetchImpl !== "function") throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1487
|
+
const defaultTimeout = timeoutValue(options.timeoutMs ?? 3e4);
|
|
1488
|
+
const retries = options.maxRetries ?? 2;
|
|
1489
|
+
if (!Number.isInteger(retries) || retries < 0 || retries > 2) {
|
|
1490
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1491
|
+
}
|
|
1492
|
+
async function request(path, schema, requestOptions = {}, body, stage = false, retryAmbiguous = true) {
|
|
1493
|
+
assertOptionKeys(requestOptions, ["signal", "timeoutMs"]);
|
|
1494
|
+
if (privateCredential.test(path) || credential && path.includes(credential) || typeof body === "string" && (privateCredential.test(body) || credential && body.includes(credential))) {
|
|
1495
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1496
|
+
}
|
|
1497
|
+
if (requestOptions.signal !== void 0 && !(requestOptions.signal instanceof AbortSignal)) {
|
|
1498
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1499
|
+
}
|
|
1500
|
+
const timeoutMs = timeoutValue(requestOptions.timeoutMs ?? (stage ? 12e4 : defaultTimeout));
|
|
1501
|
+
if (requestOptions.signal?.aborted) throw new ElenaAgentTransportError("ABORTED");
|
|
1502
|
+
const controller = new AbortController();
|
|
1503
|
+
let timedOut = false;
|
|
1504
|
+
const timer = setTimeout(() => {
|
|
1505
|
+
timedOut = true;
|
|
1506
|
+
controller.abort();
|
|
1507
|
+
}, timeoutMs);
|
|
1508
|
+
const onAbort = () => controller.abort();
|
|
1509
|
+
requestOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1510
|
+
const abortError = () => new ElenaAgentTransportError(timedOut ? "TIMEOUT" : "ABORTED");
|
|
1511
|
+
const deadline = Date.now() + timeoutMs;
|
|
1512
|
+
const bounded = (promise) => new Promise((resolve, reject) => {
|
|
1513
|
+
const abort = () => {
|
|
1514
|
+
cleanup();
|
|
1515
|
+
reject(abortError());
|
|
1516
|
+
};
|
|
1517
|
+
const cleanup = () => controller.signal.removeEventListener("abort", abort);
|
|
1518
|
+
if (controller.signal.aborted) {
|
|
1519
|
+
reject(abortError());
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
controller.signal.addEventListener("abort", abort, { once: true });
|
|
1523
|
+
promise.then(
|
|
1524
|
+
(value) => {
|
|
1525
|
+
cleanup();
|
|
1526
|
+
resolve(value);
|
|
1527
|
+
},
|
|
1528
|
+
(error) => {
|
|
1529
|
+
cleanup();
|
|
1530
|
+
reject(error);
|
|
1531
|
+
}
|
|
1532
|
+
);
|
|
1533
|
+
});
|
|
1534
|
+
const delay = (ms) => new Promise((resolve, reject) => {
|
|
1535
|
+
const abort = () => {
|
|
1536
|
+
clearTimeout(wait);
|
|
1537
|
+
controller.signal.removeEventListener("abort", abort);
|
|
1538
|
+
reject(abortError());
|
|
1539
|
+
};
|
|
1540
|
+
const wait = setTimeout(() => {
|
|
1541
|
+
controller.signal.removeEventListener("abort", abort);
|
|
1542
|
+
resolve();
|
|
1543
|
+
}, ms);
|
|
1544
|
+
controller.signal.addEventListener("abort", abort, { once: true });
|
|
1545
|
+
if (controller.signal.aborted) abort();
|
|
1546
|
+
});
|
|
1547
|
+
try {
|
|
1548
|
+
for (let attempt = 0; ; attempt++) {
|
|
1549
|
+
let failure;
|
|
1550
|
+
let retryDelay = 100 * 2 ** attempt;
|
|
1551
|
+
try {
|
|
1552
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
1553
|
+
if (header) headers.set(header, credential);
|
|
1554
|
+
if (typeof body === "string") headers.set("Content-Type", "application/json");
|
|
1555
|
+
const response = await bounded(
|
|
1556
|
+
Promise.resolve().then(() => {
|
|
1557
|
+
if (controller.signal.aborted) throw abortError();
|
|
1558
|
+
return fetchImpl(`${baseUrl}${path}`, {
|
|
1559
|
+
method: body === void 0 ? "GET" : "POST",
|
|
1560
|
+
headers,
|
|
1561
|
+
body: typeof body === "function" ? body() : body,
|
|
1562
|
+
credentials: "omit",
|
|
1563
|
+
redirect: "error",
|
|
1564
|
+
referrerPolicy: "no-referrer",
|
|
1565
|
+
cache: "no-store",
|
|
1566
|
+
signal: controller.signal
|
|
1567
|
+
});
|
|
1568
|
+
}).catch(() => {
|
|
1569
|
+
throw new ElenaAgentTransportError("NETWORK_ERROR", 0, retryAmbiguous);
|
|
1570
|
+
})
|
|
1571
|
+
);
|
|
1572
|
+
if (response.redirected || response.status >= 300 && response.status < 400) {
|
|
1573
|
+
throw new ElenaAgentTransportError("REDIRECT_REJECTED", response.status);
|
|
1574
|
+
}
|
|
1575
|
+
if (response.url && new URL(response.url).origin !== baseUrl) {
|
|
1576
|
+
throw new ElenaAgentTransportError("ORIGIN_REJECTED");
|
|
1577
|
+
}
|
|
1578
|
+
const text = await bounded(
|
|
1579
|
+
Promise.resolve().then(() => response.text()).catch(() => {
|
|
1580
|
+
throw new ElenaAgentTransportError("NETWORK_ERROR", 0, retryAmbiguous);
|
|
1581
|
+
})
|
|
1582
|
+
);
|
|
1583
|
+
if (privateCredential.test(text) || credential && text.includes(credential)) {
|
|
1584
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE", response.status);
|
|
1585
|
+
}
|
|
1586
|
+
let payload;
|
|
1587
|
+
try {
|
|
1588
|
+
payload = JSON.parse(text);
|
|
1589
|
+
} catch {
|
|
1590
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE", response.status);
|
|
1591
|
+
}
|
|
1592
|
+
const decodedText = JSON.stringify(payload);
|
|
1593
|
+
if (privateCredential.test(decodedText) || credential && decodedText.includes(credential)) {
|
|
1594
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE", response.status);
|
|
1595
|
+
}
|
|
1596
|
+
if (response.ok) return parseWire(schema, payload, true);
|
|
1597
|
+
const wire = parseWire(elenaAgentErrorSchema, payload, true).error;
|
|
1598
|
+
const retryable = retryCodes[wire.code] === true && wire.retryable && (wire.code === "RATE_LIMITED" && response.status === 429 || wire.code === "TEMPORARY_UNAVAILABLE" && response.status === 503);
|
|
1599
|
+
failure = new ElenaAgentTransportError(
|
|
1600
|
+
wire.code,
|
|
1601
|
+
response.status,
|
|
1602
|
+
retryable,
|
|
1603
|
+
wire.requestId,
|
|
1604
|
+
wire.retryAfterSeconds
|
|
1605
|
+
);
|
|
1606
|
+
const retryAfter = response.headers.get("retry-after");
|
|
1607
|
+
let headerDelay = 0;
|
|
1608
|
+
if (retryAfter !== null) {
|
|
1609
|
+
const seconds = /^\d+$/.test(retryAfter) ? Number(retryAfter) : (Date.parse(retryAfter) - Date.now()) / 1e3;
|
|
1610
|
+
if (!Number.isFinite(seconds) || seconds < 0 || seconds > 3600) {
|
|
1611
|
+
throw new ElenaAgentTransportError(
|
|
1612
|
+
wire.code,
|
|
1613
|
+
response.status,
|
|
1614
|
+
false,
|
|
1615
|
+
wire.requestId,
|
|
1616
|
+
wire.retryAfterSeconds
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
headerDelay = Math.ceil(seconds * 1e3);
|
|
1620
|
+
}
|
|
1621
|
+
retryDelay = Math.max(retryDelay, headerDelay, (wire.retryAfterSeconds ?? 0) * 1e3);
|
|
1622
|
+
} catch (error) {
|
|
1623
|
+
if (controller.signal.aborted) throw abortError();
|
|
1624
|
+
failure = error instanceof ElenaAgentTransportError ? error : new ElenaAgentTransportError("NETWORK_ERROR", 0, retryAmbiguous);
|
|
1625
|
+
}
|
|
1626
|
+
if (!failure.retryable || attempt >= retries || Date.now() + retryDelay >= deadline)
|
|
1627
|
+
throw failure;
|
|
1628
|
+
await delay(retryDelay);
|
|
1629
|
+
}
|
|
1630
|
+
} finally {
|
|
1631
|
+
clearTimeout(timer);
|
|
1632
|
+
requestOptions.signal?.removeEventListener("abort", onAbort);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
return { request };
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// src/client-key.ts
|
|
1639
|
+
function createElenaClientKeyClient(options) {
|
|
1640
|
+
assertOptionKeys(options, [
|
|
1641
|
+
"baseUrl",
|
|
1642
|
+
"clientKey",
|
|
1643
|
+
"fetch",
|
|
1644
|
+
"timeoutMs",
|
|
1645
|
+
"maxRetries",
|
|
1646
|
+
"testing"
|
|
1647
|
+
]);
|
|
1648
|
+
if (!elenaClientKeySchema.safeParse(options.clientKey).success) {
|
|
1649
|
+
throw new ElenaAgentTransportError("CREDENTIAL_WRONG_FAMILY");
|
|
1650
|
+
}
|
|
1651
|
+
const { request } = createKeyTransport(options, "x-elena-client-key", options.clientKey);
|
|
1652
|
+
const root = "/api/v1/public/sdk";
|
|
1653
|
+
return Object.freeze({
|
|
1654
|
+
context: Object.freeze({
|
|
1655
|
+
get: (options2) => request(`${root}/context`, elenaClientContextSchema, options2)
|
|
1656
|
+
}),
|
|
1657
|
+
catalog: Object.freeze({
|
|
1658
|
+
list: (query, options2) => request(`${root}/catalog${pageQuery(query)}`, elenaClientCatalogSchema, options2),
|
|
1659
|
+
getProduct: async (id, options2) => {
|
|
1660
|
+
const expectedId = resourceId(id);
|
|
1661
|
+
const result = await request(
|
|
1662
|
+
`${root}/catalog/products/${expectedId}`,
|
|
1663
|
+
elenaClientProductResponseSchema,
|
|
1664
|
+
options2
|
|
1665
|
+
);
|
|
1666
|
+
if (result.product.id !== expectedId)
|
|
1667
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1668
|
+
return result;
|
|
1669
|
+
}
|
|
1670
|
+
}),
|
|
1671
|
+
assets: Object.freeze({
|
|
1672
|
+
get: async (id, options2) => {
|
|
1673
|
+
const expectedId = resourceId(id);
|
|
1674
|
+
const result = await request(
|
|
1675
|
+
`${root}/assets/${expectedId}`,
|
|
1676
|
+
elenaClientAssetResponseSchema,
|
|
1677
|
+
options2
|
|
1678
|
+
);
|
|
1679
|
+
if (result.asset.assetId !== expectedId)
|
|
1680
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1681
|
+
return result;
|
|
1682
|
+
}
|
|
1683
|
+
})
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// ../shared/src/agent-contracts/public-connection.ts
|
|
1688
|
+
import { z as z4 } from "zod";
|
|
1689
|
+
var ELENA_PUBLIC_CONNECTION_DISCOVERY_ROOT = "/api/v1/public/catalog-connections";
|
|
1690
|
+
var elenaPublicConnectionSlugSchema = z4.string().min(1).max(120).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
|
|
1691
|
+
var ttl = z4.number().int().min(3600).max(ELENA_AGENT_LIMITS.keyMaxTtlSeconds).default(ELENA_AGENT_LIMITS.keyDefaultTtlSeconds);
|
|
1692
|
+
var mutation = {
|
|
1693
|
+
version: elenaAgentVersionSchema,
|
|
1694
|
+
// Zero asserts that no connection has ever been created for this restaurant.
|
|
1695
|
+
expectedRevision: z4.number().int().min(0).max(Number.MAX_SAFE_INTEGER - 1)
|
|
1696
|
+
};
|
|
1697
|
+
var elenaPublicConnectionMutationSchema = z4.discriminatedUnion("action", [
|
|
1698
|
+
z4.strictObject({ ...mutation, action: z4.literal("enable"), ttlSeconds: ttl }),
|
|
1699
|
+
z4.strictObject({ ...mutation, action: z4.literal("renew"), ttlSeconds: ttl }),
|
|
1700
|
+
z4.strictObject({ ...mutation, action: z4.literal("disable") }),
|
|
1701
|
+
z4.strictObject({ ...mutation, action: z4.literal("revoke") })
|
|
1702
|
+
]);
|
|
1703
|
+
var elenaPublicConnectionMetadataSchema = z4.strictObject({
|
|
1704
|
+
revision: elenaAgentRevisionSchema,
|
|
1705
|
+
status: z4.enum(["published", "disabled", "revoked"]),
|
|
1706
|
+
expiresAt: elenaAgentTimestampSchema
|
|
1707
|
+
});
|
|
1708
|
+
var elenaPublicConnectionManagementSchema = z4.strictObject({
|
|
1709
|
+
version: elenaAgentVersionSchema,
|
|
1710
|
+
publication: elenaPublicConnectionMetadataSchema.nullable()
|
|
1711
|
+
});
|
|
1712
|
+
var elenaPublicConnectionSchema = z4.strictObject({
|
|
1713
|
+
version: elenaAgentVersionSchema,
|
|
1714
|
+
apiOrigin: elenaAgentBaseUrlSchema,
|
|
1715
|
+
clientKey: elenaManagedClientKeySchema,
|
|
1716
|
+
publication: z4.strictObject({
|
|
1717
|
+
revision: elenaAgentRevisionSchema,
|
|
1718
|
+
expiresAt: elenaAgentTimestampSchema
|
|
1719
|
+
}),
|
|
1720
|
+
context: elenaClientContextSchema
|
|
1721
|
+
});
|
|
1722
|
+
|
|
1723
|
+
// src/public-connection.ts
|
|
1724
|
+
async function discoverElenaPublicConnection(options, slug, requestOptions) {
|
|
1725
|
+
assertOptionKeys(options, ["baseUrl", "fetch", "timeoutMs", "maxRetries", "testing"]);
|
|
1726
|
+
const identifier = parseWire(elenaPublicConnectionSlugSchema, slug);
|
|
1727
|
+
const result = await createKeyTransport(options, null, "").request(
|
|
1728
|
+
`${ELENA_PUBLIC_CONNECTION_DISCOVERY_ROOT}/${identifier}`,
|
|
1729
|
+
elenaPublicConnectionSchema,
|
|
1730
|
+
requestOptions
|
|
1731
|
+
);
|
|
1732
|
+
if (explicitOrigin(result.apiOrigin) !== explicitOrigin(options.baseUrl, options.testing?.allowHttp) || result.context.restaurant.slug !== identifier)
|
|
1733
|
+
throw new ElenaAgentTransportError("ORIGIN_REJECTED");
|
|
1734
|
+
return result;
|
|
1735
|
+
}
|
|
1736
|
+
export {
|
|
1737
|
+
AccessResource,
|
|
1738
|
+
AssetsResource,
|
|
1739
|
+
BusinessScope,
|
|
1740
|
+
CatalogResource,
|
|
1741
|
+
ChatResource,
|
|
1742
|
+
ContinuationsResource,
|
|
1743
|
+
ElenaAgentTransportError,
|
|
1744
|
+
ElenaApiError,
|
|
1745
|
+
ElenaCheckoutValidationError,
|
|
1746
|
+
ElenaHttpClient,
|
|
1747
|
+
ElenaNetworkError,
|
|
1748
|
+
ElenaRequestAbortedError,
|
|
1749
|
+
ElenaResponseError,
|
|
1750
|
+
ElenaTimeoutError,
|
|
1751
|
+
EventsResource,
|
|
1752
|
+
JourneyConflictError,
|
|
1753
|
+
JourneyResource,
|
|
1754
|
+
OrdersResource,
|
|
1755
|
+
PresenceResource,
|
|
1756
|
+
RealtimeResource,
|
|
1757
|
+
ReceiptsResource,
|
|
1758
|
+
TablesResource,
|
|
1759
|
+
TicketsResource,
|
|
1760
|
+
buildOrderItems,
|
|
1761
|
+
createElenaClientKeyClient,
|
|
1762
|
+
createElenaJourneyClient,
|
|
1763
|
+
createElenaPublicClient,
|
|
1764
|
+
discoverElenaPublicConnection
|
|
1765
|
+
};
|
|
1766
|
+
//# sourceMappingURL=index.js.map
|