@karmaniverous/smoz 0.2.15 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +30 -4
- package/dist/index.d.ts +19 -2
- package/dist/mjs/cli/inline-server.js +65 -1
- package/dist/mjs/index.js +28 -5
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -997,6 +997,12 @@ const serverlessConfigSchema = zod.z.object({
|
|
|
997
997
|
defaultHandlerFileExport: zod.z.string().min(1),
|
|
998
998
|
});
|
|
999
999
|
|
|
1000
|
+
/** Security context classification used throughout the handlers. */
|
|
1001
|
+
/**
|
|
1002
|
+
* HTTP security context tokens.
|
|
1003
|
+
*/
|
|
1004
|
+
const httpContexts = ['my', 'private', 'public'];
|
|
1005
|
+
|
|
1000
1006
|
const splitPath = (basePath) => basePath.split('/').filter(Boolean);
|
|
1001
1007
|
/** Prefix non-public contexts and return path elements. */
|
|
1002
1008
|
/**
|
|
@@ -1011,6 +1017,19 @@ const buildPathElements = (basePath, context) => {
|
|
|
1011
1017
|
return context === 'public' ? parts : [context, ...parts];
|
|
1012
1018
|
};
|
|
1013
1019
|
const sanitizeBasePath = (p) => p.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
1020
|
+
/**
|
|
1021
|
+
* Infer the security context from a path that was built by
|
|
1022
|
+
* `buildPathElements`. Non-public contexts appear as the first
|
|
1023
|
+
* path segment; public paths have no context prefix.
|
|
1024
|
+
*/
|
|
1025
|
+
const inferContextFromPath = (path) => {
|
|
1026
|
+
const firstSeg = splitPath(sanitizeBasePath(path))[0];
|
|
1027
|
+
return firstSeg !== undefined &&
|
|
1028
|
+
httpContexts.includes(firstSeg) &&
|
|
1029
|
+
firstSeg !== 'public'
|
|
1030
|
+
? firstSeg
|
|
1031
|
+
: 'public';
|
|
1032
|
+
};
|
|
1014
1033
|
|
|
1015
1034
|
const HTTP_METHODS = new Set([
|
|
1016
1035
|
'get',
|
|
@@ -1129,11 +1148,15 @@ const buildAllServerlessFunctions = (registry, serverless, buildFnEnv) => {
|
|
|
1129
1148
|
...(r.basePath ? { basePath: r.basePath } : {}),
|
|
1130
1149
|
...(r.httpContexts ? { httpContexts: r.httpContexts } : {}),
|
|
1131
1150
|
}, r.callerModuleUrl, r.endpointsRootAbs);
|
|
1132
|
-
const path = `/${basePath.replace(/^\/+/, '')}`;
|
|
1133
1151
|
const ctxs = contexts.length > 0 ? contexts : ['public'];
|
|
1134
|
-
events = ctxs.map(() =>
|
|
1135
|
-
|
|
1136
|
-
|
|
1152
|
+
events = ctxs.map((ctx) => {
|
|
1153
|
+
const elems = buildPathElements(basePath, ctx);
|
|
1154
|
+
const ctxPath = `/${elems.join('/')}`;
|
|
1155
|
+
const ctxEventProps = serverless.httpContextEventMap[ctx];
|
|
1156
|
+
return {
|
|
1157
|
+
http: { method, path: ctxPath, ...ctxEventProps },
|
|
1158
|
+
};
|
|
1159
|
+
});
|
|
1137
1160
|
}
|
|
1138
1161
|
catch {
|
|
1139
1162
|
events = r.serverlessExtras ?? [];
|
|
@@ -1480,12 +1503,15 @@ const dirFromHere = (metaUrl, levelsUp = 1) => {
|
|
|
1480
1503
|
|
|
1481
1504
|
exports.App = App;
|
|
1482
1505
|
exports.baseEventTypeMapSchema = baseEventTypeMapSchema;
|
|
1506
|
+
exports.buildPathElements = buildPathElements;
|
|
1483
1507
|
exports.buildSafeDefaults = buildSafeDefaults;
|
|
1484
1508
|
exports.defineAppConfig = defineAppConfig;
|
|
1485
1509
|
exports.detectSecurityContext = detectSecurityContext;
|
|
1486
1510
|
exports.dirFromHere = dirFromHere;
|
|
1487
1511
|
exports.findIndex = findIndex;
|
|
1488
1512
|
exports.getId = getId;
|
|
1513
|
+
exports.httpContexts = httpContexts;
|
|
1514
|
+
exports.inferContextFromPath = inferContextFromPath;
|
|
1489
1515
|
exports.insertAfter = insertAfter;
|
|
1490
1516
|
exports.insertBefore = insertBefore;
|
|
1491
1517
|
exports.removeStep = removeStep;
|
package/dist/index.d.ts
CHANGED
|
@@ -122,7 +122,8 @@ declare function buildStageArtifacts<GlobalParamsSchema extends ZodObject<ZodRaw
|
|
|
122
122
|
/**
|
|
123
123
|
* HTTP security context tokens.
|
|
124
124
|
*/
|
|
125
|
-
|
|
125
|
+
declare const httpContexts: readonly ["my", "private", "public"];
|
|
126
|
+
type HttpContext = (typeof httpContexts)[number];
|
|
126
127
|
|
|
127
128
|
/**
|
|
128
129
|
* Extract the property type `K` from a union of object types `U`.
|
|
@@ -625,6 +626,22 @@ declare function wrapHandler<GlobalParamsSchema extends ZodObject<ZodRawShape>,
|
|
|
625
626
|
*/
|
|
626
627
|
type LambdaEvent = Record<string, unknown>;
|
|
627
628
|
|
|
629
|
+
/** Prefix non-public contexts and return path elements. */
|
|
630
|
+
/**
|
|
631
|
+
* Build OpenAPI/serverless path elements from a base path and context.
|
|
632
|
+
*
|
|
633
|
+
* @param basePath - base path (e.g., 'users/:id')
|
|
634
|
+
* @param context - 'my' | 'private' | 'public' (public is unprefixed)
|
|
635
|
+
* @returns array of path segments without leading/trailing slashes
|
|
636
|
+
*/
|
|
637
|
+
declare const buildPathElements: (basePath: string, context: HttpContext) => string[];
|
|
638
|
+
/**
|
|
639
|
+
* Infer the security context from a path that was built by
|
|
640
|
+
* `buildPathElements`. Non-public contexts appear as the first
|
|
641
|
+
* path segment; public paths have no context prefix.
|
|
642
|
+
*/
|
|
643
|
+
declare const inferContextFromPath: (path: string) => HttpContext;
|
|
644
|
+
|
|
628
645
|
/** Normalize a path to POSIX separators. */
|
|
629
646
|
declare const toPosixPath: (p: string) => string;
|
|
630
647
|
/**
|
|
@@ -636,5 +653,5 @@ declare const toPosixPath: (p: string) => string;
|
|
|
636
653
|
*/
|
|
637
654
|
declare const dirFromHere: (metaUrl: string, levelsUp?: number) => string;
|
|
638
655
|
|
|
639
|
-
export { App, baseEventTypeMapSchema, buildSafeDefaults, defineAppConfig, detectSecurityContext, dirFromHere, findIndex, getId, insertAfter, insertBefore, removeStep, replaceStep, tagStep, toPosixPath, wrapHandler };
|
|
656
|
+
export { App, baseEventTypeMapSchema, buildPathElements, buildSafeDefaults, defineAppConfig, detectSecurityContext, dirFromHere, findIndex, getId, httpContexts, inferContextFromPath, insertAfter, insertBefore, removeStep, replaceStep, tagStep, toPosixPath, wrapHandler };
|
|
640
657
|
export type { AppHttpConfig, AppInit, ConsoleLogger, DefineAppConfigInput, DefineAppConfigOutput, EnvKeysNode, EnvSchemaNode, FunctionHttpConfig, GlobalEnvConfig, GlobalParamsNode, Handler, HandlerOptions, HttpContext, HttpProfile, HttpStackOptions, LambdaEvent, MethodKey, SecurityContextHttpEventMap, ShapedEvent, StageParamsNode };
|
|
@@ -48,6 +48,46 @@ const match = (segs, pathName) => {
|
|
|
48
48
|
}
|
|
49
49
|
return { ok: true, params };
|
|
50
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Decode JWT payload without signature validation (local dev only).
|
|
53
|
+
* Returns the parsed claims object, or an empty object on failure.
|
|
54
|
+
*/
|
|
55
|
+
const decodeJwtClaims = (token) => {
|
|
56
|
+
try {
|
|
57
|
+
const parts = token.replace(/^Bearer\s+/i, '').split('.');
|
|
58
|
+
if (parts.length < 2)
|
|
59
|
+
return {};
|
|
60
|
+
const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
|
|
61
|
+
const parsed = JSON.parse(payload);
|
|
62
|
+
return typeof parsed === 'object' && parsed !== null
|
|
63
|
+
? parsed
|
|
64
|
+
: {};
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Build a local-dev authorizer that makes `detectSecurityContext`
|
|
72
|
+
* return the correct context for the route.
|
|
73
|
+
*
|
|
74
|
+
* Only populates the authorizer when the client actually sends
|
|
75
|
+
* credentials, so `detectSecurityContext` reflects reality:
|
|
76
|
+
* - `my` route + JWT in Authorization header → decoded claims.
|
|
77
|
+
* - `private` route → no authorizer; the forwarded `x-api-key`
|
|
78
|
+
* header is enough for `detectSecurityContext`.
|
|
79
|
+
* - No credentials → empty authorizer → `public`.
|
|
80
|
+
*/
|
|
81
|
+
const buildLocalAuthorizer = (context, authHeader) => {
|
|
82
|
+
if (context === 'my' && authHeader) {
|
|
83
|
+
const claims = decodeJwtClaims(authHeader);
|
|
84
|
+
if (Object.keys(claims).length > 0)
|
|
85
|
+
return { claims };
|
|
86
|
+
}
|
|
87
|
+
// Return undefined so detectSecurityContext sees no authorizer,
|
|
88
|
+
// rather than {} which it treats as truthy.
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
51
91
|
const toEvent = async (req, route, params) => {
|
|
52
92
|
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
53
93
|
const { single, multi } = toHeaders(req.headers);
|
|
@@ -90,7 +130,7 @@ const toEvent = async (req, route, params) => {
|
|
|
90
130
|
requestTimeEpoch: Date.now(),
|
|
91
131
|
resourceId: 'res',
|
|
92
132
|
resourcePath: route.pattern,
|
|
93
|
-
authorizer:
|
|
133
|
+
authorizer: buildLocalAuthorizer(route.context, single['authorization']),
|
|
94
134
|
protocol: 'HTTP/1.1',
|
|
95
135
|
},
|
|
96
136
|
};
|
|
@@ -149,6 +189,28 @@ const loadApp = async (root) => {
|
|
|
149
189
|
return app;
|
|
150
190
|
};
|
|
151
191
|
|
|
192
|
+
/** Security context classification used throughout the handlers. */
|
|
193
|
+
/**
|
|
194
|
+
* HTTP security context tokens.
|
|
195
|
+
*/
|
|
196
|
+
const httpContexts = ['my', 'private', 'public'];
|
|
197
|
+
|
|
198
|
+
const splitPath = (basePath) => basePath.split('/').filter(Boolean);
|
|
199
|
+
const sanitizeBasePath = (p) => p.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
200
|
+
/**
|
|
201
|
+
* Infer the security context from a path that was built by
|
|
202
|
+
* `buildPathElements`. Non-public contexts appear as the first
|
|
203
|
+
* path segment; public paths have no context prefix.
|
|
204
|
+
*/
|
|
205
|
+
const inferContextFromPath = (path) => {
|
|
206
|
+
const firstSeg = splitPath(sanitizeBasePath(path))[0];
|
|
207
|
+
return firstSeg !== undefined &&
|
|
208
|
+
httpContexts.includes(firstSeg) &&
|
|
209
|
+
firstSeg !== 'public'
|
|
210
|
+
? firstSeg
|
|
211
|
+
: 'public';
|
|
212
|
+
};
|
|
213
|
+
|
|
152
214
|
const splitPattern = (p) => p
|
|
153
215
|
.replace(/\\/g, '/')
|
|
154
216
|
.replace(/^\/+|\/+$/g, '')
|
|
@@ -195,11 +257,13 @@ const loadHandlers = async (root, app) => {
|
|
|
195
257
|
const pattern = '/' + (httpEvt?.path ?? '').replace(/^\/+/, '');
|
|
196
258
|
if (!method || !pattern)
|
|
197
259
|
continue;
|
|
260
|
+
const context = inferContextFromPath(pattern);
|
|
198
261
|
routes.push({
|
|
199
262
|
method,
|
|
200
263
|
pattern,
|
|
201
264
|
segs: splitPattern(pattern),
|
|
202
265
|
handlerRef: `${moduleRel}.${exportName}`,
|
|
266
|
+
context,
|
|
203
267
|
handler: handler,
|
|
204
268
|
});
|
|
205
269
|
}
|
package/dist/mjs/index.js
CHANGED
|
@@ -995,6 +995,12 @@ const serverlessConfigSchema = z.object({
|
|
|
995
995
|
defaultHandlerFileExport: z.string().min(1),
|
|
996
996
|
});
|
|
997
997
|
|
|
998
|
+
/** Security context classification used throughout the handlers. */
|
|
999
|
+
/**
|
|
1000
|
+
* HTTP security context tokens.
|
|
1001
|
+
*/
|
|
1002
|
+
const httpContexts = ['my', 'private', 'public'];
|
|
1003
|
+
|
|
998
1004
|
const splitPath = (basePath) => basePath.split('/').filter(Boolean);
|
|
999
1005
|
/** Prefix non-public contexts and return path elements. */
|
|
1000
1006
|
/**
|
|
@@ -1009,6 +1015,19 @@ const buildPathElements = (basePath, context) => {
|
|
|
1009
1015
|
return context === 'public' ? parts : [context, ...parts];
|
|
1010
1016
|
};
|
|
1011
1017
|
const sanitizeBasePath = (p) => p.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
1018
|
+
/**
|
|
1019
|
+
* Infer the security context from a path that was built by
|
|
1020
|
+
* `buildPathElements`. Non-public contexts appear as the first
|
|
1021
|
+
* path segment; public paths have no context prefix.
|
|
1022
|
+
*/
|
|
1023
|
+
const inferContextFromPath = (path) => {
|
|
1024
|
+
const firstSeg = splitPath(sanitizeBasePath(path))[0];
|
|
1025
|
+
return firstSeg !== undefined &&
|
|
1026
|
+
httpContexts.includes(firstSeg) &&
|
|
1027
|
+
firstSeg !== 'public'
|
|
1028
|
+
? firstSeg
|
|
1029
|
+
: 'public';
|
|
1030
|
+
};
|
|
1012
1031
|
|
|
1013
1032
|
const HTTP_METHODS = new Set([
|
|
1014
1033
|
'get',
|
|
@@ -1127,11 +1146,15 @@ const buildAllServerlessFunctions = (registry, serverless, buildFnEnv) => {
|
|
|
1127
1146
|
...(r.basePath ? { basePath: r.basePath } : {}),
|
|
1128
1147
|
...(r.httpContexts ? { httpContexts: r.httpContexts } : {}),
|
|
1129
1148
|
}, r.callerModuleUrl, r.endpointsRootAbs);
|
|
1130
|
-
const path = `/${basePath.replace(/^\/+/, '')}`;
|
|
1131
1149
|
const ctxs = contexts.length > 0 ? contexts : ['public'];
|
|
1132
|
-
events = ctxs.map(() =>
|
|
1133
|
-
|
|
1134
|
-
|
|
1150
|
+
events = ctxs.map((ctx) => {
|
|
1151
|
+
const elems = buildPathElements(basePath, ctx);
|
|
1152
|
+
const ctxPath = `/${elems.join('/')}`;
|
|
1153
|
+
const ctxEventProps = serverless.httpContextEventMap[ctx];
|
|
1154
|
+
return {
|
|
1155
|
+
http: { method, path: ctxPath, ...ctxEventProps },
|
|
1156
|
+
};
|
|
1157
|
+
});
|
|
1135
1158
|
}
|
|
1136
1159
|
catch {
|
|
1137
1160
|
events = r.serverlessExtras ?? [];
|
|
@@ -1476,4 +1499,4 @@ const dirFromHere = (metaUrl, levelsUp = 1) => {
|
|
|
1476
1499
|
return toPosixPath(abs);
|
|
1477
1500
|
};
|
|
1478
1501
|
|
|
1479
|
-
export { App, baseEventTypeMapSchema, buildSafeDefaults, defineAppConfig, detectSecurityContext, dirFromHere, findIndex, getId, insertAfter, insertBefore, removeStep, replaceStep, tagStep, toPosixPath, wrapHandler };
|
|
1502
|
+
export { App, baseEventTypeMapSchema, buildPathElements, buildSafeDefaults, defineAppConfig, detectSecurityContext, dirFromHere, findIndex, getId, httpContexts, inferContextFromPath, insertAfter, insertBefore, removeStep, replaceStep, tagStep, toPosixPath, wrapHandler };
|
package/package.json
CHANGED
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
"templates:lint": "eslint --fix -c templates/default/eslint.config.ts \"templates/default/**/*.{ts,tsx,js,jsx}\" \"templates/default/eslint.config.ts\""
|
|
184
184
|
},
|
|
185
185
|
"type": "module",
|
|
186
|
-
"version": "0.2.
|
|
186
|
+
"version": "0.2.16",
|
|
187
187
|
"volta": {
|
|
188
188
|
"node": "22.19.0"
|
|
189
189
|
}
|