@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 +569 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +153 -3
- package/dist/index.d.ts +153 -3
- package/dist/index.js +549 -56
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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
|
|
392
|
+
var import_slang_ts8 = require("slang-ts");
|
|
319
393
|
|
|
320
394
|
// engine/engine.ts
|
|
321
|
-
var
|
|
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
|
|
399
|
+
var import_slang_ts3 = require("slang-ts");
|
|
326
400
|
|
|
327
401
|
// utils/handle-error.ts
|
|
328
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
825
|
+
return (0, import_slang_ts4.Ok)(true);
|
|
752
826
|
}
|
|
753
|
-
const result = await (0,
|
|
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,
|
|
831
|
+
return (0, import_slang_ts4.Err)(result.error);
|
|
758
832
|
}
|
|
759
|
-
return (0,
|
|
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,
|
|
837
|
+
return (0, import_slang_ts4.Ok)(currentResult);
|
|
764
838
|
}
|
|
765
|
-
const result = await (0,
|
|
839
|
+
const result = await (0, import_slang_ts4.safeTry)(
|
|
766
840
|
() => handler({
|
|
767
841
|
nileContext,
|
|
768
842
|
action,
|
|
769
843
|
payload,
|
|
770
|
-
result: (0,
|
|
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,
|
|
850
|
+
return (0, import_slang_ts4.Err)(result.error);
|
|
777
851
|
}
|
|
778
|
-
return (0,
|
|
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,
|
|
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,
|
|
863
|
+
return (0, import_slang_ts4.Err)(`Validation failed: ${validationError}`);
|
|
790
864
|
}
|
|
791
|
-
return (0,
|
|
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,
|
|
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,
|
|
874
|
+
return (0, import_slang_ts4.Err)(result.error);
|
|
801
875
|
}
|
|
802
876
|
nileContext.setHookOutput(result.value);
|
|
803
|
-
return (0,
|
|
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,
|
|
951
|
+
const getServices = () => (0, import_slang_ts5.Ok)(serviceSummaries);
|
|
846
952
|
const getServiceActions = (serviceName) => {
|
|
847
953
|
const actions = serviceActionsStore[serviceName];
|
|
848
|
-
return actions ? (0,
|
|
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,
|
|
959
|
+
return (0, import_slang_ts5.Err)(`Service '${serviceName}' not found`);
|
|
854
960
|
}
|
|
855
961
|
const action = serviceMap[actionName];
|
|
856
|
-
return action ? (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1043
|
+
return (0, import_slang_ts5.Err)(globalAfterResult.error);
|
|
926
1044
|
}
|
|
927
|
-
return action.result?.pipeline ? (0,
|
|
1045
|
+
return action.result?.pipeline ? (0, import_slang_ts5.Ok)({
|
|
928
1046
|
data: globalAfterResult.value,
|
|
929
1047
|
pipeline: nileContext.hookContext.log
|
|
930
|
-
}) : (0,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
1198
|
-
|
|
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,
|
|
1209
|
-
const mod = await import(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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
|