@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.mjs
CHANGED
|
@@ -26,30 +26,29 @@ var defaultFetcher = async (req) => {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
// src/routesV3.client.index.ts
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
buildCacheKey,
|
|
31
|
+
compilePath,
|
|
32
|
+
lowProfileParse
|
|
33
|
+
} from "@emeryld/rrroutes-contract";
|
|
30
34
|
import {
|
|
31
35
|
keepPreviousData,
|
|
32
36
|
useInfiniteQuery,
|
|
33
37
|
useMutation,
|
|
34
38
|
useQuery
|
|
35
39
|
} from "@tanstack/react-query";
|
|
40
|
+
import { useCallback, useRef } from "react";
|
|
36
41
|
import { z } from "zod";
|
|
37
|
-
import {
|
|
38
|
-
buildCacheKey,
|
|
39
|
-
compilePath
|
|
40
|
-
} from "@emeryld/rrroutes-contract";
|
|
41
42
|
var toUpper = (m) => m.toUpperCase();
|
|
42
|
-
var
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
43
|
+
var paginationQueryShape = {
|
|
44
|
+
pagination_cursor: z.string().optional(),
|
|
45
|
+
pagination_limit: z.coerce.number().min(1).max(100).default(20)
|
|
46
|
+
};
|
|
47
|
+
var defaultFeedQuerySchema = z.object(paginationQueryShape);
|
|
46
48
|
var defaultFeedOutputSchema = z.object({
|
|
47
49
|
items: z.array(z.unknown()),
|
|
48
50
|
nextCursor: z.string().optional()
|
|
49
51
|
});
|
|
50
|
-
function zParse(value, schema) {
|
|
51
|
-
return schema ? schema.parse(value) : value;
|
|
52
|
-
}
|
|
53
52
|
function toSearchString(query) {
|
|
54
53
|
if (!query) return "";
|
|
55
54
|
const params = new URLSearchParams();
|
|
@@ -148,41 +147,53 @@ function extractArgs(args) {
|
|
|
148
147
|
function toArgsTuple(args) {
|
|
149
148
|
return typeof args === "undefined" ? [] : [args];
|
|
150
149
|
}
|
|
150
|
+
function getZodShape(schema) {
|
|
151
|
+
const shapeOrGetter = schema.shape ? schema.shape : schema._def?.shape?.();
|
|
152
|
+
if (!shapeOrGetter) return {};
|
|
153
|
+
return typeof shapeOrGetter === "function" ? shapeOrGetter.call(schema) : shapeOrGetter;
|
|
154
|
+
}
|
|
151
155
|
function augmentFeedQuerySchema(schema) {
|
|
152
156
|
if (!schema) return defaultFeedQuerySchema;
|
|
153
|
-
if (schema instanceof z.ZodObject) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
limit: defaultFeedQuerySchema.shape.limit
|
|
159
|
-
});
|
|
157
|
+
if (!(schema instanceof z.ZodObject)) {
|
|
158
|
+
console.warn(
|
|
159
|
+
"Feed queries must be a ZodObject; default pagination applied."
|
|
160
|
+
);
|
|
161
|
+
return defaultFeedQuerySchema;
|
|
160
162
|
}
|
|
161
|
-
return
|
|
163
|
+
return schema.extend(paginationQueryShape);
|
|
162
164
|
}
|
|
163
165
|
function augmentFeedOutputSchema(schema) {
|
|
164
166
|
if (!schema) return defaultFeedOutputSchema;
|
|
165
|
-
if (schema instanceof z.
|
|
166
|
-
const shape = schema.shape ? schema.shape : schema._def?.shape?.();
|
|
167
|
-
const hasItems = Boolean(shape?.items);
|
|
168
|
-
if (hasItems) return schema.extend({ nextCursor: z.string().optional() });
|
|
167
|
+
if (schema instanceof z.ZodArray) {
|
|
169
168
|
return z.object({
|
|
170
|
-
items:
|
|
169
|
+
items: schema,
|
|
171
170
|
nextCursor: z.string().optional()
|
|
172
171
|
});
|
|
173
172
|
}
|
|
174
|
-
if (schema instanceof z.
|
|
173
|
+
if (schema instanceof z.ZodObject) {
|
|
174
|
+
const shape = getZodShape(schema);
|
|
175
|
+
if (shape?.items) {
|
|
176
|
+
return schema.extend({
|
|
177
|
+
nextCursor: z.string().optional()
|
|
178
|
+
});
|
|
179
|
+
}
|
|
175
180
|
return z.object({
|
|
176
|
-
items: schema,
|
|
181
|
+
items: z.array(schema),
|
|
177
182
|
nextCursor: z.string().optional()
|
|
178
183
|
});
|
|
179
184
|
}
|
|
180
|
-
return
|
|
185
|
+
return z.object({
|
|
186
|
+
items: z.array(schema),
|
|
187
|
+
nextCursor: z.string().optional()
|
|
188
|
+
});
|
|
181
189
|
}
|
|
182
190
|
function buildUrl(leaf, baseUrl, params, query) {
|
|
183
|
-
const normalizedParams =
|
|
184
|
-
const normalizedQuery =
|
|
185
|
-
const path = compilePath(
|
|
191
|
+
const normalizedParams = leaf.cfg.paramsSchema ? lowProfileParse(leaf.cfg.paramsSchema, params) : {};
|
|
192
|
+
const normalizedQuery = leaf.cfg.querySchema ? lowProfileParse(leaf.cfg.querySchema, query) : {};
|
|
193
|
+
const path = compilePath(
|
|
194
|
+
leaf.path,
|
|
195
|
+
normalizedParams ?? {}
|
|
196
|
+
);
|
|
186
197
|
const url = `${baseUrl ?? ""}${path}${toSearchString(normalizedQuery)}`;
|
|
187
198
|
return { url, normalizedQuery, normalizedParams };
|
|
188
199
|
}
|
|
@@ -190,10 +201,13 @@ function createRouteClient(opts) {
|
|
|
190
201
|
const queryClient = opts.queryClient;
|
|
191
202
|
const fetcher = opts.fetcher ?? defaultFetcher;
|
|
192
203
|
const baseUrl = opts.baseUrl;
|
|
193
|
-
const cursorParam = opts.cursorParam ?? "
|
|
204
|
+
const cursorParam = opts.cursorParam ?? "pagination_cursor";
|
|
194
205
|
const getNextCursor = opts.getNextCursor ?? defaultGetNextCursor;
|
|
195
206
|
const environment = opts.environment ?? void 0;
|
|
196
|
-
const { emit: emitDebug, mode: debugMode } = createDebugEmitter(
|
|
207
|
+
const { emit: emitDebug, mode: debugMode } = createDebugEmitter(
|
|
208
|
+
opts.debug,
|
|
209
|
+
environment
|
|
210
|
+
);
|
|
197
211
|
const isVerboseDebug = debugMode === "complete";
|
|
198
212
|
const decorateDebugEvent = (event, details) => {
|
|
199
213
|
if (!isVerboseDebug || !details) return event;
|
|
@@ -207,11 +221,12 @@ function createRouteClient(opts) {
|
|
|
207
221
|
function buildInternal(leaf, rqOpts, meta) {
|
|
208
222
|
const isGet = leaf.method === "get";
|
|
209
223
|
const isFeed = !!leaf.cfg.feed;
|
|
224
|
+
const rawLeafCfg = leaf.cfg;
|
|
210
225
|
const leafCfg = isFeed ? {
|
|
211
|
-
...
|
|
212
|
-
querySchema: augmentFeedQuerySchema(
|
|
213
|
-
outputSchema: augmentFeedOutputSchema(
|
|
214
|
-
} :
|
|
226
|
+
...rawLeafCfg,
|
|
227
|
+
querySchema: augmentFeedQuerySchema(rawLeafCfg.querySchema),
|
|
228
|
+
outputSchema: augmentFeedOutputSchema(rawLeafCfg.outputSchema)
|
|
229
|
+
} : rawLeafCfg;
|
|
215
230
|
const method = toUpper(leaf.method);
|
|
216
231
|
const expectsArgs = Boolean(leafCfg.paramsSchema || leafCfg.querySchema);
|
|
217
232
|
const leafLabel = `${leaf.method.toUpperCase()} ${String(leaf.path)}`;
|
|
@@ -267,9 +282,13 @@ function createRouteClient(opts) {
|
|
|
267
282
|
const acceptsBody = Boolean(leafCfg.bodySchema);
|
|
268
283
|
const requiresBody = options?.requireBody ?? (!isGet && acceptsBody);
|
|
269
284
|
if (typeof options?.body !== "undefined") {
|
|
270
|
-
const normalizedBody =
|
|
285
|
+
const normalizedBody = leafCfg.bodySchema ? lowProfileParse(leafCfg.bodySchema, options.body) : void 0;
|
|
271
286
|
const isMultipart = Array.isArray(leafCfg.bodyFiles) && leafCfg.bodyFiles.length > 0;
|
|
272
|
-
|
|
287
|
+
if (isMultipart && normalizedBody && typeof normalizedBody === "object") {
|
|
288
|
+
payload = toFormData(normalizedBody);
|
|
289
|
+
} else {
|
|
290
|
+
payload = normalizedBody;
|
|
291
|
+
}
|
|
273
292
|
} else if (requiresBody) {
|
|
274
293
|
throw new Error("Body is required when invoking a mutation fetch.");
|
|
275
294
|
}
|
|
@@ -292,7 +311,7 @@ function createRouteClient(opts) {
|
|
|
292
311
|
const out = await fetcher(
|
|
293
312
|
payload === void 0 ? { url, method } : { url, method, body: payload }
|
|
294
313
|
);
|
|
295
|
-
const parsed =
|
|
314
|
+
const parsed = leafCfg.outputSchema ? lowProfileParse(leafCfg.outputSchema, out) : void 0;
|
|
296
315
|
emit(
|
|
297
316
|
decorateDebugEvent(
|
|
298
317
|
{
|
|
@@ -303,7 +322,11 @@ function createRouteClient(opts) {
|
|
|
303
322
|
leaf: leafLabel,
|
|
304
323
|
durationMs: Date.now() - startedAt
|
|
305
324
|
},
|
|
306
|
-
isVerboseDebug ? {
|
|
325
|
+
isVerboseDebug ? {
|
|
326
|
+
params: normalizedParams,
|
|
327
|
+
query: normalizedQuery,
|
|
328
|
+
output: parsed
|
|
329
|
+
} : void 0
|
|
307
330
|
)
|
|
308
331
|
);
|
|
309
332
|
options?.onReceive?.(parsed);
|
|
@@ -334,7 +357,11 @@ function createRouteClient(opts) {
|
|
|
334
357
|
const hasBodyCandidate = acceptsBody && tupleLength > maybeBodyIndex;
|
|
335
358
|
const body = hasBodyCandidate ? tupleWithBody[tupleLength - 1] : void 0;
|
|
336
359
|
const tuple = hasBodyCandidate ? tupleWithBody.slice(0, tupleLength - 1) : tupleWithBody;
|
|
337
|
-
return fetchEndpoint(tuple, {
|
|
360
|
+
return fetchEndpoint(tuple, {
|
|
361
|
+
body,
|
|
362
|
+
onReceive: buildOnReceive,
|
|
363
|
+
requireBody: false
|
|
364
|
+
});
|
|
338
365
|
};
|
|
339
366
|
if (isGet && isFeed) {
|
|
340
367
|
const useEndpoint2 = (...useArgs) => {
|
|
@@ -345,13 +372,10 @@ function createRouteClient(opts) {
|
|
|
345
372
|
const query = args?.query;
|
|
346
373
|
const buildOptions = rqOpts ?? {};
|
|
347
374
|
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
348
|
-
const notifyOnReceive = useCallback(
|
|
349
|
-
(data)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
},
|
|
353
|
-
[]
|
|
354
|
-
);
|
|
375
|
+
const notifyOnReceive = useCallback((data) => {
|
|
376
|
+
buildOptions?.onReceive?.(data);
|
|
377
|
+
listenersRef.current.forEach((listener) => listener(data));
|
|
378
|
+
}, []);
|
|
355
379
|
const registerOnReceive = useCallback(
|
|
356
380
|
(listener) => {
|
|
357
381
|
listenersRef.current.add(listener);
|
|
@@ -367,24 +391,27 @@ function createRouteClient(opts) {
|
|
|
367
391
|
params,
|
|
368
392
|
query
|
|
369
393
|
);
|
|
370
|
-
const queryResult = useInfiniteQuery(
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
394
|
+
const queryResult = useInfiniteQuery(
|
|
395
|
+
{
|
|
396
|
+
...buildOptions,
|
|
397
|
+
queryKey: getQueryKeys(...tuple),
|
|
398
|
+
initialPageParam: void 0,
|
|
399
|
+
getNextPageParam: (lastPage) => getNextCursor(lastPage),
|
|
400
|
+
placeholderData: keepPreviousData,
|
|
401
|
+
queryFn: ({ pageParam }) => {
|
|
402
|
+
const pageQuery = {
|
|
403
|
+
...normalizedQuery,
|
|
404
|
+
...pageParam ? { [cursorParam]: pageParam } : {}
|
|
405
|
+
};
|
|
406
|
+
return fetchEndpoint(tuple, {
|
|
407
|
+
queryOverride: pageQuery,
|
|
408
|
+
onReceive: notifyOnReceive
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
// NOTE: TData is InfiniteData<T>, so we don't need a select here.
|
|
412
|
+
},
|
|
413
|
+
queryClient
|
|
414
|
+
);
|
|
388
415
|
return { ...queryResult, onReceive: registerOnReceive };
|
|
389
416
|
};
|
|
390
417
|
return {
|
|
@@ -404,13 +431,10 @@ function createRouteClient(opts) {
|
|
|
404
431
|
const query = args?.query;
|
|
405
432
|
const buildOptions = rqOpts ?? {};
|
|
406
433
|
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
407
|
-
const notifyOnReceive = useCallback(
|
|
408
|
-
(data)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
},
|
|
412
|
-
[]
|
|
413
|
-
);
|
|
434
|
+
const notifyOnReceive = useCallback((data) => {
|
|
435
|
+
buildOptions?.onReceive?.(data);
|
|
436
|
+
listenersRef.current.forEach((listener) => listener(data));
|
|
437
|
+
}, []);
|
|
414
438
|
const registerOnReceive = useCallback(
|
|
415
439
|
(listener) => {
|
|
416
440
|
listenersRef.current.add(listener);
|
|
@@ -421,14 +445,17 @@ function createRouteClient(opts) {
|
|
|
421
445
|
[]
|
|
422
446
|
);
|
|
423
447
|
buildUrl({ ...leaf, cfg: leafCfg }, baseUrl, params, query);
|
|
424
|
-
const queryResult = useQuery(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
448
|
+
const queryResult = useQuery(
|
|
449
|
+
{
|
|
450
|
+
...buildOptions,
|
|
451
|
+
queryKey: getQueryKeys(...tuple),
|
|
452
|
+
placeholderData: keepPreviousData,
|
|
453
|
+
queryFn: () => fetchEndpoint(tuple, {
|
|
454
|
+
onReceive: notifyOnReceive
|
|
455
|
+
})
|
|
456
|
+
},
|
|
457
|
+
queryClient
|
|
458
|
+
);
|
|
432
459
|
return { ...queryResult, onReceive: registerOnReceive };
|
|
433
460
|
};
|
|
434
461
|
return {
|
|
@@ -462,21 +489,29 @@ function createRouteClient(opts) {
|
|
|
462
489
|
const notifyListeners = useCallback((data) => {
|
|
463
490
|
listenersRef.current.forEach((listener) => listener(data));
|
|
464
491
|
}, []);
|
|
465
|
-
const registerOnReceive = useCallback(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
492
|
+
const registerOnReceive = useCallback(
|
|
493
|
+
(listener) => {
|
|
494
|
+
listenersRef.current.add(listener);
|
|
495
|
+
return () => {
|
|
496
|
+
listenersRef.current.delete(listener);
|
|
497
|
+
};
|
|
498
|
+
},
|
|
499
|
+
[]
|
|
500
|
+
);
|
|
501
|
+
const mutationResult = useMutation(
|
|
502
|
+
{
|
|
503
|
+
...mutationBuildOptions,
|
|
504
|
+
mutationKey: getQueryKeys(...tuple),
|
|
505
|
+
mutationFn: async (body) => {
|
|
506
|
+
const result = await fetchMutation(
|
|
507
|
+
...[...tuple, body]
|
|
508
|
+
);
|
|
509
|
+
notifyListeners(result);
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
queryClient
|
|
514
|
+
);
|
|
480
515
|
return { ...mutationResult, onReceive: registerOnReceive };
|
|
481
516
|
};
|
|
482
517
|
return {
|
|
@@ -487,25 +522,163 @@ function createRouteClient(opts) {
|
|
|
487
522
|
fetch: fetchMutation
|
|
488
523
|
};
|
|
489
524
|
}
|
|
525
|
+
const fetchRaw = async (input) => {
|
|
526
|
+
const { path, method, query, body, params } = input;
|
|
527
|
+
if (!path || typeof path !== "string") {
|
|
528
|
+
throw new Error("fetch(path, ...) requires a non-empty string path.");
|
|
529
|
+
}
|
|
530
|
+
if (!method) {
|
|
531
|
+
throw new Error("fetch(path, method, ...) requires an HTTP method.");
|
|
532
|
+
}
|
|
533
|
+
const methodLower = String(method).toLowerCase();
|
|
534
|
+
const methodUpper = toUpper(methodLower);
|
|
535
|
+
const flatQuery = normalizeFlatQuery(query);
|
|
536
|
+
const search = toSearchString(flatQuery);
|
|
537
|
+
const compiledPath = compileRawPath(path, params);
|
|
538
|
+
const url = `${baseUrl ?? ""}${compiledPath}${search}`;
|
|
539
|
+
const leafLabel = `${methodUpper} ${path}`;
|
|
540
|
+
const startedAt = Date.now();
|
|
541
|
+
const detail = isVerboseDebug ? { params, query: flatQuery } : void 0;
|
|
542
|
+
emitDebug(
|
|
543
|
+
decorateDebugEvent(
|
|
544
|
+
{
|
|
545
|
+
type: "fetch",
|
|
546
|
+
stage: "start",
|
|
547
|
+
method: methodUpper,
|
|
548
|
+
url,
|
|
549
|
+
leaf: leafLabel,
|
|
550
|
+
...body !== void 0 ? { body } : {}
|
|
551
|
+
},
|
|
552
|
+
detail
|
|
553
|
+
)
|
|
554
|
+
);
|
|
555
|
+
try {
|
|
556
|
+
const out = await fetcher(
|
|
557
|
+
body === void 0 ? { url, method: methodUpper } : { url, method: methodUpper, body }
|
|
558
|
+
);
|
|
559
|
+
emitDebug(
|
|
560
|
+
decorateDebugEvent(
|
|
561
|
+
{
|
|
562
|
+
type: "fetch",
|
|
563
|
+
stage: "success",
|
|
564
|
+
method: methodUpper,
|
|
565
|
+
url,
|
|
566
|
+
leaf: leafLabel,
|
|
567
|
+
durationMs: Date.now() - startedAt
|
|
568
|
+
},
|
|
569
|
+
isVerboseDebug ? { params, query: flatQuery, output: out } : void 0
|
|
570
|
+
)
|
|
571
|
+
);
|
|
572
|
+
return out;
|
|
573
|
+
} catch (error) {
|
|
574
|
+
emitDebug(
|
|
575
|
+
decorateDebugEvent(
|
|
576
|
+
{
|
|
577
|
+
type: "fetch",
|
|
578
|
+
stage: "error",
|
|
579
|
+
method: methodUpper,
|
|
580
|
+
url,
|
|
581
|
+
leaf: leafLabel,
|
|
582
|
+
durationMs: Date.now() - startedAt,
|
|
583
|
+
...body !== void 0 ? { body } : {},
|
|
584
|
+
error
|
|
585
|
+
},
|
|
586
|
+
detail
|
|
587
|
+
)
|
|
588
|
+
);
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
591
|
+
};
|
|
490
592
|
return {
|
|
491
593
|
queryClient,
|
|
492
594
|
invalidate,
|
|
595
|
+
fetch: fetchRaw,
|
|
493
596
|
build: buildInternal
|
|
494
597
|
};
|
|
495
598
|
}
|
|
496
599
|
function buildRouter(routeClient, routes) {
|
|
497
600
|
const buildLeaf = routeClient.build;
|
|
498
|
-
return ((key, opts, meta) => buildLeaf(
|
|
601
|
+
return ((key, opts, meta) => buildLeaf(
|
|
602
|
+
routes[key],
|
|
603
|
+
opts,
|
|
604
|
+
meta
|
|
605
|
+
));
|
|
499
606
|
}
|
|
500
607
|
function toFormData(body) {
|
|
501
608
|
const fd = new FormData();
|
|
502
609
|
for (const [k, v] of Object.entries(body ?? {})) {
|
|
503
610
|
if (v == null) continue;
|
|
504
|
-
if (Array.isArray(v))
|
|
611
|
+
if (Array.isArray(v))
|
|
612
|
+
v.forEach((item, i) => fd.append(`${k}[${i}]`, item));
|
|
505
613
|
else fd.append(k, v);
|
|
506
614
|
}
|
|
507
615
|
return fd;
|
|
508
616
|
}
|
|
617
|
+
function getPathParamNames(path) {
|
|
618
|
+
const names = /* @__PURE__ */ new Set();
|
|
619
|
+
const re = /:([A-Za-z0-9_]+)/g;
|
|
620
|
+
let match;
|
|
621
|
+
while ((match = re.exec(path)) !== null) {
|
|
622
|
+
names.add(match[1]);
|
|
623
|
+
}
|
|
624
|
+
return names;
|
|
625
|
+
}
|
|
626
|
+
function normalizeFlatQuery(query) {
|
|
627
|
+
if (query == null) return void 0;
|
|
628
|
+
if (typeof query !== "object" || Array.isArray(query)) {
|
|
629
|
+
throw new Error("Query must be a plain object (Record<string, string>).");
|
|
630
|
+
}
|
|
631
|
+
const result = {};
|
|
632
|
+
for (const [k, v] of Object.entries(query)) {
|
|
633
|
+
if (v == null) continue;
|
|
634
|
+
if (typeof v !== "string") {
|
|
635
|
+
throw new Error(
|
|
636
|
+
`Query param "${k}" must be a string; received type "${typeof v}".`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
result[k] = v;
|
|
640
|
+
}
|
|
641
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
642
|
+
}
|
|
643
|
+
function compileRawPath(path, params) {
|
|
644
|
+
const placeholders = getPathParamNames(path);
|
|
645
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
646
|
+
if (placeholders.size > 0) {
|
|
647
|
+
throw new Error(
|
|
648
|
+
`Missing path parameters for "${path}": ${[...placeholders].join(
|
|
649
|
+
", "
|
|
650
|
+
)}`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
return path;
|
|
654
|
+
}
|
|
655
|
+
const paramObj = params;
|
|
656
|
+
const providedNames = new Set(Object.keys(paramObj));
|
|
657
|
+
for (const name of providedNames) {
|
|
658
|
+
if (!placeholders.has(name)) {
|
|
659
|
+
throw new Error(
|
|
660
|
+
`Unexpected path parameter "${name}" for template "${path}".`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
const value = paramObj[name];
|
|
664
|
+
if (value != null && (typeof value === "object" || Array.isArray(value))) {
|
|
665
|
+
throw new Error(
|
|
666
|
+
`Path parameter "${name}" must be a primitive; received "${typeof value}".`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
for (const name of placeholders) {
|
|
671
|
+
if (!providedNames.has(name)) {
|
|
672
|
+
throw new Error(
|
|
673
|
+
`Missing value for path parameter "${name}" in template "${path}".`
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (placeholders.size === 0) {
|
|
678
|
+
return path;
|
|
679
|
+
}
|
|
680
|
+
return compilePath(path, paramObj);
|
|
681
|
+
}
|
|
509
682
|
|
|
510
683
|
// src/sockets/socket.client.sys.ts
|
|
511
684
|
import { z as z2 } from "zod";
|
|
@@ -764,7 +937,21 @@ function roomsFromData(data, toRooms) {
|
|
|
764
937
|
if (data == null) return { rooms: [] };
|
|
765
938
|
let state = { rooms: [] };
|
|
766
939
|
const add = (input) => {
|
|
767
|
-
|
|
940
|
+
const mergeForValue = (value) => {
|
|
941
|
+
state = mergeRoomState(state, toRooms(value));
|
|
942
|
+
};
|
|
943
|
+
if (Array.isArray(input)) {
|
|
944
|
+
input.forEach((entry) => mergeForValue(entry));
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
if (input && typeof input === "object") {
|
|
948
|
+
const maybeItems = input.items;
|
|
949
|
+
if (Array.isArray(maybeItems)) {
|
|
950
|
+
maybeItems.forEach((entry) => mergeForValue(entry));
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
mergeForValue(input);
|
|
768
955
|
};
|
|
769
956
|
const maybePages = data?.pages;
|
|
770
957
|
if (Array.isArray(maybePages)) {
|
|
@@ -778,22 +965,34 @@ function buildSocketedRoute(options) {
|
|
|
778
965
|
const { built, toRooms, applySocket, useSocketClient: useSocketClient2 } = options;
|
|
779
966
|
return (...useArgs) => {
|
|
780
967
|
const client = useSocketClient2();
|
|
781
|
-
const endpointResult = built.useEndpoint(
|
|
968
|
+
const endpointResult = built.useEndpoint(
|
|
969
|
+
...useArgs
|
|
970
|
+
);
|
|
782
971
|
const argsKey = useMemo2(() => JSON.stringify(useArgs[0] ?? null), [useArgs]);
|
|
783
972
|
const [roomState, setRoomState] = useState2(
|
|
784
973
|
() => roomsFromData(endpointResult.data, toRooms)
|
|
785
974
|
);
|
|
786
975
|
const roomsKey = useMemo2(() => roomState.rooms.join("|"), [roomState.rooms]);
|
|
787
|
-
const joinMetaKey = useMemo2(
|
|
788
|
-
|
|
976
|
+
const joinMetaKey = useMemo2(
|
|
977
|
+
() => JSON.stringify(roomState.joinMeta ?? null),
|
|
978
|
+
[roomState.joinMeta]
|
|
979
|
+
);
|
|
980
|
+
const leaveMetaKey = useMemo2(
|
|
981
|
+
() => JSON.stringify(roomState.leaveMeta ?? null),
|
|
982
|
+
[roomState.leaveMeta]
|
|
983
|
+
);
|
|
789
984
|
useEffect2(() => {
|
|
790
985
|
const unsubscribe = endpointResult.onReceive((data) => {
|
|
791
|
-
setRoomState(
|
|
986
|
+
setRoomState(
|
|
987
|
+
(prev) => mergeRoomState(prev, toRooms(data))
|
|
988
|
+
);
|
|
792
989
|
});
|
|
793
990
|
return unsubscribe;
|
|
794
991
|
}, [endpointResult, toRooms]);
|
|
795
992
|
useEffect2(() => {
|
|
796
|
-
setRoomState(
|
|
993
|
+
setRoomState(
|
|
994
|
+
roomsFromData(endpointResult.data, toRooms)
|
|
995
|
+
);
|
|
797
996
|
}, [endpointResult.data, toRooms]);
|
|
798
997
|
useEffect2(() => {
|
|
799
998
|
if (roomState.rooms.length === 0) return;
|
|
@@ -812,7 +1011,14 @@ function buildSocketedRoute(options) {
|
|
|
812
1011
|
void client.leaveRooms(roomState.rooms, leaveMeta).catch(() => {
|
|
813
1012
|
});
|
|
814
1013
|
};
|
|
815
|
-
}, [
|
|
1014
|
+
}, [
|
|
1015
|
+
client,
|
|
1016
|
+
roomsKey,
|
|
1017
|
+
roomState.joinMeta,
|
|
1018
|
+
roomState.leaveMeta,
|
|
1019
|
+
joinMetaKey,
|
|
1020
|
+
leaveMetaKey
|
|
1021
|
+
]);
|
|
816
1022
|
useEffect2(() => {
|
|
817
1023
|
const entries = Object.entries(applySocket).filter(
|
|
818
1024
|
([_event, fn]) => typeof fn === "function"
|
|
@@ -821,7 +1027,9 @@ function buildSocketedRoute(options) {
|
|
|
821
1027
|
([ev, fn]) => client.on(ev, (payload, meta) => {
|
|
822
1028
|
built.setData((prev) => {
|
|
823
1029
|
const next = fn(prev, payload, meta);
|
|
824
|
-
setRoomState(
|
|
1030
|
+
setRoomState(
|
|
1031
|
+
roomsFromData(next, toRooms)
|
|
1032
|
+
);
|
|
825
1033
|
return next;
|
|
826
1034
|
}, ...useArgs);
|
|
827
1035
|
})
|
|
@@ -870,7 +1078,11 @@ var SocketClient = class {
|
|
|
870
1078
|
}
|
|
871
1079
|
this.onConnect = async () => {
|
|
872
1080
|
if (!this.socket) {
|
|
873
|
-
this.dbg({
|
|
1081
|
+
this.dbg({
|
|
1082
|
+
type: "connection",
|
|
1083
|
+
phase: "connect_event",
|
|
1084
|
+
err: "Socket is null"
|
|
1085
|
+
});
|
|
874
1086
|
throw new Error("Socket is null in onConnect handler");
|
|
875
1087
|
}
|
|
876
1088
|
this.dbg({
|
|
@@ -889,7 +1101,11 @@ var SocketClient = class {
|
|
|
889
1101
|
};
|
|
890
1102
|
this.onReconnect = async (attempt) => {
|
|
891
1103
|
if (!this.socket) {
|
|
892
|
-
this.dbg({
|
|
1104
|
+
this.dbg({
|
|
1105
|
+
type: "connection",
|
|
1106
|
+
phase: "reconnect_event",
|
|
1107
|
+
err: "Socket is null"
|
|
1108
|
+
});
|
|
893
1109
|
throw new Error("Socket is null in onReconnect handler");
|
|
894
1110
|
}
|
|
895
1111
|
this.dbg({
|
|
@@ -910,7 +1126,11 @@ var SocketClient = class {
|
|
|
910
1126
|
};
|
|
911
1127
|
this.onDisconnect = async (reason) => {
|
|
912
1128
|
if (!this.socket) {
|
|
913
|
-
this.dbg({
|
|
1129
|
+
this.dbg({
|
|
1130
|
+
type: "connection",
|
|
1131
|
+
phase: "disconnect_event",
|
|
1132
|
+
err: "Socket is null"
|
|
1133
|
+
});
|
|
914
1134
|
throw new Error("Socket is null in onDisconnect handler");
|
|
915
1135
|
}
|
|
916
1136
|
this.dbg({
|
|
@@ -930,7 +1150,11 @@ var SocketClient = class {
|
|
|
930
1150
|
};
|
|
931
1151
|
this.onConnectError = async (err) => {
|
|
932
1152
|
if (!this.socket) {
|
|
933
|
-
this.dbg({
|
|
1153
|
+
this.dbg({
|
|
1154
|
+
type: "connection",
|
|
1155
|
+
phase: "connect_error_event",
|
|
1156
|
+
err: "Socket is null"
|
|
1157
|
+
});
|
|
934
1158
|
throw new Error("Socket is null in onConnectError handler");
|
|
935
1159
|
}
|
|
936
1160
|
this.dbg({
|
|
@@ -948,7 +1172,11 @@ var SocketClient = class {
|
|
|
948
1172
|
};
|
|
949
1173
|
this.onPong = async (raw) => {
|
|
950
1174
|
if (!this.socket) {
|
|
951
|
-
this.dbg({
|
|
1175
|
+
this.dbg({
|
|
1176
|
+
type: "heartbeat",
|
|
1177
|
+
phase: "pong_recv",
|
|
1178
|
+
err: "Socket is null"
|
|
1179
|
+
});
|
|
952
1180
|
throw new Error("Socket is null in onPong handler");
|
|
953
1181
|
}
|
|
954
1182
|
const parsed = this.config.pongPayload.safeParse(raw);
|
|
@@ -1048,11 +1276,15 @@ var SocketClient = class {
|
|
|
1048
1276
|
}
|
|
1049
1277
|
/** internal stats snapshot */
|
|
1050
1278
|
stats() {
|
|
1051
|
-
const rooms = Array.from(this.roomCounts.entries()).map(
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1279
|
+
const rooms = Array.from(this.roomCounts.entries()).map(
|
|
1280
|
+
([room, count]) => ({ room, count })
|
|
1281
|
+
);
|
|
1282
|
+
const handlers = Array.from(this.handlerMap.entries()).map(
|
|
1283
|
+
([event, set]) => ({
|
|
1284
|
+
event,
|
|
1285
|
+
handlers: set.size
|
|
1286
|
+
})
|
|
1287
|
+
);
|
|
1056
1288
|
return {
|
|
1057
1289
|
roomsCount: rooms.length,
|
|
1058
1290
|
totalHandlers: handlers.reduce((a, b) => a + b.handlers, 0),
|
|
@@ -1124,7 +1356,10 @@ var SocketClient = class {
|
|
|
1124
1356
|
details: this.getValidationDetails(check.error)
|
|
1125
1357
|
});
|
|
1126
1358
|
if (this.environment === "development") {
|
|
1127
|
-
console.warn(
|
|
1359
|
+
console.warn(
|
|
1360
|
+
"[socket] ping schema validation failed",
|
|
1361
|
+
check.error.issues
|
|
1362
|
+
);
|
|
1128
1363
|
}
|
|
1129
1364
|
return;
|
|
1130
1365
|
}
|
|
@@ -1183,7 +1418,12 @@ var SocketClient = class {
|
|
|
1183
1418
|
}
|
|
1184
1419
|
async joinRooms(rooms, meta) {
|
|
1185
1420
|
if (!this.socket) {
|
|
1186
|
-
this.dbg({
|
|
1421
|
+
this.dbg({
|
|
1422
|
+
type: "room",
|
|
1423
|
+
phase: "join",
|
|
1424
|
+
rooms: this.toArray(rooms),
|
|
1425
|
+
err: "Socket is null"
|
|
1426
|
+
});
|
|
1187
1427
|
throw new Error("Socket is null in joinRooms method");
|
|
1188
1428
|
}
|
|
1189
1429
|
if (!await this.getSysEvent("sys:room_join")({
|
|
@@ -1236,7 +1476,12 @@ var SocketClient = class {
|
|
|
1236
1476
|
}
|
|
1237
1477
|
async leaveRooms(rooms, meta) {
|
|
1238
1478
|
if (!this.socket) {
|
|
1239
|
-
this.dbg({
|
|
1479
|
+
this.dbg({
|
|
1480
|
+
type: "room",
|
|
1481
|
+
phase: "leave",
|
|
1482
|
+
rooms: this.toArray(rooms),
|
|
1483
|
+
err: "Socket is null"
|
|
1484
|
+
});
|
|
1240
1485
|
throw new Error("Socket is null in leaveRooms method");
|
|
1241
1486
|
}
|
|
1242
1487
|
if (!await this.getSysEvent("sys:room_leave")({
|
|
@@ -1288,7 +1533,12 @@ var SocketClient = class {
|
|
|
1288
1533
|
const schema = this.events[event].message;
|
|
1289
1534
|
this.dbg({ type: "register", phase: "register", event });
|
|
1290
1535
|
if (!this.socket) {
|
|
1291
|
-
this.dbg({
|
|
1536
|
+
this.dbg({
|
|
1537
|
+
type: "register",
|
|
1538
|
+
phase: "register",
|
|
1539
|
+
event,
|
|
1540
|
+
err: "Socket is null"
|
|
1541
|
+
});
|
|
1292
1542
|
return () => {
|
|
1293
1543
|
};
|
|
1294
1544
|
}
|