@emeryld/rrroutes-server 2.6.1 → 2.6.3

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.js CHANGED
@@ -341,6 +341,44 @@ function adaptRouteBeforeMw(mw) {
341
341
  }
342
342
  };
343
343
  }
344
+ function runRouteBeforeHandler(mw, args) {
345
+ return new Promise((resolve, reject) => {
346
+ let settled = false;
347
+ const next = (err) => {
348
+ if (settled) return;
349
+ settled = true;
350
+ if (err) {
351
+ reject(err);
352
+ return;
353
+ }
354
+ resolve();
355
+ };
356
+ try {
357
+ const result = mw({ ...args, next });
358
+ if (result && typeof result.then === "function") {
359
+ ;
360
+ result.then(() => {
361
+ if (settled) return;
362
+ settled = true;
363
+ resolve();
364
+ }).catch((err) => {
365
+ if (settled) return;
366
+ settled = true;
367
+ reject(err);
368
+ });
369
+ return;
370
+ }
371
+ if (!settled) {
372
+ settled = true;
373
+ resolve();
374
+ }
375
+ } catch (err) {
376
+ if (settled) return;
377
+ settled = true;
378
+ reject(err);
379
+ }
380
+ });
381
+ }
344
382
  function logHandlerDebugWithRoutesLogger(logger, event) {
345
383
  if (!logger || event.type !== "handler") return;
346
384
  const payload = [
@@ -393,6 +431,7 @@ function createRRRoute(router, config) {
393
431
  const send = config.send ?? defaultSend;
394
432
  const { emit: defaultEmitDebug, mode: defaultDebugMode } = createServerDebugEmitter(config.debug);
395
433
  const knownLeaves = /* @__PURE__ */ new Map();
434
+ const registeredDefs = /* @__PURE__ */ new Map();
396
435
  const decorateDebugEvent = (isVerbose, event, details) => {
397
436
  if (!isVerbose || !details) return event;
398
437
  return { ...event, ...details };
@@ -694,6 +733,71 @@ function createRRRoute(router, config) {
694
733
  };
695
734
  router[method](path, ...before, wrapped);
696
735
  registered.add(key);
736
+ registeredDefs.set(key, {
737
+ leaf,
738
+ def
739
+ });
740
+ }
741
+ async function invoke(key, args) {
742
+ const registration = registeredDefs.get(key);
743
+ if (!registration) {
744
+ throw new Error(`No controller registered for route: ${key}`);
745
+ }
746
+ const { leaf, def } = registration;
747
+ const req = args.req;
748
+ const res = args.res;
749
+ const next = args.next ?? (() => void 0);
750
+ const parsedParams = leaf.cfg.paramsSchema ? lowProfileParse(leaf.cfg.paramsSchema, args.params) : args.params;
751
+ const parsedQueryInput = leaf.cfg.querySchema ? decodeJsonLikeQueryValue(args.query) : args.query;
752
+ let parsedQuery = parsedQueryInput;
753
+ if (leaf.cfg.querySchema) {
754
+ try {
755
+ parsedQuery = lowProfileParse(
756
+ leaf.cfg.querySchema,
757
+ parsedQueryInput
758
+ );
759
+ } catch (err) {
760
+ const parseError = new Error(
761
+ `Query parsing error: ${err.message ?? String(err)}`
762
+ );
763
+ parseError.raw = JSON.stringify(args.query);
764
+ parseError.cause = err;
765
+ throw parseError;
766
+ }
767
+ }
768
+ const parsedBody = leaf.cfg.bodySchema ? lowProfileParse(leaf.cfg.bodySchema, args.body) : args.body;
769
+ const payload = {
770
+ params: parsedParams,
771
+ query: parsedQuery,
772
+ body: parsedBody,
773
+ bodyFiles: args.bodyFiles
774
+ };
775
+ setRouteRequestPayload(res, payload);
776
+ const ctx = args.ctx ?? await config.buildCtx({
777
+ req,
778
+ res
779
+ });
780
+ res.locals[CTX_SYMBOL] = ctx;
781
+ for (const before of def.before ?? []) {
782
+ await runRouteBeforeHandler(before, {
783
+ req,
784
+ res,
785
+ ctx,
786
+ ...payload
787
+ });
788
+ }
789
+ const result = await def.handler({
790
+ req,
791
+ res,
792
+ next,
793
+ ctx,
794
+ params: payload.params,
795
+ query: payload.query,
796
+ body: payload.body,
797
+ bodyFiles: payload.bodyFiles
798
+ });
799
+ const output = validateOutput && leaf.cfg.outputSchema ? lowProfileParse(leaf.cfg.outputSchema, result) : result;
800
+ return output;
697
801
  }
698
802
  function registerControllers(registry, controllers, all) {
699
803
  for (const leaf of registry.all) {
@@ -738,6 +842,7 @@ function createRRRoute(router, config) {
738
842
  register,
739
843
  registerControllers,
740
844
  warnMissingControllers: warnMissing,
845
+ invoke,
741
846
  getRegisteredKeys: () => Array.from(registered)
742
847
  };
743
848
  }
@@ -751,6 +856,59 @@ function bindAll(router, registry, controllers, config) {
751
856
  server.registerControllers(registry, controllers);
752
857
  return router;
753
858
  }
859
+ function batchLeaf(server, path, registry, options) {
860
+ const method = String(options?.method ?? "post").toLowerCase();
861
+ const allowedMethods = ["get", "post", "put", "patch", "delete"];
862
+ if (!allowedMethods.includes(method)) {
863
+ throw new Error(
864
+ `Invalid batch method "${String(options?.method)}". Expected one of: ${allowedMethods.join(", ")}.`
865
+ );
866
+ }
867
+ ;
868
+ server.router[method](
869
+ path,
870
+ async (req, res, next) => {
871
+ try {
872
+ const body = req.body;
873
+ if (!isPlainObject2(body)) {
874
+ throw new Error(
875
+ "Batch request body must be a plain object keyed by branch aliases."
876
+ );
877
+ }
878
+ const entries = Object.entries(body);
879
+ const outputEntries = await Promise.all(
880
+ entries.map(async ([alias, value]) => {
881
+ const payload = isPlainObject2(value) ? value : {};
882
+ if (typeof payload.encodedLeaf !== "string" || payload.encodedLeaf.length === 0) {
883
+ throw new Error(
884
+ `Batch entry "${alias}" must include a non-empty "encodedLeaf" string.`
885
+ );
886
+ }
887
+ const decodedKey = decodeURIComponent(payload.encodedLeaf);
888
+ const leaf = registry.byKey[decodedKey];
889
+ if (!leaf) {
890
+ throw new Error(`Unknown batch route key: ${decodedKey}`);
891
+ }
892
+ const result = await server.invoke(decodedKey, {
893
+ req,
894
+ res,
895
+ next,
896
+ params: payload.params,
897
+ query: payload.query,
898
+ body: payload.body,
899
+ bodyFiles: payload.bodyFiles
900
+ });
901
+ return [alias, result];
902
+ })
903
+ );
904
+ res.json(Object.fromEntries(outputEntries));
905
+ } catch (err) {
906
+ next(err);
907
+ }
908
+ }
909
+ );
910
+ return server.router;
911
+ }
754
912
  var defineControllers = () => (m) => m;
755
913
  function warnMissingControllers(router, registry, logger) {
756
914
  const registeredStore = router[REGISTERED_ROUTES_SYMBOL];
@@ -1578,6 +1736,7 @@ var createConnectionLoggingMiddleware = (options = {}) => {
1578
1736
  };
1579
1737
  export {
1580
1738
  CTX_SYMBOL,
1739
+ batchLeaf,
1581
1740
  bindAll,
1582
1741
  bindExpressRoutes,
1583
1742
  buildLowProfileLeaf,