@nilejs/nile 0.0.6 → 0.0.7

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 CHANGED
@@ -42,10 +42,84 @@ __export(index_exports, {
42
42
  getContext: () => getContext,
43
43
  getLogs: () => getLogs,
44
44
  getZodSchema: () => getZodSchema,
45
- handleError: () => handleError
45
+ handleError: () => handleError,
46
+ handleFormDataRequest: () => handleFormDataRequest,
47
+ validateFiles: () => validateFiles,
48
+ verifyJWT: () => verifyJWT
46
49
  });
47
50
  module.exports = __toCommonJS(index_exports);
48
51
 
52
+ // auth/jwt-handler.ts
53
+ var import_jwt = require("hono/jwt");
54
+ var import_slang_ts = require("slang-ts");
55
+ function extractUserId(claims) {
56
+ const value = claims.userId ?? claims.id ?? claims.sub;
57
+ return typeof value === "string" ? value : null;
58
+ }
59
+ function extractOrganizationId(claims) {
60
+ const value = claims.organizationId ?? claims.organization_id ?? claims.orgId;
61
+ return typeof value === "string" ? value : null;
62
+ }
63
+ function extractTokenFromHeader(headers, headerName) {
64
+ if (!headers) {
65
+ return (0, import_slang_ts.Ok)(null);
66
+ }
67
+ const authHeader = headers.get(headerName);
68
+ if (!authHeader) {
69
+ return (0, import_slang_ts.Ok)(null);
70
+ }
71
+ if (!authHeader.startsWith("Bearer ")) {
72
+ return (0, import_slang_ts.Err)("Authorization header must use Bearer scheme");
73
+ }
74
+ return (0, import_slang_ts.Ok)(authHeader.substring(7));
75
+ }
76
+ function extractTokenFromCookie(cookies, cookieName) {
77
+ if (!cookies) {
78
+ return null;
79
+ }
80
+ return cookies[cookieName] ?? null;
81
+ }
82
+ function extractToken(context, config) {
83
+ const method = config.method ?? "header";
84
+ if (method === "header") {
85
+ const headerName = config.headerName ?? "authorization";
86
+ return extractTokenFromHeader(context.headers, headerName);
87
+ }
88
+ const cookieName = config.cookieName ?? "auth_token";
89
+ return (0, import_slang_ts.Ok)(extractTokenFromCookie(context.cookies, cookieName));
90
+ }
91
+ async function verifyJWT(context, config) {
92
+ const tokenResult = extractToken(context, config);
93
+ if (tokenResult.isErr) {
94
+ return (0, import_slang_ts.Err)(tokenResult.error);
95
+ }
96
+ const token = tokenResult.value;
97
+ if (!token) {
98
+ return (0, import_slang_ts.Err)(`No JWT token found in ${config.method ?? "header"}`);
99
+ }
100
+ try {
101
+ const claims = await (0, import_jwt.verify)(token, config.secret, "HS256");
102
+ if (!claims) {
103
+ return (0, import_slang_ts.Err)("Invalid JWT token");
104
+ }
105
+ const userId = extractUserId(claims);
106
+ const organizationId = extractOrganizationId(
107
+ claims
108
+ );
109
+ if (!(userId && organizationId)) {
110
+ return (0, import_slang_ts.Err)("Missing userId or organizationId in JWT token");
111
+ }
112
+ return (0, import_slang_ts.Ok)({
113
+ userId,
114
+ organizationId,
115
+ claims
116
+ });
117
+ } catch (error) {
118
+ const message = error instanceof Error ? error.message : "JWT verification failed";
119
+ return (0, import_slang_ts.Err)(`JWT authentication failed: ${message}`);
120
+ }
121
+ }
122
+
49
123
  // engine/create-action.ts
50
124
  function createAction(config) {
51
125
  return config;
@@ -315,17 +389,17 @@ var createLogger = (appName, config) => {
315
389
  };
316
390
 
317
391
  // nile/server.ts
318
- var import_slang_ts7 = require("slang-ts");
392
+ var import_slang_ts8 = require("slang-ts");
319
393
 
320
394
  // engine/engine.ts
321
- var import_slang_ts4 = require("slang-ts");
395
+ var import_slang_ts5 = require("slang-ts");
322
396
 
323
397
  // utils/db/create-model.ts
324
398
  var import_drizzle_orm = require("drizzle-orm");
325
- var import_slang_ts2 = require("slang-ts");
399
+ var import_slang_ts3 = require("slang-ts");
326
400
 
327
401
  // utils/handle-error.ts
328
- var import_slang_ts = require("slang-ts");
402
+ var import_slang_ts2 = require("slang-ts");
329
403
  var CALLER_LINE_REGEX = /at\s+(\S+)\s+/;
330
404
  function inferCallerName() {
331
405
  const _err = new Error("capture stack trace");
@@ -357,7 +431,7 @@ function handleError(params) {
357
431
  message: params.message,
358
432
  data: params.data
359
433
  });
360
- return (0, import_slang_ts.Err)(`[${logId}] ${params.message}`);
434
+ return (0, import_slang_ts2.Err)(`[${logId}] ${params.message}`);
361
435
  }
362
436
 
363
437
  // utils/db/create-transaction-variant.ts
@@ -458,7 +532,7 @@ function createModel(table, options) {
458
532
  });
459
533
  }
460
534
  const db = dbx ? asDb(dbx) : getDb();
461
- const result = await (0, import_slang_ts2.safeTry)(
535
+ const result = await (0, import_slang_ts3.safeTry)(
462
536
  () => db.insert(table).values(data).returning()
463
537
  );
464
538
  if (result.isErr) {
@@ -475,7 +549,7 @@ function createModel(table, options) {
475
549
  atFunction: `${name}.create`
476
550
  });
477
551
  }
478
- return (0, import_slang_ts2.Ok)(row);
552
+ return (0, import_slang_ts3.Ok)(row);
479
553
  };
