@emeryld/rrroutes-client 2.2.14 → 2.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +367 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +371 -121
- package/dist/index.mjs.map +1 -1
- package/dist/routesV3.client.index.d.ts +2 -2
- package/dist/routesV3.client.types.d.ts +85 -42
- package/dist/sockets/socket.client.index.d.ts +2 -2
- package/dist/sockets/socketedRoute/socket.client.helper.d.ts +5 -5
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -69,22 +69,20 @@ var defaultFetcher = async (req) => {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
// src/routesV3.client.index.ts
|
|
72
|
-
var
|
|
72
|
+
var import_rrroutes_contract = require("@emeryld/rrroutes-contract");
|
|
73
73
|
var import_react_query = require("@tanstack/react-query");
|
|
74
|
+
var import_react = require("react");
|
|
74
75
|
var import_zod = require("zod");
|
|
75
|
-
var import_rrroutes_contract = require("@emeryld/rrroutes-contract");
|
|
76
76
|
var toUpper = (m) => m.toUpperCase();
|
|
77
|
-
var
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
77
|
+
var paginationQueryShape = {
|
|
78
|
+
pagination_cursor: import_zod.z.string().optional(),
|
|
79
|
+
pagination_limit: import_zod.z.coerce.number().min(1).max(100).default(20)
|
|
80
|
+
};
|
|
81
|
+
var defaultFeedQuerySchema = import_zod.z.object(paginationQueryShape);
|
|
81
82
|
var defaultFeedOutputSchema = import_zod.z.object({
|
|
82
83
|
items: import_zod.z.array(import_zod.z.unknown()),
|
|
83
84
|
nextCursor: import_zod.z.string().optional()
|
|
84
85
|
});
|
|
85
|
-
function zParse(value, schema) {
|
|
86
|
-
return schema ? schema.parse(value) : value;
|
|
87
|
-
}
|
|
88
86
|
function toSearchString(query) {
|
|
89
87
|
if (!query) return "";
|
|
90
88
|
const params = new URLSearchParams();
|
|
@@ -183,41 +181,53 @@ function extractArgs(args) {
|
|
|
183
181
|
function toArgsTuple(args) {
|
|
184
182
|
return typeof args === "undefined" ? [] : [args];
|
|
185
183
|
}
|
|
184
|
+
function getZodShape(schema) {
|
|
185
|
+
const shapeOrGetter = schema.shape ? schema.shape : schema._def?.shape?.();
|
|
186
|
+
if (!shapeOrGetter) return {};
|
|
187
|
+
return typeof shapeOrGetter === "function" ? shapeOrGetter.call(schema) : shapeOrGetter;
|
|
188
|
+
}
|
|
186
189
|
function augmentFeedQuerySchema(schema) {
|
|
187
190
|
if (!schema) return defaultFeedQuerySchema;
|
|
188
|
-
if (schema instanceof import_zod.z.ZodObject) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
limit: defaultFeedQuerySchema.shape.limit
|
|
194
|
-
});
|
|
191
|
+
if (!(schema instanceof import_zod.z.ZodObject)) {
|
|
192
|
+
console.warn(
|
|
193
|
+
"Feed queries must be a ZodObject; default pagination applied."
|
|
194
|
+
);
|
|
195
|
+
return defaultFeedQuerySchema;
|
|
195
196
|
}
|
|
196
|
-
return
|
|
197
|
+
return schema.extend(paginationQueryShape);
|
|
197
198
|
}
|
|
198
199
|
function augmentFeedOutputSchema(schema) {
|
|
199
200
|
if (!schema) return defaultFeedOutputSchema;
|
|
200
|
-
if (schema instanceof import_zod.z.
|
|
201
|
-
const shape = schema.shape ? schema.shape : schema._def?.shape?.();
|
|
202
|
-
const hasItems = Boolean(shape?.items);
|
|
203
|
-
if (hasItems) return schema.extend({ nextCursor: import_zod.z.string().optional() });
|
|
201
|
+
if (schema instanceof import_zod.z.ZodArray) {
|
|
204
202
|
return import_zod.z.object({
|
|
205
|
-
items:
|
|
203
|
+
items: schema,
|
|
206
204
|
nextCursor: import_zod.z.string().optional()
|
|
207
205
|
});
|
|
208
206
|
}
|
|
209
|
-
if (schema instanceof import_zod.z.
|
|
207
|
+
if (schema instanceof import_zod.z.ZodObject) {
|
|
208
|
+
const shape = getZodShape(schema);
|
|
209
|
+
if (shape?.items) {
|
|
210
|
+
return schema.extend({
|
|
211
|
+
nextCursor: import_zod.z.string().optional()
|
|
212
|
+
});
|
|
213
|
+
}
|
|
210
214
|
return import_zod.z.object({
|
|
211
|
-
items: schema,
|
|
215
|
+
items: import_zod.z.array(schema),
|
|
212
216
|
nextCursor: import_zod.z.string().optional()
|
|
213
217
|
});
|
|
214
218
|
}
|
|
215
|
-
return
|
|
219
|
+
return import_zod.z.object({
|
|
220
|
+
items: import_zod.z.array(schema),
|
|
221
|
+
nextCursor: import_zod.z.string().optional()
|
|
222
|
+
});
|
|
216
223
|
}
|
|
217
224
|
function buildUrl(leaf, baseUrl, params, query) {
|
|
218
|
-
const normalizedParams =
|
|
219
|
-
const normalizedQuery =
|
|
220
|
-
const path = (0, import_rrroutes_contract.compilePath)(
|
|
225
|
+
const normalizedParams = leaf.cfg.paramsSchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.paramsSchema, params) : {};
|
|
226
|
+
const normalizedQuery = leaf.cfg.querySchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.querySchema, query) : {};
|
|
227
|
+
const path = (0, import_rrroutes_contract.compilePath)(
|
|
228
|
+
leaf.path,
|
|
229
|
+
normalizedParams ?? {}
|
|
230
|
+
);
|
|
221
231
|
const url = `${baseUrl ?? ""}${path}${toSearchString(normalizedQuery)}`;
|
|
222
232
|
return { url, normalizedQuery, normalizedParams };
|
|
223
233
|
}
|
|
@@ -225,10 +235,13 @@ function createRouteClient(opts) {
|
|
|
225
235
|
const queryClient = opts.queryClient;
|
|
226
236
|
const fetcher = opts.fetcher ?? defaultFetcher;
|
|
227
237
|
const baseUrl = opts.baseUrl;
|
|
228
|
-
const cursorParam = opts.cursorParam ?? "
|
|
238
|
+
const cursorParam = opts.cursorParam ?? "pagination_cursor";
|
|
229
239
|
const getNextCursor = opts.getNextCursor ?? defaultGetNextCursor;
|
|
230
240
|
const environment = opts.environment ?? void 0;
|
|
231
|
-
const { emit: emitDebug, mode: debugMode } = createDebugEmitter(
|
|
241
|
+
const { emit: emitDebug, mode: debugMode } = createDebugEmitter(
|
|
242
|
+
opts.debug,
|
|
243
|
+
environment
|
|
244
|
+
);
|
|
232
245
|
const isVerboseDebug = debugMode === "complete";
|
|
233
246
|
const decorateDebugEvent = (event, details) => {
|
|
234
247
|
if (!isVerboseDebug || !details) return event;
|
|
@@ -242,11 +255,12 @@ function createRouteClient(opts) {
|
|
|
242
255
|
function buildInternal(leaf, rqOpts, meta) {
|
|
243
256
|
const isGet = leaf.method === "get";
|
|
244
257
|
const isFeed = !!leaf.cfg.feed;
|
|
258
|
+
const rawLeafCfg = leaf.cfg;
|
|
245
259
|
const leafCfg = isFeed ? {
|
|
246
|
-
...
|
|
247
|
-
querySchema: augmentFeedQuerySchema(
|
|
248
|
-
outputSchema: augmentFeedOutputSchema(
|
|
249
|
-
} :
|
|
260
|
+
...rawLeafCfg,
|
|
261
|
+
querySchema: augmentFeedQuerySchema(rawLeafCfg.querySchema),
|
|
262
|
+
outputSchema: augmentFeedOutputSchema(rawLeafCfg.outputSchema)
|
|
263
|
+
} : rawLeafCfg;
|
|
250
264
|
const method = toUpper(leaf.method);
|
|
251
265
|
const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
|
|
252
266
|
const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;
|
|
@@ -302,9 +316,13 @@ function createRouteClient(opts) {
|
|
|
302
316
|
const acceptsBody = Boolean(leafCfg.bodySchema);
|
|
303
317
|
const requiresBody = options?.requireBody ?? (!isGet && acceptsBody);
|
|
304
318
|
if (typeof options?.body !== "undefined") {
|
|
305
|
-
const normalizedBody =
|
|
319
|
+
const normalizedBody = leafCfg.bodySchema ? (0, import_rrroutes_contract.lowProfileParse)(leafCfg.bodySchema, options.body) : void 0;
|
|
306
320
|
const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
|
|
307
|
-
|
|
321
|
+
if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
|
|
322
|
+
payload = toFormData(normalizedBody);
|
|
323
|
+
} else {
|
|
324
|
+
payload = normalizedBody;
|
|
325
|
+
}
|
|
308
326
|
} else if (requiresBody) {
|
|
309
327
|
throw new Error("Body is required when invoking a mutation fetch.");
|
|
310
328
|
}
|
|
@@ -327,7 +345,7 @@ function createRouteClient(opts) {
|
|
|
327
345
|
const out = await fetcher(
|
|
328
346
|
payload === void 0 ? { url, method } : { url, method, body: payload }
|
|
329
347
|
);
|
|
330
|
-
const parsed =
|
|
348
|
+
const parsed = leafCfg.outputSchema ? (0, import_rrroutes_contract.lowProfileParse)(leafCfg.outputSchema, out) : void 0;
|
|
331
349
|
emit(
|
|
332
350
|
decorateDebugEvent(
|
|
333
351
|
{
|
|
@@ -338,7 +356,11 @@ function createRouteClient(opts) {
|
|
|
338
356
|
leaf: leafLabel,
|
|
339
357
|
durationMs: Date.now() - startedAt
|
|
340
358
|
},
|
|
341
|
-
isVerboseDebug ? {
|
|
359
|
+
isVerboseDebug ? {
|
|
360
|
+
params: normalizedParams,
|
|
361
|
+
query: normalizedQuery,
|
|
362
|
+
output: parsed
|
|
363
|
+
} : void 0
|
|
342
364
|
)
|
|
343
365
|
);
|
|
344
366
|
options?.onReceive?.(parsed);
|
|
@@ -369,7 +391,11 @@ function createRouteClient(opts) {
|
|
|
369
391
|
const hasBodyCandidate = acceptsBody && tupleLength > maybeBodyIndex;
|
|
370
392
|
const body = hasBodyCandidate ? tupleWithBody[tupleLength - 1] : void 0;
|
|
371
393
|
const tuple = hasBodyCandidate ? tupleWithBody.slice(0, tupleLength - 1) : tupleWithBody;
|
|
372
|
-
return fetchEndpoint(tuple, {
|
|
394
|
+
return fetchEndpoint(tuple, {
|
|
395
|
+
body,
|
|
396
|
+
onReceive: buildOnReceive,
|
|
397
|
+
requireBody: false
|
|
398
|
+
});
|
|
373
399
|
};
|
|
374
400
|
if (isGet && isFeed) {
|
|
375
401
|
const useEndpoint2 = (...useArgs) => {
|
|
@@ -380,13 +406,10 @@ function createRouteClient(opts) {
|
|
|
380
406
|
const query = args?.query;
|
|
381
407
|
const buildOptions = rqOpts ?? {};
|
|
382
408
|
const listenersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
383
|
-
const notifyOnReceive = (0, import_react.useCallback)(
|
|
384
|
-
(data)
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
},
|
|
388
|
-
[]
|
|
389
|
-
);
|
|
409
|
+
const notifyOnReceive = (0, import_react.useCallback)((data) => {
|
|
410
|
+
buildOptions?.onReceive?.(data);
|
|
411
|
+
listenersRef.current.forEach((listener) => listener(data));
|
|
412
|
+
}, []);
|
|
390
413
|
const registerOnReceive = (0, import_react.useCallback)(
|
|
391
414
|
(listener) => {
|
|
392
415
|
listenersRef.current.add(listener);
|
|
@@ -402,24 +425,27 @@ function createRouteClient(opts) {
|
|
|
402
425
|
params,
|
|
403
426
|
query
|
|
404
427
|
);
|
|
405
|
-
const queryResult = (0, import_react_query.useInfiniteQuery)(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
428
|
+
const queryResult = (0, import_react_query.useInfiniteQuery)(
|
|
429
|
+
{
|
|
430
|
+
...buildOptions,
|
|
431
|
+
queryKey: getQueryKeys(...tuple),
|
|
432
|
+
initialPageParam: void 0,
|
|
433
|
+
getNextPageParam: (lastPage) => getNextCursor(lastPage),
|
|
434
|
+
placeholderData: import_react_query.keepPreviousData,
|
|
435
|
+
queryFn: ({ pageParam }) => {
|
|
436
|
+
const pageQuery = {
|
|
437
|
+
...normalizedQuery,
|
|
438
|
+
...pageParam ? { [cursorParam]: pageParam } : {}
|
|
439
|
+
};
|
|
440
|
+
return fetchEndpoint(tuple, {
|
|
441
|
+
queryOverride: pageQuery,
|
|
442
|
+
onReceive: notifyOnReceive
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
// NOTE: TData is InfiniteData<T>, so we don't need a select here.
|
|
446
|
+
},
|
|
447
|
+
queryClient
|
|
448
|
+
);
|
|
423
449
|
return { ...queryResult, onReceive: registerOnReceive };
|
|
424
450
|
};
|
|
425
451
|
return {
|
|
@@ -439,13 +465,10 @@ function createRouteClient(opts) {
|
|
|
439
465
|
const query = args?.query;
|
|
440
466
|
const buildOptions = rqOpts ?? {};
|
|
441
467
|
const listenersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
442
|
-
const notifyOnReceive = (0, import_react.useCallback)(
|
|
443
|
-
(data)
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
},
|
|
447
|
-
[]
|
|
448
|
-
);
|
|
468
|
+
const notifyOnReceive = (0, import_react.useCallback)((data) => {
|
|
469
|
+
buildOptions?.onReceive?.(data);
|
|
470
|
+
listenersRef.current.forEach((listener) => listener(data));
|
|
471
|
+
}, []);
|
|
449
472
|
const registerOnReceive = (0, import_react.useCallback)(
|
|
450
473
|
(listener) => {
|
|
451
474
|
listenersRef.current.add(listener);
|
|
@@ -456,14 +479,17 @@ function createRouteClient(opts) {
|
|
|
456
479
|
[]
|
|
457
480
|
);
|
|
458
481
|
buildUrl({ ...leaf, cfg: leafCfg }, baseUrl, params, query);
|
|
459
|
-
const queryResult = (0, import_react_query.useQuery)(
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
482
|
+
const queryResult = (0, import_react_query.useQuery)(
|
|
483
|
+
{
|
|
484
|
+
...buildOptions,
|
|
485
|
+
queryKey: getQueryKeys(...tuple),
|
|
486
|
+
placeholderData: import_react_query.keepPreviousData,
|
|
487
|
+
queryFn: () => fetchEndpoint(tuple, {
|
|
488
|
+
onReceive: notifyOnReceive
|
|
489
|
+
})
|
|
490
|
+
},
|
|
491
|
+
queryClient
|
|
492
|
+
);
|
|
467
493
|
return { ...queryResult, onReceive: registerOnReceive };
|
|
468
494
|
};
|
|
469
495
|
return {
|
|
@@ -497,21 +523,29 @@ function createRouteClient(opts) {
|
|
|
497
523
|
const notifyListeners = (0, import_react.useCallback)((data) => {
|
|
498
524
|
listenersRef.current.forEach((listener) => listener(data));
|
|
499
525
|
}, []);
|
|
500
|
-
const registerOnReceive = (0, import_react.useCallback)(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
526
|
+
const registerOnReceive = (0, import_react.useCallback)(
|
|
527
|
+
(listener) => {
|
|
528
|
+
listenersRef.current.add(listener);
|
|
529
|
+
return () => {
|
|
530
|
+
listenersRef.current.delete(listener);
|
|
531
|
+
};
|
|
532
|
+
},
|
|
533
|
+
[]
|
|
534
|
+
);
|
|
535
|
+
const mutationResult = (0, import_react_query.useMutation)(
|
|
536
|
+
{
|
|
537
|
+
...mutationBuildOptions,
|
|
538
|
+
mutationKey: getQueryKeys(...tuple),
|
|
539
|
+
mutationFn: async (body) => {
|
|
540
|
+
const result = await fetchMutation(
|
|
541
|
+
...[...tuple, body]
|
|
542
|
+
);
|
|
543
|
+
notifyListeners(result);
|
|
544
|
+
return result;
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
queryClient
|
|
548
|
+
);
|
|
515
549
|
return { ...mutationResult, onReceive: registerOnReceive };
|
|
516
550
|
};
|
|
517
551
|
return {
|
|
@@ -522,25 +556,163 @@ function createRouteClient(opts) {
|
|
|
522
556
|
fetch: fetchMutation
|
|
523
557
|
};
|
|
524
558
|
}
|
|
559
|
+
const fetchRaw = async (input) => {
|
|
560
|
+
const { path, method, query, body, params } = input;
|
|
561
|
+
if (!path || typeof path !== "string") {
|
|
562
|
+
throw new Error("fetch(path, ...) requires a non-empty string path.");
|
|
563
|
+
}
|
|
564
|
+
if (!method) {
|
|
565
|
+
throw new Error("fetch(path, method, ...) requires an HTTP method.");
|
|
566
|
+
}
|
|
567
|
+
const methodLower = String(method).toLowerCase();
|
|
568
|
+
const methodUpper = toUpper(methodLower);
|
|
569
|
+
const flatQuery = normalizeFlatQuery(query);
|
|
570
|
+
const search = toSearchString(flatQuery);
|
|
571
|
+
const compiledPath = compileRawPath(path, params);
|
|
572
|
+
const url = `${baseUrl ?? ""}${compiledPath}${search}`;
|
|
573
|
+
const leafLabel = `${methodUpper} ${path}`;
|
|
574
|
+
const startedAt = Date.now();
|
|
575
|
+
const detail = isVerboseDebug ? { params, query: flatQuery } : void 0;
|
|
576
|
+
emitDebug(
|
|
577
|
+
decorateDebugEvent(
|
|
578
|
+
{
|
|
579
|
+
type: "fetch",
|
|
580
|
+
stage: "start",
|
|
581
|
+
method: methodUpper,
|
|
582
|
+
url,
|
|
583
|
+
leaf: leafLabel,
|
|
584
|
+
...body !== void 0 ? { body } : {}
|
|
585
|
+
},
|
|
586
|
+
detail
|
|
587
|
+
)
|
|
588
|
+
);
|
|
589
|
+
try {
|
|
590
|
+
const out = await fetcher(
|
|
591
|
+
body === void 0 ? { url, method: methodUpper } : { url, method: methodUpper, body }
|
|
592
|
+
);
|
|
593
|
+
emitDebug(
|
|
594
|
+
decorateDebugEvent(
|
|
595
|
+
{
|
|
596
|
+
type: "fetch",
|
|
597
|
+
stage: "success",
|
|
598
|
+
method: methodUpper,
|
|
599
|
+
url,
|
|
600
|
+
leaf: leafLabel,
|
|
601
|
+
durationMs: Date.now() - startedAt
|
|
602
|
+
},
|
|
603
|
+
isVerboseDebug ? { params, query: flatQuery, output: out } : void 0
|
|
604
|
+
)
|
|
605
|
+
);
|
|
606
|
+
return out;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
emitDebug(
|
|
609
|
+
decorateDebugEvent(
|
|
610
|
+
{
|
|
611
|
+
type: "fetch",
|
|
612
|
+
stage: "error",
|
|
613
|
+
method: methodUpper,
|
|
614
|
+
url,
|
|
615
|
+
leaf: leafLabel,
|
|
616
|
+
durationMs: Date.now() - startedAt,
|
|
617
|
+
...body !== void 0 ? { body } : {},
|
|
618
|
+
error
|
|
619
|
+
},
|
|
620
|
+
detail
|
|
621
|
+
)
|
|
622
|
+
);
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
625
|
+
};
|
|
525
626
|
return {
|
|
526
627
|
queryClient,
|
|
527
628
|
invalidate,
|
|
629
|
+
fetch: fetchRaw,
|
|
528
630
|
build: buildInternal
|
|
529
631
|
};
|
|
530
632
|
}
|
|
531
633
|
function buildRouter(routeClient, routes) {
|
|
532
634
|
const buildLeaf = routeClient.build;
|
|
533
|
-
return ((key, opts, meta) => buildLeaf(
|
|
635
|
+
return ((key, opts, meta) => buildLeaf(
|
|
636
|
+
routes[key],
|
|
637
|
+
opts,
|
|
638
|
+
meta
|
|
639
|
+
));
|
|
534
640
|
}
|
|
535
641
|
function toFormData(body) {
|
|
536
642
|
const fd = new FormData();
|
|
537
643
|
for (const [k, v] of Object.entries(body ?? {})) {
|
|
538
644
|
if (v == null) continue;
|
|
539
|
-
if (Array.isArray(v))
|
|
645
|
+
if (Array.isArray(v))
|
|
646
|
+
v.forEach((item, i) => fd.append(`${k}[${i}]`, item));
|
|
540
647
|
else fd.append(k, v);
|
|
541
648
|
}
|
|
542
649
|
return fd;
|
|
543
650
|
}
|
|
651
|
+
function getPathParamNames(path) {
|
|
652
|
+
const names = /* @__PURE__ */ new Set();
|
|
653
|
+
const re = /:([A-Za-z0-9_]+)/g;
|
|
654
|
+
let match;
|
|
655
|
+
while ((match = re.exec(path)) !== null) {
|
|
656
|
+
names.add(match[1]);
|
|
657
|
+
}
|
|
658
|
+
return names;
|
|
659
|
+
}
|
|
660
|
+
function normalizeFlatQuery(query) {
|
|
661
|
+
if (query == null) return void 0;
|
|
662
|
+
if (typeof query !== "object" || Array.isArray(query)) {
|
|
663
|
+
throw new Error("Query must be a plain object (Record<string, string>).");
|
|
664
|
+
}
|
|
665
|
+
const result = {};
|
|
666
|
+
for (const [k, v] of Object.entries(query)) {
|
|
667
|
+
if (v == null) continue;
|
|
668
|
+
if (typeof v !== "string") {
|
|
669
|
+
throw new Error(
|
|
670
|
+
`Query param "${k}" must be a string; received type "${typeof v}".`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
result[k] = v;
|
|
674
|
+
}
|
|
675
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
676
|
+
}
|
|
677
|
+
function compileRawPath(path, params) {
|
|
678
|
+
const placeholders = getPathParamNames(path);
|
|
679
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
680
|
+
if (placeholders.size > 0) {
|
|
681
|
+
throw new Error(
|
|
682
|
+
`Missing path parameters for "${path}": ${[...placeholders].join(
|
|
683
|
+
", "
|
|
684
|
+
)}`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
return path;
|
|
688
|
+
}
|
|
689
|
+
const paramObj = params;
|
|
690
|
+
const providedNames = new Set(Object.keys(paramObj));
|
|
691
|
+
for (const name of providedNames) {
|
|
692
|
+
if (!placeholders.has(name)) {
|
|
693
|
+
throw new Error(
|
|
694
|
+
`Unexpected path parameter "${name}" for template "${path}".`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
const value = paramObj[name];
|
|
698
|
+
if (value != null && (typeof value === "object" || Array.isArray(value))) {
|
|
699
|
+
throw new Error(
|
|
700
|
+
`Path parameter "${name}" must be a primitive; received "${typeof value}".`
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
for (const name of placeholders) {
|
|
705
|
+
if (!providedNames.has(name)) {
|
|
706
|
+
throw new Error(
|
|
707
|
+
`Missing value for path parameter "${name}" in template "${path}".`
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (placeholders.size === 0) {
|
|
712
|
+
return path;
|
|
713
|
+
}
|
|
714
|
+
return (0, import_rrroutes_contract.compilePath)(path, paramObj);
|
|
715
|
+
}
|
|
544
716
|
|
|
545
717
|
// src/sockets/socket.client.sys.ts
|
|
546
718
|
var import_zod2 = require("zod");
|
|
@@ -799,7 +971,21 @@ function roomsFromData(data, toRooms) {
|
|
|
799
971
|
if (data == null) return { rooms: [] };
|
|
800
972
|
let state = { rooms: [] };
|
|
801
973
|
const add = (input) => {
|
|
802
|
-
|
|
974
|
+
const mergeForValue = (value) => {
|
|
975
|
+
state = mergeRoomState(state, toRooms(value));
|
|
976
|
+
};
|
|
977
|
+
if (Array.isArray(input)) {
|
|
978
|
+
input.forEach((entry) => mergeForValue(entry));
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
if (input && typeof input === "object") {
|
|
982
|
+
const maybeItems = input.items;
|
|
983
|
+
if (Array.isArray(maybeItems)) {
|
|
984
|
+
maybeItems.forEach((entry) => mergeForValue(entry));
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
mergeForValue(input);
|
|
803
989
|
};
|
|
804
990
|
const maybePages = data?.pages;
|
|
805
991
|
if (Array.isArray(maybePages)) {
|
|
@@ -813,22 +999,34 @@ function buildSocketedRoute(options) {
|
|
|
813
999
|
const { built, toRooms, applySocket, useSocketClient: useSocketClient2 } = options;
|
|
814
1000
|
return (...useArgs) => {
|
|
815
1001
|
const client = useSocketClient2();
|
|
816
|
-
const endpointResult = built.useEndpoint(
|
|
1002
|
+
const endpointResult = built.useEndpoint(
|
|
1003
|
+
...useArgs
|
|
1004
|
+
);
|
|
817
1005
|
const argsKey = (0, import_react2.useMemo)(() => JSON.stringify(useArgs[0] ?? null), [useArgs]);
|
|
818
1006
|
const [roomState, setRoomState] = (0, import_react2.useState)(
|
|
819
1007
|
() => roomsFromData(endpointResult.data, toRooms)
|
|
820
1008
|
);
|
|
821
1009
|
const roomsKey = (0, import_react2.useMemo)(() => roomState.rooms.join("|"), [roomState.rooms]);
|
|
822
|
-
const joinMetaKey = (0, import_react2.useMemo)(
|
|
823
|
-
|
|
1010
|
+
const joinMetaKey = (0, import_react2.useMemo)(
|
|
1011
|
+
() => JSON.stringify(roomState.joinMeta ?? null),
|
|
1012
|
+
[roomState.joinMeta]
|
|
1013
|
+
);
|
|
1014
|
+
const leaveMetaKey = (0, import_react2.useMemo)(
|
|
1015
|
+
() => JSON.stringify(roomState.leaveMeta ?? null),
|
|
1016
|
+
[roomState.leaveMeta]
|
|
1017
|
+
);
|
|
824
1018
|
(0, import_react2.useEffect)(() => {
|
|
825
1019
|
const unsubscribe = endpointResult.onReceive((data) => {
|
|
826
|
-
setRoomState(
|
|
1020
|
+
setRoomState(
|
|
1021
|
+
(prev) => mergeRoomState(prev, toRooms(data))
|
|
1022
|
+
);
|
|
827
1023
|
});
|
|
828
1024
|
return unsubscribe;
|
|
829
1025
|
}, [endpointResult, toRooms]);
|
|
830
1026
|
(0, import_react2.useEffect)(() => {
|
|
831
|
-
setRoomState(
|
|
1027
|
+
setRoomState(
|
|
1028
|
+
roomsFromData(endpointResult.data, toRooms)
|
|
1029
|
+
);
|
|
832
1030
|
}, [endpointResult.data, toRooms]);
|
|
833
1031
|
(0, import_react2.useEffect)(() => {
|
|
834
1032
|
if (roomState.rooms.length === 0) return;
|
|
@@ -847,7 +1045,14 @@ function buildSocketedRoute(options) {
|
|
|
847
1045
|
void client.leaveRooms(roomState.rooms, leaveMeta).catch(() => {
|
|
848
1046
|
});
|
|
849
1047
|
};
|
|
850
|
-
}, [
|
|
1048
|
+
}, [
|
|
1049
|
+
client,
|
|
1050
|
+
roomsKey,
|
|
1051
|
+
roomState.joinMeta,
|
|
1052
|
+
roomState.leaveMeta,
|
|
1053
|
+
joinMetaKey,
|
|
1054
|
+
leaveMetaKey
|
|
1055
|
+
]);
|
|
851
1056
|
(0, import_react2.useEffect)(() => {
|
|
852
1057
|
const entries = Object.entries(applySocket).filter(
|
|
853
1058
|
([_event, fn]) => typeof fn === "function"
|
|
@@ -856,7 +1061,9 @@ function buildSocketedRoute(options) {
|
|
|
856
1061
|
([ev, fn]) => client.on(ev, (payload, meta) => {
|
|
857
1062
|
built.setData((prev) => {
|
|
858
1063
|
const next = fn(prev, payload, meta);
|
|
859
|
-
setRoomState(
|
|
1064
|
+
setRoomState(
|
|
1065
|
+
roomsFromData(next, toRooms)
|
|
1066
|
+
);
|
|
860
1067
|
return next;
|
|
861
1068
|
}, ...useArgs);
|
|
862
1069
|
})
|
|
@@ -905,7 +1112,11 @@ var SocketClient = class {
|
|
|
905
1112
|
}
|
|
906
1113
|
this.onConnect = async () => {
|
|
907
1114
|
if (!this.socket) {
|
|
908
|
-
this.dbg({
|
|
1115
|
+
this.dbg({
|
|
1116
|
+
type: "connection",
|
|
1117
|
+
phase: "connect_event",
|
|
1118
|
+
err: "Socket is null"
|
|
1119
|
+
});
|
|
909
1120
|
throw new Error("Socket is null in onConnect handler");
|
|
910
1121
|
}
|
|
911
1122
|
this.dbg({
|
|
@@ -924,7 +1135,11 @@ var SocketClient = class {
|
|
|
924
1135
|
};
|
|
925
1136
|
this.onReconnect = async (attempt) => {
|
|
926
1137
|
if (!this.socket) {
|
|
927
|
-
this.dbg({
|
|
1138
|
+
this.dbg({
|
|
1139
|
+
type: "connection",
|
|
1140
|
+
phase: "reconnect_event",
|
|
1141
|
+
err: "Socket is null"
|
|
1142
|
+
});
|
|
928
1143
|
throw new Error("Socket is null in onReconnect handler");
|
|
929
1144
|
}
|
|
930
1145
|
this.dbg({
|
|
@@ -945,7 +1160,11 @@ var SocketClient = class {
|
|
|
945
1160
|
};
|
|
946
1161
|
this.onDisconnect = async (reason) => {
|
|
947
1162
|
if (!this.socket) {
|
|
948
|
-
this.dbg({
|
|
1163
|
+
this.dbg({
|
|
1164
|
+
type: "connection",
|
|
1165
|
+
phase: "disconnect_event",
|
|
1166
|
+
err: "Socket is null"
|
|
1167
|
+
});
|
|
949
1168
|
throw new Error("Socket is null in onDisconnect handler");
|
|
950
1169
|
}
|
|
951
1170
|
this.dbg({
|
|
@@ -965,7 +1184,11 @@ var SocketClient = class {
|
|
|
965
1184
|
};
|
|
966
1185
|
this.onConnectError = async (err) => {
|
|
967
1186
|
if (!this.socket) {
|
|
968
|
-
this.dbg({
|
|
1187
|
+
this.dbg({
|
|
1188
|
+
type: "connection",
|
|
1189
|
+
phase: "connect_error_event",
|
|
1190
|
+
err: "Socket is null"
|
|
1191
|
+
});
|
|
969
1192
|
throw new Error("Socket is null in onConnectError handler");
|
|
970
1193
|
}
|
|
971
1194
|
this.dbg({
|
|
@@ -983,7 +1206,11 @@ var SocketClient = class {
|
|
|
983
1206
|
};
|
|
984
1207
|
this.onPong = async (raw) => {
|
|
985
1208
|
if (!this.socket) {
|
|
986
|
-
this.dbg({
|
|
1209
|
+
this.dbg({
|
|
1210
|
+
type: "heartbeat",
|
|
1211
|
+
phase: "pong_recv",
|
|
1212
|
+
err: "Socket is null"
|
|
1213
|
+
});
|
|
987
1214
|
throw new Error("Socket is null in onPong handler");
|
|
988
1215
|
}
|
|
989
1216
|
const parsed = this.config.pongPayload.safeParse(raw);
|
|
@@ -1083,11 +1310,15 @@ var SocketClient = class {
|
|
|
1083
1310
|
}
|
|
1084
1311
|
/** internal stats snapshot */
|
|
1085
1312
|
stats() {
|
|
1086
|
-
const rooms = Array.from(this.roomCounts.entries()).map(
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1313
|
+
const rooms = Array.from(this.roomCounts.entries()).map(
|
|
1314
|
+
([room, count]) => ({ room, count })
|
|
1315
|
+
);
|
|
1316
|
+
const handlers = Array.from(this.handlerMap.entries()).map(
|
|
1317
|
+
([event, set]) => ({
|
|
1318
|
+
event,
|
|
1319
|
+
handlers: set.size
|
|
1320
|
+
})
|
|
1321
|
+
);
|
|
1091
1322
|
return {
|
|
1092
1323
|
roomsCount: rooms.length,
|
|
1093
1324
|
totalHandlers: handlers.reduce((a, b) => a + b.handlers, 0),
|
|
@@ -1159,7 +1390,10 @@ var SocketClient = class {
|
|
|
1159
1390
|
details: this.getValidationDetails(check.error)
|
|
1160
1391
|
});
|
|
1161
1392
|
if (this.environment === "development") {
|
|
1162
|
-
console.warn(
|
|
1393
|
+
console.warn(
|
|
1394
|
+
"[socket] ping schema validation failed",
|
|
1395
|
+
check.error.issues
|
|
1396
|
+
);
|
|
1163
1397
|
}
|
|
1164
1398
|
return;
|
|
1165
1399
|
}
|
|
@@ -1218,7 +1452,12 @@ var SocketClient = class {
|
|
|
1218
1452
|
}
|
|
1219
1453
|
async joinRooms(rooms, meta) {
|
|
1220
1454
|
if (!this.socket) {
|
|
1221
|
-
this.dbg({
|
|
1455
|
+
this.dbg({
|
|
1456
|
+
type: "room",
|
|
1457
|
+
phase: "join",
|
|
1458
|
+
rooms: this.toArray(rooms),
|
|
1459
|
+
err: "Socket is null"
|
|
1460
|
+
});
|
|
1222
1461
|
throw new Error("Socket is null in joinRooms method");
|
|
1223
1462
|
}
|
|
1224
1463
|
if (!await this.getSysEvent("sys:room_join")({
|
|
@@ -1271,7 +1510,12 @@ var SocketClient = class {
|
|
|
1271
1510
|
}
|
|
1272
1511
|
async leaveRooms(rooms, meta) {
|
|
1273
1512
|
if (!this.socket) {
|
|
1274
|
-
this.dbg({
|
|
1513
|
+
this.dbg({
|
|
1514
|
+
type: "room",
|
|
1515
|
+
phase: "leave",
|
|
1516
|
+
rooms: this.toArray(rooms),
|
|
1517
|
+
err: "Socket is null"
|
|
1518
|
+
});
|
|
1275
1519
|
throw new Error("Socket is null in leaveRooms method");
|
|
1276
1520
|
}
|
|
1277
1521
|
if (!await this.getSysEvent("sys:room_leave")({
|
|
@@ -1323,7 +1567,12 @@ var SocketClient = class {
|
|
|
1323
1567
|
const schema = this.events[event].message;
|
|
1324
1568
|
this.dbg({ type: "register", phase: "register", event });
|
|
1325
1569
|
if (!this.socket) {
|
|
1326
|
-
this.dbg({
|
|
1570
|
+
this.dbg({
|
|
1571
|
+
type: "register",
|
|
1572
|
+
phase: "register",
|
|
1573
|
+
event,
|
|
1574
|
+
err: "Socket is null"
|
|
1575
|
+
});
|
|
1327
1576
|
return () => {
|
|
1328
1577
|
};
|
|
1329
1578
|
}
|