480
554
  const update = async ({ id, data, dbx }) => {
481
555
  const parsed = schemas.update.safeParse(data);
@@ -488,7 +562,7 @@ function createModel(table, options) {
488
562
  }
489
563
  const db = dbx ? asDb(dbx) : getDb();
490
564
  const idCol = tableRef.id;
491
- const result = await (0, import_slang_ts2.safeTry)(
565
+ const result = await (0, import_slang_ts3.safeTry)(
492
566
  () => db.update(table).set(data).where((0, import_drizzle_orm.eq)(idCol, id)).returning()
493
567
  );
494
568
  if (result.isErr) {
@@ -506,7 +580,7 @@ function createModel(table, options) {
506
580
  atFunction: `${name}.update`
507
581
  });
508
582
  }
509
- return (0, import_slang_ts2.Ok)(row);
583
+ return (0, import_slang_ts3.Ok)(row);
510
584
  };
511
585
  const createTx = createTransactionVariant(
512
586
  create
@@ -517,7 +591,7 @@ function createModel(table, options) {
517
591
  const findById = async (id) => {
518
592
  const db = getDb();
519
593
  const idCol = tableRef.id;
520
- const result = await (0, import_slang_ts2.safeTry)(
594
+ const result = await (0, import_slang_ts3.safeTry)(
521
595
  () => db.select().from(table).where((0, import_drizzle_orm.eq)(idCol, id))
522
596
  );
523
597
  if (result.isErr) {
@@ -535,12 +609,12 @@ function createModel(table, options) {
535
609
  atFunction: `${name}.findById`
536
610
  });
537
611
  }
538
- return (0, import_slang_ts2.Ok)(row);
612
+ return (0, import_slang_ts3.Ok)(row);
539
613
  };
540
614
  const deleteFn = async (id) => {
541
615
  const db = getDb();
542
616
  const idCol = tableRef.id;
543
- const result = await (0, import_slang_ts2.safeTry)(
617
+ const result = await (0, import_slang_ts3.safeTry)(
544
618
  () => db.delete(table).where((0, import_drizzle_orm.eq)(idCol, id)).returning()
545
619
  );
546
620
  if (result.isErr) {
@@ -558,12 +632,12 @@ function createModel(table, options) {
558
632
  atFunction: `${name}.delete`
559
633
  });
560
634
  }
561
- return (0, import_slang_ts2.Ok)(row);
635
+ return (0, import_slang_ts3.Ok)(row);
562
636
  };
563
637
  const findAll = async () => {
564
638
  const db = getDb();
565
639
  const tsCol = findTimestampColumn(tableRef);
566
- const result = await (0, import_slang_ts2.safeTry)(() => {
640
+ const result = await (0, import_slang_ts3.safeTry)(() => {
567
641
  const query = db.select().from(table);
568
642
  if (!tsCol) {
569
643
  return query;
@@ -577,12 +651,12 @@ function createModel(table, options) {
577
651
  atFunction: `${name}.findAll`
578
652
  });
579
653
  }
580
- return (0, import_slang_ts2.Ok)(result.value ?? []);
654
+ return (0, import_slang_ts3.Ok)(result.value ?? []);
581
655
  };
582
656
  const findOffsetPage = async (limit, offset) => {
583
657
  const db = getDb();
584
658
  const tsCol = findTimestampColumn(tableRef);
585
- const itemsResult = await (0, import_slang_ts2.safeTry)(() => {
659
+ const itemsResult = await (0, import_slang_ts3.safeTry)(() => {
586
660
  const query = db.select().from(table);
587
661
  const ordered = tsCol ? query.orderBy((0, import_drizzle_orm.desc)(tableRef[tsCol])) : query;
588
662
  return ordered.limit(limit).offset(offset);
@@ -594,7 +668,7 @@ function createModel(table, options) {
594
668
  atFunction: `${name}.findPaginated`
595
669
  });
596
670
  }
597
- const countResult = await (0, import_slang_ts2.safeTry)(
671
+ const countResult = await (0, import_slang_ts3.safeTry)(
598
672
  () => db.select({ total: (0, import_drizzle_orm.count)() }).from(table)
599
673
  );
600
674
  if (countResult.isErr) {
@@ -606,7 +680,7 @@ function createModel(table, options) {
606
680
  }
607
681
  const items = itemsResult.value ?? [];
608
682
  const total = countResult.value?.[0]?.total ?? 0;
609
- return (0, import_slang_ts2.Ok)({
683
+ return (0, import_slang_ts3.Ok)({
610
684
  items,
611
685
  total,
612
686
  hasMore: offset + items.length < total
@@ -622,7 +696,7 @@ function createModel(table, options) {
622
696
  });
623
697
  }
624
698
  const typedColumn = column;
625
- const result = await (0, import_slang_ts2.safeTry)(
699
+ const result = await (0, import_slang_ts3.safeTry)(
626
700
  () => db.select().from(table).where((0, import_drizzle_orm.lt)(typedColumn, cursor)).orderBy((0, import_drizzle_orm.desc)(typedColumn)).limit(limit + 1)
627
701
  );
628
702
  if (result.isErr) {
@@ -637,7 +711,7 @@ function createModel(table, options) {
637
711
  const items = hasMore ? rows.slice(0, limit) : rows;
638
712
  const lastItem = items.at(-1);
639
713
  const nextCursor = lastItem ? String(lastItem[colName] ?? "") || null : null;
640
- return (0, import_slang_ts2.Ok)({
714
+ return (0, import_slang_ts3.Ok)({
641
715
  items,
642
716
  nextCursor,
643
717
  hasMore
@@ -694,10 +768,10 @@ function createDiagnosticsLog(prefix, params) {
694
768
  }
695
769
 
696
770
  // engine/pipeline.ts
697
- var import_slang_ts3 = require("slang-ts");
771
+ var import_slang_ts4 = require("slang-ts");
698
772
  var import_zod = require("zod");
699
773
  async function runHook(hookDef, hookAction, input, nileContext) {
700
- const result = await (0, import_slang_ts3.safeTry)(
774
+ const result = await (0, import_slang_ts4.safeTry)(
701
775
  () => hookAction.handler(input, nileContext)
702
776
  );
703
777
  return {
@@ -719,7 +793,7 @@ async function processHooks(hooks, initialValue, getAction, nileContext, logTarg
719
793
  log(errorMsg);
720
794
  if (hookDef.isCritical) {
721
795
  nileContext.setHookError(errorMsg);
722
- return (0, import_slang_ts3.Err)(errorMsg);
796
+ return (0, import_slang_ts4.Err)(errorMsg);
723
797
  }
724
798
  continue;
725
799
  }
@@ -738,72 +812,90 @@ async function processHooks(hooks, initialValue, getAction, nileContext, logTarg
738
812
  );
739
813
  if (hookDef.isCritical) {
740
814
  nileContext.setHookError(errorMsg);
741
- return (0, import_slang_ts3.Err)(errorMsg);
815
+ return (0, import_slang_ts4.Err)(errorMsg);
742
816
  }
743
817
  continue;
744
818
  }
745
819
  currentValue = result.value;
746
820
  }
747
- return (0, import_slang_ts3.Ok)(currentValue);
821
+ return (0, import_slang_ts4.Ok)(currentValue);
748
822
  }
749
823
  async function runGlobalBeforeHook(handler, nileContext, action, payload, log) {
750
824
  if (!handler) {
751
- return (0, import_slang_ts3.Ok)(true);
825
+ return (0, import_slang_ts4.Ok)(true);
752
826
  }
753
- const result = await (0, import_slang_ts3.safeTry)(() => handler({ nileContext, action, payload }));
827
+ const result = await (0, import_slang_ts4.safeTry)(() => handler({ nileContext, action, payload }));
754
828
  if (result.isErr) {
755
829
  log(`Global before hook failed for ${action.name}`);
756
830
  nileContext.setHookError(result.error);
757
- return (0, import_slang_ts3.Err)(result.error);
831
+ return (0, import_slang_ts4.Err)(result.error);
758
832
  }
759
- return (0, import_slang_ts3.Ok)(true);
833
+ return (0, import_slang_ts4.Ok)(true);
760
834
  }
761
835
  async function runGlobalAfterHook(handler, nileContext, action, payload, currentResult, log) {
762
836
  if (!handler) {
763
- return (0, import_slang_ts3.Ok)(currentResult);
837
+ return (0, import_slang_ts4.Ok)(currentResult);
764
838
  }
765
- const result = await (0, import_slang_ts3.safeTry)(
839
+ const result = await (0, import_slang_ts4.safeTry)(
766
840
  () => handler({
767
841
  nileContext,
768
842
  action,
769
843
  payload,
770
- result: (0, import_slang_ts3.Ok)(currentResult)
844
+ result: (0, import_slang_ts4.Ok)(currentResult)
771
845
  })
772
846
  );
773
847
  if (result.isErr) {
774
848
  log(`Global after hook failed for ${action.name}`);
775
849
  nileContext.setHookError(result.error);
776
- return (0, import_slang_ts3.Err)(result.error);
850
+ return (0, import_slang_ts4.Err)(result.error);
777
851
  }
778
- return (0, import_slang_ts3.Ok)(result.value);
852
+ return (0, import_slang_ts4.Ok)(result.value);
779
853
  }
780
854
  function validatePayload(action, payload, nileContext, log) {
781
855
  if (!action.validation) {
782
- return (0, import_slang_ts3.Ok)(payload);
856
+ return (0, import_slang_ts4.Ok)(payload);
783
857
  }
784
858
  const parseResult = action.validation.safeParse(payload);
785
859
  if (!parseResult.success) {
786
860
  const validationError = (0, import_zod.prettifyError)(parseResult.error);
787
861
  log(`Validation failed for ${action.name}`, validationError);
788
862
  nileContext.setHookError(validationError);
789
- return (0, import_slang_ts3.Err)(`Validation failed: ${validationError}`);
863
+ return (0, import_slang_ts4.Err)(`Validation failed: ${validationError}`);
790
864
  }
791
- return (0, import_slang_ts3.Ok)(parseResult.data);
865
+ return (0, import_slang_ts4.Ok)(parseResult.data);
792
866
  }
793
867
  async function runHandler(action, payload, nileContext, log) {
794
- const result = await (0, import_slang_ts3.safeTry)(
868
+ const result = await (0, import_slang_ts4.safeTry)(
795
869
  () => action.handler(payload, nileContext)
796
870
  );
797
871
  if (result.isErr) {
798
872
  log(`Handler failed for ${action.name}`, result.error);
799
873
  nileContext.setHookError(result.error);
800
- return (0, import_slang_ts3.Err)(result.error);
874
+ return (0, import_slang_ts4.Err)(result.error);
801
875
  }
802
876
  nileContext.setHookOutput(result.value);
803
- return (0, import_slang_ts3.Ok)(result.value);
877
+ return (0, import_slang_ts4.Ok)(result.value);
804
878
  }
805
879
 
806
880
  // engine/engine.ts
881
+ async function authenticateAction(action, auth, authContext, nileContext, serviceName, actionName, log) {
882
+ if (!(action.isProtected && auth)) {
883
+ return (0, import_slang_ts5.Ok)(void 0);
884
+ }
885
+ if (!authContext) {
886
+ return (0, import_slang_ts5.Err)("Authentication required: no auth context provided");
887
+ }
888
+ const authResult = await verifyJWT(authContext, auth);
889
+ if (authResult.isErr) {
890
+ log(`Auth failed for ${serviceName}.${actionName}: ${authResult.error}`);
891
+ return (0, import_slang_ts5.Err)(authResult.error);
892
+ }
893
+ nileContext.authResult = authResult.value;
894
+ log(
895
+ `Auth OK for ${serviceName}.${actionName} (user: ${authResult.value.userId})`
896
+ );
897
+ return (0, import_slang_ts5.Ok)(void 0);
898
+ }
807
899
  function createEngine(options) {
808
900
  const { diagnostics, services, logger } = options;
809
901
  const log = createDiagnosticsLog("Engine", {
@@ -814,11 +906,25 @@ function createEngine(options) {
814
906
  const serviceActionsStore = {};
815
907
  const actionStore = {};
816
908
  const initStartTime = performance.now();
909
+ const seenServiceNames = /* @__PURE__ */ new Set();
817
910
  for (const service of services) {
911
+ if (seenServiceNames.has(service.name)) {
912
+ throw new Error(
913
+ `Duplicate service name '${service.name}'. Service names must be unique.`
914
+ );
915
+ }
916
+ seenServiceNames.add(service.name);
917
+ const seenActionNames = /* @__PURE__ */ new Set();
818
918
  const actionNames = [];
819
919
  serviceActionsStore[service.name] = [];
820
920
  actionStore[service.name] = {};
821
921
  for (const action of service.actions) {
922
+ if (seenActionNames.has(action.name)) {
923
+ throw new Error(
924
+ `Duplicate action name '${action.name}' in service '${service.name}'. Action names must be unique within a service.`
925
+ );
926
+ }
927
+ seenActionNames.add(action.name);
822
928
  actionNames.push(action.name);
823
929
  serviceActionsStore[service.name]?.push({
824
930
  name: action.name,
@@ -842,27 +948,39 @@ function createEngine(options) {
842
948
  log(
843
949
  `Initialized in ${performance.now() - initStartTime}ms. Loaded ${services.length} services.`
844
950
  );
845
- const getServices = () => (0, import_slang_ts4.Ok)(serviceSummaries);
951
+ const getServices = () => (0, import_slang_ts5.Ok)(serviceSummaries);
846
952
  const getServiceActions = (serviceName) => {
847
953
  const actions = serviceActionsStore[serviceName];
848
- return actions ? (0, import_slang_ts4.Ok)(actions) : (0, import_slang_ts4.Err)(`Service '${serviceName}' not found`);
954
+ return actions ? (0, import_slang_ts5.Ok)(actions) : (0, import_slang_ts5.Err)(`Service '${serviceName}' not found`);
849
955
  };
850
956
  const getAction = (serviceName, actionName) => {
851
957
  const serviceMap = actionStore[serviceName];
852
958
  if (!serviceMap) {
853
- return (0, import_slang_ts4.Err)(`Service '${serviceName}' not found`);
959
+ return (0, import_slang_ts5.Err)(`Service '${serviceName}' not found`);
854
960
  }
855
961
  const action = serviceMap[actionName];
856
- return action ? (0, import_slang_ts4.Ok)(action) : (0, import_slang_ts4.Err)(`Action '${actionName}' not found in service '${serviceName}'`);
962
+ return action ? (0, import_slang_ts5.Ok)(action) : (0, import_slang_ts5.Err)(`Action '${actionName}' not found in service '${serviceName}'`);
857
963
  };
858
- const executeAction = async (serviceName, actionName, payload, nileContext) => {
964
+ const executeAction = async (serviceName, actionName, payload, nileContext, authContext) => {
859
965
  const { onBeforeActionHandler, onAfterActionHandler } = options;
860
966
  const actionResult = getAction(serviceName, actionName);
861
967
  if (actionResult.isErr) {
862
- return (0, import_slang_ts4.Err)(actionResult.error);
968
+ return (0, import_slang_ts5.Err)(actionResult.error);
863
969
  }
864
970
  const action = actionResult.value;
865
971
  nileContext.resetHookContext(`${serviceName}.${actionName}`, payload);
972
+ const authStep = await authenticateAction(
973
+ action,
974
+ options.auth,
975
+ authContext,
976
+ nileContext,
977
+ serviceName,
978
+ actionName,
979
+ log
980
+ );
981
+ if (authStep.isErr) {
982
+ return (0, import_slang_ts5.Err)(authStep.error);
983
+ }
866
984
  const globalBeforeResult = await runGlobalBeforeHook(
867
985
  onBeforeActionHandler,
868
986
  nileContext,
@@ -871,7 +989,7 @@ function createEngine(options) {
871
989
  log
872
990
  );
873
991
  if (globalBeforeResult.isErr) {
874
- return (0, import_slang_ts4.Err)(globalBeforeResult.error);
992
+ return (0, import_slang_ts5.Err)(globalBeforeResult.error);
875
993
  }
876
994
  const beforeHooksResult = await processHooks(
877
995
  action.hooks?.before ?? [],
@@ -882,7 +1000,7 @@ function createEngine(options) {
882
1000
  log
883
1001
  );
884
1002
  if (beforeHooksResult.isErr) {
885
- return (0, import_slang_ts4.Err)(beforeHooksResult.error);
1003
+ return (0, import_slang_ts5.Err)(beforeHooksResult.error);
886
1004
  }
887
1005
  const validationResult = validatePayload(
888
1006
  action,
@@ -891,7 +1009,7 @@ function createEngine(options) {
891
1009
  log
892
1010
  );
893
1011
  if (validationResult.isErr) {
894
- return (0, import_slang_ts4.Err)(validationResult.error);
1012
+ return (0, import_slang_ts5.Err)(validationResult.error);
895
1013
  }
896
1014
  const handlerResult = await runHandler(
897
1015
  action,
@@ -900,7 +1018,7 @@ function createEngine(options) {
900
1018
  log
901
1019
  );
902
1020
  if (handlerResult.isErr) {
903
- return (0, import_slang_ts4.Err)(handlerResult.error);
1021
+ return (0, import_slang_ts5.Err)(handlerResult.error);
904
1022
  }
905
1023
  const afterHooksResult = await processHooks(
906
1024
  action.hooks?.after ?? [],
@@ -911,7 +1029,7 @@ function createEngine(options) {
911
1029
  log
912
1030
  );
913
1031
  if (afterHooksResult.isErr) {
914
- return (0, import_slang_ts4.Err)(afterHooksResult.error);
1032
+ return (0, import_slang_ts5.Err)(afterHooksResult.error);
915
1033
  }
916
1034
  const globalAfterResult = await runGlobalAfterHook(
917
1035
  onAfterActionHandler,
@@ -922,12 +1040,12 @@ function createEngine(options) {
922
1040
  log
923
1041
  );
924
1042
  if (globalAfterResult.isErr) {
925
- return (0, import_slang_ts4.Err)(globalAfterResult.error);
1043
+ return (0, import_slang_ts5.Err)(globalAfterResult.error);
926
1044
  }
927
- return action.result?.pipeline ? (0, import_slang_ts4.Ok)({
1045
+ return action.result?.pipeline ? (0, import_slang_ts5.Ok)({
928
1046
  data: globalAfterResult.value,
929
1047
  pipeline: nileContext.hookContext.log
930
- }) : (0, import_slang_ts4.Ok)(globalAfterResult.value);
1048
+ }) : (0, import_slang_ts5.Ok)(globalAfterResult.value);
931
1049
  };
932
1050
  return {
933
1051
  getServices,
@@ -939,6 +1057,7 @@ function createEngine(options) {
939
1057
 
940
1058
  // rest/rest.ts
941
1059
  var import_hono = require("hono");
1060
+ var import_cookie = require("hono/cookie");
942
1061
  var import_zod3 = __toESM(require("zod"), 1);
943
1062
 
944
1063
  // cors/cors.ts
@@ -1024,7 +1143,7 @@ var evaluateResolver = (resolver, origin, c, defaultOpts) => {
1024
1143
  };
1025
1144
 
1026
1145
  // rest/intent-handlers.ts
1027
- var import_slang_ts5 = require("slang-ts");
1146
+ var import_slang_ts6 = require("slang-ts");
1028
1147
  var import_zod2 = __toESM(require("zod"), 1);
1029
1148
  function toExternalResponse(result, successMessage) {
1030
1149
  if (result.isOk) {
@@ -1051,7 +1170,7 @@ function handleExplore(engine, request) {
1051
1170
  }
1052
1171
  const act = actionResult.value;
1053
1172
  return toExternalResponse(
1054
- (0, import_slang_ts5.Ok)({
1173
+ (0, import_slang_ts6.Ok)({
1055
1174
  name: act.name,
1056
1175
  description: act.description,
1057
1176
  isProtected: act.isProtected ?? false,
@@ -1062,7 +1181,7 @@ function handleExplore(engine, request) {
1062
1181
  `Details for '${service}.${action}'`
1063
1182
  );
1064
1183
  }
1065
- async function handleExecute(engine, request, nileContext) {
1184
+ async function handleExecute(engine, request, nileContext, authContext) {
1066
1185
  const { service, action, payload } = request;
1067
1186
  if (service === "*" || action === "*") {
1068
1187
  return {
@@ -1075,7 +1194,8 @@ async function handleExecute(engine, request, nileContext) {
1075
1194
  service,
1076
1195
  action,
1077
1196
  payload,
1078
- nileContext
1197
+ nileContext,
1198
+ authContext
1079
1199
  );
1080
1200
  return toExternalResponse(result, `Action '${service}.${action}' executed`);
1081
1201
  }
@@ -1098,7 +1218,7 @@ function handleSchema(engine, request) {
1098
1218
  actionsResult.value.map((a) => a.name)
1099
1219
  );
1100
1220
  }
1101
- return toExternalResponse((0, import_slang_ts5.Ok)(schemas), "All service schemas");
1221
+ return toExternalResponse((0, import_slang_ts6.Ok)(schemas), "All service schemas");
1102
1222
  }
1103
1223
  if (action === "*") {
1104
1224
  const actionsResult = engine.getServiceActions(service);
@@ -1110,7 +1230,7 @@ function handleSchema(engine, request) {
1110
1230
  service,
1111
1231
  actionsResult.value.map((a) => a.name)
1112
1232
  );
1113
- return toExternalResponse((0, import_slang_ts5.Ok)(schemas), `Schemas for '${service}'`);
1233
+ return toExternalResponse((0, import_slang_ts6.Ok)(schemas), `Schemas for '${service}'`);
1114
1234
  }
1115
1235
  const actionResult = engine.getAction(service, action);
1116
1236
  if (actionResult.isErr) {
@@ -1118,7 +1238,7 @@ function handleSchema(engine, request) {
1118
1238
  }
1119
1239
  const schema = extractActionSchema(actionResult.value);
1120
1240
  return toExternalResponse(
1121
- (0, import_slang_ts5.Ok)({ [action]: schema }),
1241
+ (0, import_slang_ts6.Ok)({ [action]: schema }),
1122
1242
  `Schema for '${service}.${action}'`
1123
1243
  );
1124
1244
  }
@@ -1152,13 +1272,13 @@ function safeTrySync(fn) {
1152
1272
  }
1153
1273
  var intentHandlers = {
1154
1274
  explore: (engine, request) => handleExplore(engine, request),
1155
- execute: (engine, request, nileContext) => handleExecute(engine, request, nileContext),
1275
+ execute: (engine, request, nileContext, authContext) => handleExecute(engine, request, nileContext, authContext),
1156
1276
  schema: (engine, request) => handleSchema(engine, request)
1157
1277
  };
1158
1278
 
1159
1279
  // rest/middleware.ts
1160
1280
  var import_hono_rate_limiter = require("hono-rate-limiter");
1161
- var import_slang_ts6 = require("slang-ts");
1281
+ var import_slang_ts7 = require("slang-ts");
1162
1282
  var ASSETS_REGEX = /^\/assets\//;
1163
1283
  var DEFAULT_RATE_LIMIT_WINDOW_MS = 15 * 60 * 1e3;
1164
1284
  var DEFAULT_RATE_LIMIT_MAX = 100;
@@ -1190,12 +1310,17 @@ function applyRateLimiting(app, config, log) {
1190
1310
  `Rate limiting enabled: ${rateLimiting.limit ?? DEFAULT_RATE_LIMIT_MAX} requests per ${rateLimiting.windowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS}ms window`
1191
1311
  );
1192
1312
  }
1313
+ var STATIC_ADAPTER_MODULES = {
1314
+ bun: "hono/bun",
1315
+ node: "@hono/node-server/serve-static"
1316
+ };
1193
1317
  function applyStaticServing(app, config, runtime, log) {
1194
1318
  if (!config.enableStatic) {
1195
1319
  return;
1196
1320
  }
1197
- if (runtime !== "bun") {
1198
- log(`Static file serving not yet supported for runtime: ${runtime}`);
1321
+ const adapterModule = STATIC_ADAPTER_MODULES[runtime];
1322
+ if (!adapterModule) {
1323
+ log(`Static file serving not supported for runtime: ${runtime}`);
1199
1324
  return;
1200
1325
  }
1201
1326
  let cachedHandler = null;
@@ -1205,15 +1330,18 @@ function applyStaticServing(app, config, runtime, log) {
1205
1330
  return next();
1206
1331
  }
1207
1332
  if (!cachedHandler) {
1208
- const importResult = await (0, import_slang_ts6.safeTry)(async () => {
1209
- const mod = await import("hono/bun");
1333
+ const importResult = await (0, import_slang_ts7.safeTry)(async () => {
1334
+ const mod = await import(adapterModule);
1210
1335
  return mod.serveStatic({
1211
1336
  root: "./assets",
1212
1337
  rewriteRequestPath: (path) => path.replace(ASSETS_REGEX, "")
1213
1338
  });
1214
1339
  });
1215
1340
  if (importResult.isErr) {
1216
- log("Failed to load static file adapter", importResult.error);
1341
+ log(
1342
+ `Failed to load static file adapter for ${runtime}`,
1343
+ importResult.error
1344
+ );
1217
1345
  importFailed = true;
1218
1346
  return next();
1219
1347
  }
@@ -1223,7 +1351,255 @@ function applyStaticServing(app, config, runtime, log) {
1223
1351
  return cachedHandler(c, next);
1224
1352
  }
1225
1353
  });
1226
- log("Static file serving enabled at /assets/*");
1354
+ log(`Static file serving enabled at /assets/* (runtime: ${runtime})`);
1355
+ }
1356
+
1357
+ // rest/uploads/validate-files.ts
1358
+ var DEFAULT_MAX_FILES = 10;
1359
+ var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
1360
+ var DEFAULT_MIN_FILE_SIZE = 1;
1361
+ var DEFAULT_MAX_TOTAL_SIZE = 20 * 1024 * 1024;
1362
+ var DEFAULT_MAX_FILENAME_LENGTH = 128;
1363
+ var DEFAULT_ALLOWED_MIMES = ["image/png", "image/jpeg", "application/pdf"];
1364
+ var DEFAULT_ALLOWED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".pdf"];
1365
+ var PASS = { status: true };
1366
+ function validateFilenameLength(files, maxLength) {
1367
+ const tooLong = files.filter((file) => file.name.length > maxLength);
1368
+ if (tooLong.length === 0) {
1369
+ return PASS;
1370
+ }
1371
+ return {
1372
+ status: false,
1373
+ message: "file name too long",
1374
+ data: {
1375
+ error_category: "validation",
1376
+ files: tooLong.map((f) => f.name),
1377
+ maxLength
1378
+ }
1379
+ };
1380
+ }
1381
+ function validateZeroByteFiles(files) {
1382
+ const emptyFiles = files.filter((file) => file.size === 0);
1383
+ if (emptyFiles.length === 0) {
1384
+ return PASS;
1385
+ }
1386
+ return {
1387
+ status: false,
1388
+ message: "empty file not allowed",
1389
+ data: {
1390
+ error_category: "validation",
1391
+ files: emptyFiles.map((f) => f.name)
1392
+ }
1393
+ };
1394
+ }
1395
+ function validateMinFileSize(files, minFileSize) {
1396
+ const tooSmall = files.filter((file) => file.size < minFileSize);
1397
+ if (tooSmall.length === 0) {
1398
+ return PASS;
1399
+ }
1400
+ return {
1401
+ status: false,
1402
+ message: "file too small",
1403
+ data: {
1404
+ error_category: "validation",
1405
+ limit: "minFileSize",
1406
+ min: minFileSize,
1407
+ files: tooSmall.map((f) => ({ name: f.name, size: f.size }))
1408
+ }
1409
+ };
1410
+ }
1411
+ function validateFileCount(files, maxFiles) {
1412
+ if (files.length <= maxFiles) {
1413
+ return PASS;
1414
+ }
1415
+ return {
1416
+ status: false,
1417
+ message: "upload limit exceeded",
1418
+ data: {
1419
+ error_category: "validation",
1420
+ limit: "maxFiles",
1421
+ max: maxFiles,
1422
+ received: files.length
1423
+ }
1424
+ };
1425
+ }
1426
+ function validateFileSize(files, maxFileSize) {
1427
+ const oversized = files.filter((file) => file.size > maxFileSize);
1428
+ if (oversized.length === 0) {
1429
+ return PASS;
1430
+ }
1431
+ return {
1432
+ status: false,
1433
+ message: "upload limit exceeded",
1434
+ data: {
1435
+ error_category: "validation",
1436
+ limit: "maxFileSize",
1437
+ max: maxFileSize,
1438
+ files: oversized.map((f) => ({ name: f.name, size: f.size }))
1439
+ }
1440
+ };
1441
+ }
1442
+ function validateTotalSize(files, maxTotalSize) {
1443
+ const totalSize = files.reduce((sum, file) => sum + file.size, 0);
1444
+ if (totalSize <= maxTotalSize) {
1445
+ return PASS;
1446
+ }
1447
+ return {
1448
+ status: false,
1449
+ message: "upload limit exceeded",
1450
+ data: {
1451
+ error_category: "validation",
1452
+ limit: "maxTotalSize",
1453
+ max: maxTotalSize,
1454
+ total: totalSize
1455
+ }
1456
+ };
1457
+ }
1458
+ function validateAllowlist(files, allowedMimes, allowedExtensions) {
1459
+ const rejected = files.filter((file) => {
1460
+ const matchesMime = allowedMimes.includes(file.type);
1461
+ const matchesExt = allowedExtensions.some(
1462
+ (ext) => file.name.toLowerCase().endsWith(ext.toLowerCase())
1463
+ );
1464
+ return !(matchesMime && matchesExt);
1465
+ });
1466
+ if (rejected.length === 0) {
1467
+ return PASS;
1468
+ }
1469
+ return {
1470
+ status: false,
1471
+ message: "file type not allowed",
1472
+ data: {
1473
+ error_category: "validation",
1474
+ rejected: rejected.map((f) => ({ name: f.name, type: f.type })),
1475
+ allowed: { mimeTypes: allowedMimes, extensions: allowedExtensions }
1476
+ }
1477
+ };
1478
+ }
1479
+ function validateFiles(files, config) {
1480
+ if (files.length === 0) {
1481
+ return PASS;
1482
+ }
1483
+ const maxFiles = config.limits?.maxFiles ?? DEFAULT_MAX_FILES;
1484
+ const maxFileSize = config.limits?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
1485
+ const minFileSize = config.limits?.minFileSize ?? DEFAULT_MIN_FILE_SIZE;
1486
+ const maxTotalSize = config.limits?.maxTotalSize ?? DEFAULT_MAX_TOTAL_SIZE;
1487
+ const maxFilenameLength = config.limits?.maxFilenameLength ?? DEFAULT_MAX_FILENAME_LENGTH;
1488
+ const allowedMimes = config.allow?.mimeTypes ?? DEFAULT_ALLOWED_MIMES;
1489
+ const allowedExtensions = config.allow?.extensions ?? DEFAULT_ALLOWED_EXTENSIONS;
1490
+ const checks = [
1491
+ validateFilenameLength(files, maxFilenameLength),
1492
+ validateZeroByteFiles(files),
1493
+ validateMinFileSize(files, minFileSize),
1494
+ validateFileCount(files, maxFiles),
1495
+ validateFileSize(files, maxFileSize),
1496
+ validateTotalSize(files, maxTotalSize),
1497
+ validateAllowlist(files, allowedMimes, allowedExtensions)
1498
+ ];
1499
+ for (const check of checks) {
1500
+ if (!check.status) {
1501
+ return check;
1502
+ }
1503
+ }
1504
+ return PASS;
1505
+ }
1506
+
1507
+ // rest/uploads/parse-formdata.ts
1508
+ async function parseBodyToStructured(c) {
1509
+ try {
1510
+ const body = await c.req.parseBody({ all: true });
1511
+ const fields = {};
1512
+ const files = {};
1513
+ const conflicts = [];
1514
+ for (const [key, value] of Object.entries(body)) {
1515
+ if (key === "action") {
1516
+ continue;
1517
+ }
1518
+ if (Array.isArray(value)) {
1519
+ const hasFiles = value.some((v) => v instanceof File);
1520
+ const hasStrings = value.some((v) => typeof v === "string");
1521
+ if (hasFiles && hasStrings) {
1522
+ conflicts.push(key);
1523
+ continue;
1524
+ }
1525
+ if (hasFiles) {
1526
+ files[key] = value.filter((v) => v instanceof File);
1527
+ } else {
1528
+ fields[key] = value.map((v) => String(v));
1529
+ }
1530
+ } else if (value instanceof File) {
1531
+ files[key] = value;
1532
+ } else {
1533
+ fields[key] = String(value);
1534
+ }
1535
+ }
1536
+ if (conflicts.length > 0) {
1537
+ return {
1538
+ status: false,
1539
+ message: "mixed key types not allowed",
1540
+ errorData: {
1541
+ error_category: "validation",
1542
+ conflicts,
1543
+ hint: "Same key cannot be used for both files and fields"
1544
+ }
1545
+ };
1546
+ }
1547
+ return { status: true, data: { fields, files } };
1548
+ } catch (error) {
1549
+ return {
1550
+ status: false,
1551
+ message: "failed to parse request body",
1552
+ errorData: {
1553
+ error_category: "parsing",
1554
+ error: error instanceof Error ? error.message : String(error)
1555
+ }
1556
+ };
1557
+ }
1558
+ }
1559
+ function enforceActionContentType(action, contentType, enforceContentType) {
1560
+ if (!(enforceContentType && action.isSpecial?.contentType)) {
1561
+ return { status: true };
1562
+ }
1563
+ const expected = action.isSpecial.contentType;
1564
+ const matches = contentType.toLowerCase().includes(expected.toLowerCase());
1565
+ if (!matches) {
1566
+ return {
1567
+ status: false,
1568
+ statusCode: 415,
1569
+ message: "unsupported content type",
1570
+ data: {
1571
+ error_category: "validation",
1572
+ expected,
1573
+ received: contentType
1574
+ }
1575
+ };
1576
+ }
1577
+ return { status: true };
1578
+ }
1579
+ async function handleFormDataRequest(c, config, _uploadMode = "flat") {
1580
+ const parseResult = await parseBodyToStructured(c);
1581
+ if (!(parseResult.status && parseResult.data)) {
1582
+ return parseResult;
1583
+ }
1584
+ const payload = parseResult.data;
1585
+ const allFiles = [];
1586
+ for (const value of Object.values(payload.files)) {
1587
+ if (Array.isArray(value)) {
1588
+ allFiles.push(...value);
1589
+ } else {
1590
+ allFiles.push(value);
1591
+ }
1592
+ }
1593
+ const validationResult = validateFiles(allFiles, config);
1594
+ if (!validationResult.status) {
1595
+ return {
1596
+ status: false,
1597
+ message: validationResult.message,
1598
+ errorData: validationResult.data,
1599
+ statusCode: validationResult.statusCode
1600
+ };
1601
+ }
1602
+ return { status: true, data: payload };
1227
1603
  }
1228
1604
 
1229
1605
  // rest/rest.ts
@@ -1233,6 +1609,87 @@ var externalRequestSchema = import_zod3.default.object({
1233
1609
  action: import_zod3.default.string().min(1),
1234
1610
  payload: import_zod3.default.record(import_zod3.default.string(), import_zod3.default.unknown())
1235
1611
  });
1612
+ var formDataRoutingSchema = import_zod3.default.object({
1613
+ intent: import_zod3.default.enum(["explore", "execute", "schema"]),
1614
+ service: import_zod3.default.string().min(1),
1615
+ action: import_zod3.default.string().min(1)
1616
+ });
1617
+ async function handleFormDataPath(c, config, engine, nileContext, authContext, log) {
1618
+ const rawBody = await c.req.parseBody({ all: true }).catch(() => null);
1619
+ if (!rawBody) {
1620
+ return c.json(
1621
+ {
1622
+ status: false,
1623
+ message: "Failed to parse multipart form data",
1624
+ data: {}
1625
+ },
1626
+ 400
1627
+ );
1628
+ }
1629
+ const routing = formDataRoutingSchema.safeParse({
1630
+ intent: rawBody.intent,
1631
+ service: rawBody.service,
1632
+ action: rawBody.action
1633
+ });
1634
+ if (!routing.success) {
1635
+ return c.json(
1636
+ {
1637
+ status: false,
1638
+ message: "Form-data must include 'intent', 'service', and 'action' fields",
1639
+ data: { errors: routing.error.issues }
1640
+ },
1641
+ 400
1642
+ );
1643
+ }
1644
+ const { intent, service, action } = routing.data;
1645
+ log(`${intent} -> ${service}.${action} (form-data)`);
1646
+ const actionResult = engine.getAction(service, action);
1647
+ if (actionResult.isOk && config.uploads?.enforceContentType) {
1648
+ const contentTypeCheck = enforceActionContentType(
1649
+ actionResult.value,
1650
+ "multipart/form-data",
1651
+ true
1652
+ );
1653
+ if (!contentTypeCheck.status) {
1654
+ return c.json(
1655
+ {
1656
+ status: false,
1657
+ message: contentTypeCheck.message ?? "Unsupported content type",
1658
+ data: contentTypeCheck.data ?? {}
1659
+ },
1660
+ contentTypeCheck.statusCode ?? 415
1661
+ );
1662
+ }
1663
+ }
1664
+ const uploadConfig = config.uploads ?? {};
1665
+ const uploadMode = actionResult.isOk ? actionResult.value.isSpecial?.uploadMode ?? "flat" : "flat";
1666
+ const uploadResult = await handleFormDataRequest(c, uploadConfig, uploadMode);
1667
+ if (!(uploadResult.status && uploadResult.data)) {
1668
+ return c.json(
1669
+ {
1670
+ status: false,
1671
+ message: uploadResult.message ?? "Upload validation failed",
1672
+ data: uploadResult.errorData ?? {}
1673
+ },
1674
+ uploadResult.statusCode ?? 400
1675
+ );
1676
+ }
1677
+ const request = {
1678
+ intent,
1679
+ service,
1680
+ action,
1681
+ payload: uploadResult.data
1682
+ };
1683
+ const handler = intentHandlers[request.intent];
1684
+ const response = await handler(
1685
+ engine,
1686
+ request,
1687
+ nileContext,
1688
+ authContext
1689
+ );
1690
+ const statusCode = response.status ? 200 : 400;
1691
+ return c.json(response, statusCode);
1692
+ }
1236
1693
  function createRestApp(params) {
1237
1694
  const { config, engine, nileContext, serverName, runtime } = params;
1238
1695
  const app = new import_hono.Hono();
@@ -1245,6 +1702,22 @@ function createRestApp(params) {
1245
1702
  applyStaticServing(app, config, runtime, log);
1246
1703
  const servicesPath = `${config.baseUrl}/services`;
1247
1704
  app.post(servicesPath, async (c) => {
1705
+ const contentType = c.req.header("content-type") ?? "";
1706
+ const isFormData = contentType.includes("multipart/form-data");
1707
+ const authContext = {
1708
+ headers: c.req.raw.headers,
1709
+ cookies: (0, import_cookie.getCookie)(c)
1710
+ };
1711
+ if (isFormData) {
1712
+ return handleFormDataPath(
1713
+ c,
1714
+ config,
1715
+ engine,
1716
+ nileContext,
1717
+ authContext,
1718
+ log
1719
+ );
1720
+ }
1248
1721
  const body = await c.req.json().catch(() => null);
1249
1722
  if (!body) {
1250
1723
  return c.json(
@@ -1270,7 +1743,12 @@ function createRestApp(params) {
1270
1743
  const request = parsed.data;
1271
1744
  log(`${request.intent} -> ${request.service}.${request.action}`);
1272
1745
  const handler = intentHandlers[request.intent];
1273
- const response = await handler(engine, request, nileContext);
1746
+ const response = await handler(
1747
+ engine,
1748
+ request,
1749
+ nileContext,
1750
+ authContext
1751
+ );
1274
1752
  const statusCode = response.status ? 200 : 400;
1275
1753
  return c.json(response, statusCode);
1276
1754
  });
@@ -1321,6 +1799,20 @@ function createNileContext(params) {
1321
1799
  setSession(name, data) {
1322
1800
  sessions[name] = data;
1323
1801
  },
1802
+ authResult: void 0,
1803
+ getAuth() {
1804
+ return context.authResult;
1805
+ },
1806
+ getUser() {
1807
+ if (!context.authResult) {
1808
+ return void 0;
1809
+ }
1810
+ return {
1811
+ userId: context.authResult.userId,
1812
+ organizationId: context.authResult.organizationId,
1813
+ ...context.authResult.claims
1814
+ };
1815
+ },
1324
1816
  hookContext: {
1325
1817
  actionName: "",
1326
1818
  input: null,
@@ -1379,6 +1871,7 @@ function createNileServer(config) {
1379
1871
  services: config.services,
1380
1872
  diagnostics: config.diagnostics,
1381
1873
  logger: config.resources?.logger,
1874
+ auth: config.auth,
1382
1875
  onBeforeActionHandler: config.onBeforeActionHandler,
1383
1876
  onAfterActionHandler: config.onAfterActionHandler
1384
1877
  });
@@ -1421,7 +1914,7 @@ function createNileServer(config) {
1421
1914
  if (config.onBoot) {
1422
1915
  const { fn } = config.onBoot;
1423
1916
  const _boot = (async () => {
1424
- const result = await (0, import_slang_ts7.safeTry)(() => fn(nileContext));
1917
+ const result = await (0, import_slang_ts8.safeTry)(() => fn(nileContext));
1425
1918
  if (result.isErr) {
1426
1919
  console.error("[NileServer] onBoot failed:", result.error);
1427
1920
  }
@@ -1445,6 +1938,9 @@ function createNileServer(config) {
1445
1938
  getContext,
1446
1939
  getLogs,
1447
1940
  getZodSchema,
1448
- handleError
1941
+ handleError,
1942
+ handleFormDataRequest,
1943
+ validateFiles,
1944
+ verifyJWT
1449
1945
  });
1450
1946
  //# sourceMappingURL=index.cjs.map